tipos opciones imprimir hacer funciones ejemplos como ciclos basico haskell

opciones - Haskell: ¿Por qué usar Proxy?



imprimir en haskell (2)

Dos ejemplos, uno donde Proxy es necesario y otro donde Proxy no cambia fundamentalmente los tipos, pero tiendo a usarlo de todos modos.

Proxy necesario

Proxy o algún truco equivalente es necesario cuando hay algún tipo intermedio, no expuesto en la firma de tipo normal, que desea que el consumidor pueda especificar. Quizás el tipo intermedio cambie la semántica, como read . show :: String -> String read . show :: String -> String . Con ScopedTypeVariables habilitado, escribiría

f :: forall proxy a. (Read a, Show a) => proxy a -> String -> String f _ = (show :: a -> String) . read

> f (Proxy :: Proxy Int) "3" "3" > f (Proxy :: Proxy Bool) "3" "*** Exception: Prelude.read: no parse

El parámetro proxy me permite exponer a como un parámetro de tipo. show . read show . read es una especie de ejemplo estúpido. Una mejor situación puede ser cuando algún algoritmo utiliza una colección genérica internamente, donde el tipo de colección seleccionado tiene algunas características de rendimiento que usted desea que el consumidor pueda controlar sin requerir (o permitir) que proporcione o reciba el valor intermedio.

Algo como esto, usando tipos de fgl , donde no queremos exponer el tipo de Data interno. (¿Quizás alguien puede sugerir un algoritmo apropiado para este ejemplo?)

f :: Input -> Output f = g . h where h :: Gr graph Data => Input -> graph Data g :: Gr graph Data => graph Data -> Output

La exposición de un argumento proxy le permitiría al usuario seleccionar entre un árbol Patricia o una implementación de árbol normal.

Proxy como API o conveniencia de implementación

A veces uso Proxy como una herramienta para elegir una instancia de clase de tipo, especialmente en instancias de clases recursivas o inductivas. Considere la clase MightBeA que escribí en esta respuesta sobre el uso de Anidados s :

class MightBeA t a where isA :: proxy t -> a -> Maybe t fromA :: t -> a instance MightBeA t t where isA _ = Just fromA = id instance MightBeA t (Either t b) where isA _ (Left i) = Just i isA _ _ = Nothing fromA = Left instance MightBeA t b => MightBeA t (Either a b) where isA p (Right xs) = isA p xs isA _ _ = Nothing fromA = Right . fromA

La idea es extraer un Maybe Int de, digamos, Either String (Either Bool Int) . El tipo de isA es básicamente a -> Maybe t . Hay dos razones para usar un proxy aquí:

Primero, elimina las firmas de tipo para el consumidor. Puede llamar a isA como isA (Proxy :: Proxy Int) lugar de isA :: MightBeA Int a => a -> Maybe Int .

En segundo lugar, es más fácil para mí pensar en el caso inductivo simplemente pasando el proxy. Con ScopedTypeVariables , la clase se puede reescribir sin un argumento proxy; el caso inductivo se implementaría como

instance MightBeA'' t b => MightBeA'' t (Either a b) where -- no proxy argument isA'' (Right xs) = (isA'' :: b -> Maybe t) xs isA'' _ = Nothing fromA'' = Right . fromA''

Esto no es realmente un gran cambio en este caso; si la firma de tipo de isA fuera considerablemente más compleja, usar el proxy sería una gran mejora.

Cuando el uso es exclusivamente para facilitar la implementación, normalmente exportaría una función de contenedor para que el usuario no necesite proporcionar el proxy.

Proxy vs. Tagged

En todos mis ejemplos, el parámetro tipo a no agrega nada útil al tipo de salida en sí. (En los primeros dos ejemplos, no está relacionado con el tipo de salida; en el último ejemplo, es redundante del tipo de salida). Si devolviera un Tagged ax , el consumidor invariablemente lo eliminaría inmediatamente. Además, el usuario tendrá que escribir el tipo de x en su totalidad, lo que a veces es muy inconveniente porque es un tipo intermedio complicado. (Tal vez algún día podamos usar _ en firmas de tipo ... )

