wikileaks teresa quien que pelicula hoy daniel assange haskell type-families

haskell - teresa - julian assange wikileaks



''familia de tipo'' frente a ''familia de datos'', en resumen? (2)

Estoy confundido acerca de cómo elegir entre data family y type family . La página wiki en TypeFamilies entra en muchos detalles. Ocasionalmente se refiere informalmente a la data family de Haskell como una "familia tipográfica" en prosa, pero por supuesto también hay type family en Haskell.

Hay un ejemplo simple que muestra dónde se muestran dos versiones de código, que difieren solo en si se está declarando una data family o una type family :

-- BAD: f is too ambiguous, due to non-injectivity -- type family F a -- OK data family F a f :: F a -> F a f = undefined g :: F Int -> F Int g x = f x

type y los data tienen el mismo significado, pero la versión type family falla al verificar el tipo, mientras que la versión de la data family está bien, porque data family "crea nuevos tipos y por lo tanto son inyectivos" (dice la página wiki).

Lo que me lleva de todo esto es "probar data family para casos simples y, si no es lo suficientemente potente, intente type family ". Lo cual está bien, pero me gustaría entenderlo mejor. ¿Existe un diagrama de Venn o un árbol de decisión que pueda seguir para distinguir cuándo usarlo?


No creo que exista ningún árbol de decisión o diagrama de Venn porque las aplicaciones para las familias de tipos y datos son bastante amplias.

En general, ya ha resaltado las diferencias de diseño clave y estoy de acuerdo con su punto de partida para ver primero si puede salirse con la suya con data family .

Para mí, el punto clave es que cada instancia de una data family crea un nuevo tipo, que limita sustancialmente la potencia porque no se puede hacer lo que a menudo es lo más natural y hacer que un tipo existente sea la instancia.

Por ejemplo, el ejemplo de GMapKey en la página wiki de Haskell sobre "tipos indexados" es un ajuste razonablemente natural para las familias de datos:

class GMapKey k where data GMap k :: * -> * empty :: GMap k v lookup :: k -> GMap k v -> Maybe v insert :: k -> v -> GMap k v -> GMap k v

El tipo de clave del mapa k es el argumento para la familia de datos y el tipo de mapa real es el resultado de la familia de datos ( GMap k ). Como usuario de una instancia de GMapKey , probablemente esté muy satisfecho de que el tipo de GMap k sea ​​abstracto para usted y simplemente lo manipule a través de las operaciones de mapa genéricas en la clase de tipo.

Por el contrario, el ejemplo de Collects en la misma página wiki es algo opuesto:

class Collects ce where type Elem ce empty :: ce insert :: Elem ce -> ce -> ce member :: Elem ce -> ce -> Bool toList :: ce -> [Elem ce]

El tipo de argumento es la colección y el tipo de resultado es el elemento de la colección. En general, un usuario va a querer operar esos elementos directamente utilizando las operaciones normales de ese tipo. Por ejemplo, la colección podría ser IntSet y el elemento podría ser Int . Tener el Int envuelto en algún otro tipo sería bastante inconveniente.

Nota: estos dos ejemplos son con clases de tipo y, por lo tanto, no necesitan la palabra clave family porque declarar un tipo dentro de una clase type implica que debe ser una familia. Sin embargo, se aplican exactamente las mismas consideraciones que para las familias independientes, solo se trata de cómo se organiza la abstracción.


(Impulsar información útil de los comentarios en una respuesta).

Declaración independiente vs In-Class

Dos maneras sintácticamente diferentes de declarar un tipo familia y / o familia de datos , que son semánticamente equivalentes:

ser único:

type family Foo data family Bar

o como parte de una clase de tipo:

class C where type Foo data Bar

ambos declaran una familia de tipos, pero dentro de una clase de tipos, la parte family está implícita en el contexto de class , por lo que GHC / Haskell abrevia la declaración.

"Tipo nuevo" frente a "Tipo sinónimo" / "Tipo alias"

data family F crea un nuevo tipo, similar a cómo los data F = ... crean un nuevo tipo.

type family F no crea un nuevo tipo, similar a cómo type F = Bar Baz no crea un nuevo tipo (simplemente crea un alias / sinónimo a un tipo existente).