(Estoy interesado en escuchar otras respuestas sobre esta sub-pregunta, literalmente nunca he escrito nada usando Tagged (sin reescribirlo en poco tiempo usando Proxy ) y me pregunto si me falta algo).

En Haskell, un Proxy es un tipo de valor testigo que hace que sea fácil pasar algunos tipos alrededor

data Proxy a = Proxy

Un ejemplo de uso está aquí en json-schema :

class JSONSchema a where schema :: Proxy a -> Schema

por lo que podría hacer un schema (Proxy :: Proxy (Int,Char)) para obtener lo que sería la representación JSON para un Int-Char-Tuple (probablemente una matriz).

¿Por qué la gente usa proxies? Me parece que lo mismo podría lograrse

class JSONSchema a where schema :: Schema a

similar a cómo funciona la clase de tipo Bounded . Primero pensé que podría ser más fácil obtener el esquema de algún valor dado al usar proxies, pero eso no parece ser cierto:

{-# LANGUAGE ScopedTypeVariables #-} schemaOf :: JSONSchema a => a -> Schema a schemaOf (v :: x) = schema (Proxy :: Proxy x) -- With proxy schemaOf (v :: x) = schema :: Schema x -- With `:: a` schemaOf _ = schema -- Even simpler with `:: a`

Además, uno podría preocuparse si los valores Proxy realmente se erradican en tiempo de ejecución, que es un problema de optimización que no existe cuando se utiliza el enfoque :: a .

Si el enfoque :: a adoptado por Bounded logra el mismo resultado con un código más corto y menos preocupaciones sobre la optimización, ¿por qué las personas usan proxies? ¿Cuáles son los beneficios de los proxies?

EDITAR: Algunas respuestas y comentaristas señalaron con acierto que el enfoque :: a contamina los data Schema = ... escribe con un parámetro de tipo "inútil", al menos desde la perspectiva de la propia estructura de datos, que no usa nunca el a ( ver aquí ).

La sugerencia es usar el tipo fantasma Tagged sb , que permite separar las dos preocupaciones ( Tagged a Schema combina el tipo de esquema no paramétrico con una variable de tipo a ), que es estrictamente mejor que el :: a approach.

Entonces mi pregunta debería ser mejor ¿Cuáles son los beneficios de los proxies versus el enfoque etiquetado?


En última instancia, realizarán la misma funcionalidad y los verá en cualquier estilo. A veces es apropiado etiquetar sus valores fantasma, a veces le gustaría considerarlos como sin tipo.

La otra alternativa es usar Data.Tagged .

class JSONSchema a where schema :: Tagged a Schema

Aquí tenemos algo de lo mejor de ambos mundos ya que un Schema Tagged tiene información de tipo fantasma necesaria para resolver la instancia, pero podemos pasar por alto trivialmente esa información usando unTagged :: Tagged sb -> b .

Diría que la pregunta de conducción, redactada en términos de este ejemplo, debería ser "¿Deseo considerar operaciones mecanografiadas en Schema s?". Si la respuesta es "no", entonces estará predispuesto hacia los enfoques de Proxy o Tagged . Si la respuesta es "sí", entonces Schema a es una gran solución.

Como nota final, puede usar el enfoque de Proxy (algo hackily) sin ninguna importación. Ves esto a veces en el estilo

class JSONSchema a where schema :: proxy a -> Schema

Ahora que Proxy ha convertido en una variable tipo sugestiva, solo podemos hacer algo como lo siguiente

foo :: Schema foo = schema ([] :: [X])

y nunca tiene que importar Proxy en absoluto. Personalmente creo que este es un trabajo de hack completo, que probablemente terminará por confundir a los lectores.