Ejemplo de no-inyectividad de type family

Un ejemplo (ligeramente modificado) de Data.MonoTraversable.Element :

import Data.ByteString as S import Data.ByteString.Lazy as L -- Declare a family of type synonyms, called `Element` -- `Element` has kind `* -> *`; it takes one parameter, which we call `container` type family Element container -- ByteString is a container for Word8, so... -- The Element of a `S.ByteString` is a `Word8` type instance Element S.ByteString = Word8 -- and the Element of a `L.ByteString` is also `Word8` type instance Element L.ByteString = Word8

En una familia de tipos, el lado derecho de las ecuaciones Word8 nombra un tipo existente; las cosas que están a la izquierda crean nuevos sinónimos: Element S.ByteString y Element L.ByteString

Tener un sinónimo significa que podemos intercambiar Element Data.ByteString con Word8 :

-- `w` is a Word8.... >let w = 0::Word8 -- ... and also an `Element L.ByteString` >:t w :: Element L.ByteString w :: Element L.ByteString :: Word8 -- ... and also an `Element S.ByteString` >:t w :: Element S.ByteString w :: Element S.ByteString :: Word8 -- but not an `Int` >:t w :: Int Couldn''t match expected type `Int'' with actual type `Word8''

Estos sinónimos de tipo son "no-inyectables" ("unidireccionales"), y por lo tanto no invertibles.

-- As before, `Word8` matches `Element L.ByteString` ... >(0::Word8)::(Element L.ByteString) -- .. but GHC can''t infer which `a` is the `Element` of (`L.ByteString` or `S.ByteString` ?): >(w)::(Element a) Couldn''t match expected type `Element a'' with actual type `Element a0'' NB: `Element'' is a type function, and may not be injective The type variable `a0'' is ambiguous

Peor aún, ¡GHC ni siquiera puede resolver casos no ambiguos !:

type instance Element Int = Bool > True::(Element a) > NB: `Element'' is a type function, and may not be injective

Tenga en cuenta el uso de "puede no ser"! Creo que GHC es conservador y se niega a verificar si el Element es realmente inyectivo. (Tal vez porque un programador podría agregar otra type instance más tarde, después de importar un módulo precompilado, agregando ambigüedad.

Ejemplo de inyectividad de data family de data family

En contraste: en una familia de datos, cada lado derecho contiene un constructor único, por lo que las definiciones son ecuaciones inyectivas ("reversibles").

-- Declare a list-like data family data family XList a -- Declare a list-like instance for Char data instance XList Char = XCons Char (XList Char) | XNil -- Declare a number-like instance for () data instance XList () = XListUnit Int -- ERROR: "Multiple declarations of `XListUnit''" data instance XList () = XListUnit Bool -- (Note: GHCI accepts this; the new declaration just replaces the previous one.)

Con data family , al ver el nombre del constructor a la derecha ( XCons o XListUnit ) es suficiente para que el tipo-inferencer sepa que debemos trabajar con XList () no con un XList Char . Como los nombres de los constructores son únicos, estas definiciones son inyectivas / reversibles.

Si el type "solo" declara un sinónimo, ¿por qué es semánticamente útil?

Por lo general, los sinónimos de type son solo abreviaturas, pero los sinónimos de familia type tienen mayor poder: pueden hacer que un tipo simple (kind * ) se convierta en sinónimo de "tipo con kind * -> * aplicado a un argumento":

type instance F A = B

hace que B coincida con F a . Esto se usa, por ejemplo, en Data.MonoTraversable para hacer un tipo simple de las funciones de concordancia de Word8 del tipo Element a -> a (El Element se define arriba).

Por ejemplo, (un poco tonto), supongamos que tenemos una versión de const que solo funciona con tipos "relacionados":

> class Const a where constE :: (Element a) -> a -> (Element a) > instance Const S.ByteString where constE = const > constE (0::Word8) undefined ERROR: Couldn''t match expected type `Word8'' with actual type `Element a0'' -- By providing a match `a = S.ByteString`, `Word8` matches `(Element S.ByteString)` > constE (0::Word8) (undefined::S.ByteString) 0 -- impossible, since `Char` cannot match `Element a` under our current definitions. > constF ''x'' undefined