2012-07-06 26 views
5

Usted no offen ver Maybe List excepción de control de errores, por ejemplo, porque las listas son un poco Maybe mismos: tienen su propio "Nothing ': [] y su propia' Just": (:). Escribí un tipo de lista usando Maybe y funciones para convertir listas estándar y "experimentales". toStd . toExp == id.Listas definidas como ¿Tal vez en Haskell? Por qué no?

data List a = List a (Maybe (List a)) 
    deriving (Eq, Show, Read) 

toExp [] = Nothing 
toExp (x:xs) = Just (List x (toExp xs)) 

toStd Nothing = [] 
toStd (Just (List x xs)) = x : (toStd xs) 

¿Qué opinas al respecto, como un intento de reducir la repetición, para generalizar?

Los árboles también se podría definir el uso de estas listas:

type Tree a = List (Tree a, Tree a) 

no he probado esta última pieza de código, sin embargo.

+3

'Sup dawg te escuché como las Mónadas, así que puse una Mónada en tu Mónada ...! has visto 'Maybe String' en realidad es del tipo' Maybe [Char] ', pero creo que estás reinventando los transformadores Monad (mira http://en.wikibooks.org/wiki/Haskell/Monad_transformers) pero yo soy No estoy seguro ya que yo mismo no estoy muy familiarizado con las mónadas en este momento. – epsilonhalbe

+0

Oh, vi muchos 'Maybe String' (http://www.haskell.org/hoogle/?hoogle=Maybe+%5BChar%5D)here aquí) pero tienen un significado diferente. Solo señalé que '[]' es una especie de 'Nada' y demás, así que pensé en usar' Nothing' para eliminar la "redefinición". – L01man

+0

No es redefinición. List y Maybe tienen semántica diferente cuando se usan como mónadas. También difuminar la línea y tirar la sintaxis del azúcar (especialmente los patrones de la lista) es francamente estúpido. –

Respuesta

9

Todos ADTs son isomorfos (casi - véase el final) a alguna combinación de (,), Either, (), (->), Void y Mu donde

data Void --using empty data decls or 
newtype Void = Void Void 

y Mu calcula el punto fijo de un funtor

newtype Mu f = Mu (f (Mu f)) 

así que por ejemplo

data [a] = [] | (a:[a]) 

es la misma que

data [a] = Mu (ListF a) 
data ListF a f = End | Pair a f 

que en sí misma es isomorfo a

newtype ListF a f = ListF (Either() (a,f)) 

desde

data Maybe a = Nothing | Just a 

es isomorfo a

newtype Maybe a = Maybe (Either() a) 

tiene

newtype ListF a f = ListF (Maybe (a,f)) 

que puede ser inline en las mu a

data List a = List (Maybe (a,List a)) 

y su definición

data List a = List a (Maybe (List a)) 

es sólo el despliegue de la Mu y la eliminación del exterior Tal vez (correspondiente a listas no vacías)

y listo ...

un par de cosas

  1. Uso de los ADT personalizados aumenta la claridad y el tipo de seguridad

  2. Esta universalidad es útil: veo GHC.Generic


Vale, dijo casi isomorfo. No es exactamente, a saber

hmm = List (Just undefined) 

no tiene valor equivalente en la definición de las listas [a] = [] | (a:[a]). Esto se debe a que los tipos de datos Haskell son coinductivos y han sido a point of criticism of the lazy evaluation model. Usted puede obtener en torno a estos problemas utilizando sólo sumas y productos estrictas (y por llamar a funciones de valor), y añadiendo un constructor de datos especiales "perezoso"

data SPair a b = SPair !a !b 
data SEither a b = SLeft !a | SRight !b 
data Lazy a = Lazy a --Note, this has no obvious encoding in Pure CBV languages, 
--although Laza a = (() -> a) is semantically correct, 
--it is strictly less efficient than Haskell's CB-Need 

y luego todos los isomorfismos se pueden codificar con fidelidad.

+0

¿Cómo expresaría tipos de datos no regulares, como 'data Tree a = Zero a | Succ (Árbol (Nodo a)) 'donde' data Nodo a = Nodo2 a a | Nodo3 aa a' (es decir, 2-3 arbol)? – Vitus

+0

@Vitus Tienes razón. La recursión polimórfica es más complicada. –

+0

@Vitus. Tuve que probar esto en realidad funciona: 'newtype Mu2 (f :: (* -> *) -> (* -> *)) a = Mu2 (f (Mu2 f) a)' entonces puedes definir 'data TreeF fa = ZeroF a | SuccF (f (Nodo a)) 'y' newtype Tree 'a = Tree' (Mu2 TreeF a) '. Entonces necesitas esa operación 'Mu' de mayor kinded, pero esto no es esencial (y no creo que necesites algo más alto que esto, pero podría estar equivocado). –

7

Puede definir listas en términos de Maybe, pero no de esa manera. Su tipo List no puede estar vacío. ¿O pretendía que Maybe (List a) fuera el reemplazo de [a]? Esto parece malo ya que no distingue la lista y tal vez los tipos.

Esto funcionaría

newtype List a = List (Maybe (a, List a)) 

Esto tiene algunos problemas. Primero usar esto sería más detallado que las listas habituales, y segundo, el dominio no es isomorfo para las listas ya que tenemos un par allí (que puede estar indefinido, agregando un nivel extra en el dominio).

+0

¡La lista vacía es 'Nada'! Y el tipo sigue siendo 'Maybe (List a)'. 'Nothing' actúa como' [] '. Separé el caso 'Nothing' a propósito, porque ya es un constructor de datos de' Maybe'. Tienes razón; no podemos tener una lista vacía de tipo 'List a', y esta nueva lista no es idéntica a la estándar. – L01man

+1

No me gusta tal vez y me gusta tener el mismo tipo. – augustss

+0

¿Tienen una semántica diferente? – L01man

9

Puede definir listas de muchas maneras en Haskell. Por ejemplo, como funciones:

{-# LANGUAGE RankNTypes #-} 

newtype List a = List { runList :: forall b. (a -> b -> b) -> b -> b } 

nil :: List a 
nil = List (\_ z -> z) 

cons :: a -> List a -> List a 
cons x xs = List (\f z -> f x (runList xs f z)) 

isNil :: List a -> Bool 
isNil xs = runList xs (\x xs -> False) True 

head :: List a -> a 
head xs = runList xs (\x xs -> x) (error "empty list") 

tail :: List a -> List a 
tail xs | isNil xs = error "empty list" 
tail xs = fst (runList xs go (nil, nil)) 
    where go x (xs, xs') = (xs', cons x xs) 

foldr :: (a -> b -> b) -> b -> List a -> b 
foldr f z xs = runList xs f z 

El truco de esta implementación es que las listas están siendo representadas como funciones que ejecutan un pliegue sobre los elementos de la lista:

fromNative :: [a] -> List a 
fromNative xs = List (\f z -> foldr f z xs) 

toNative :: List a -> [a] 
toNative xs = runList xs (:) [] 

En cualquier caso, lo que realmente es el contrato (o leyes) que el tipo y sus operaciones siguen, y el funcionamiento de la implementación. Básicamente, cualquier implementación que cumpla con el contrato le dará los programas correctos, y las implementaciones más rápidas le proporcionarán programas más rápidos.

¿Cuál es el contrato de listas?Bueno, no voy a expresarlo con todo detalle, pero las listas de obedecer a declaraciones como éstas:

  1. head (x:xs) == x
  2. tail (x:xs) == xs
  3. [] == []
  4. [] /= x:xs
  5. Si xs == ys y x == y, entonces x:xs == y:ys
  6. foldr f z [] == z
  7. foldr f z (x:xs) == f x (foldr f z xs)

EDIT: Y para atar a este augustss' respuesta:

newtype ExpList a = ExpList (Maybe (a, ExpList a)) 

toExpList :: List a -> ExpList a 
toExpList xs = runList xs (\x xs -> ExpList (Just (x, xs))) (ExpList Nothing) 

foldExpList f z (ExpList Nothing) = z 
foldExpList f z (ExpList (Just (head, taill))) = f head (foldExpList f z tail) 

fromExpList :: ExpList a -> List a 
fromExpList xs = List (\f z -> foldExpList f z xs) 
1

Si se trata de una lista, que debe ser una instancia de Functor, ¿verdad?

instance Functor List 
    where fmap f (List a as) = List (f a) (mapMaybeList f as) 

mapMaybeList :: (a -> b) -> Maybe (List a) -> Maybe (List b) 
mapMaybeList f as = fmap (fmap f) as 

Aquí hay un problema: se puede hacer List una instancia de Functor, pero su Quizás lista no es: incluso si Maybe no era ya una instancia de Functor por derecho propio, no se puede hacer directamente una construcción como Maybe . List en una instancia de cualquier cosa (necesitaría un tipo de envoltura).

De forma similar para otras clases de tipos.


Una vez dicho esto, con su formulación se puede hacer esto, que no se puede hacer con el estándar Haskell estados:

instance Comonad List 
    where extract (List a _) = a 
     duplicate x @ (List _ y) = List x (duplicate y) 

Una lista Quizás todavía no sería comonadic sin embargo.

+0

¿Es un problema? Todavía podemos hacer 'fmap (fmap (+2)) (toExp [1..3])', que da 'Just (Lista 3 (Just (Lista 4 (Just (List 5 Nothing)))))'. Con la coincidencia de patrones, puede usar 'List' directamente. ¿Debería 'Maybe List' considerarse la lista o solo' List'? – L01man

+0

Es un problema si necesita pasar su lista al código genérico que usa la instancia de clase de tipo. Si está utilizando 'List a' como una lista no vacía de' a's, está bien (y puede hacer cosas que no puede hacer con las listas estándar de Haskell). Pero si está utilizando 'Maybe (List a)' como una lista posiblemente vacía de 'a's, es un problema, porque la instancia de typeclass se conecta al' Maybe' – dave4420

+0

. Es un problema porque tenemos que duplicar el mapa, ¿derecho? – L01man

1

Cuando comencé a usar Haskell, también intenté representar cosas de tipos existentes tanto como pude sobre la base de que es bueno evitar la redundancia. Mi comprensión actual (¡objetivo móvil!) Tiende a involucrar más la idea de una red multidimensional de concesiones. No voy a dar ninguna "respuesta" aquí tanto como pegar ejemplos y preguntar "¿ves lo que quiero decir?" Espero que ayude de todos modos.

Vamos a echar un vistazo a un poco de código Darcs:

data UseCache = YesUseCache | NoUseCache 
    deriving (Eq) 

data DryRun = YesDryRun | NoDryRun 
    deriving (Eq) 

data Compression = NoCompression 
       | GzipCompression 
    deriving (Eq) 

¿Notó que estos tres tipos podrían Todos han sido Bool 's? ¿Por qué crees que los hackers de Darcs decidieron que deberían introducir este tipo de redundancia en su código? Como otro ejemplo, esta es una pieza de código que cambiamos hace unos años:

type Slot = Maybe Bool     -- OLD code 
data Slot = InFirst | InMiddle | InLast -- newer code 

¿Por qué cree que decidimos que el segundo código fue una mejora con respecto a la primera?

Finalmente, aquí hay un poco de código de algunos de mis trabajos del día. Utiliza la sintaxis newtype que augustss mencionó,

newtype Role = Role { fromRole :: Text } 
    deriving (Eq, Ord) 

newtype KmClass = KmClass { fromKmClass :: Text } 
    deriving (Eq, Ord) 

newtype Lemma = Lemma { fromLemma :: Text } 
    deriving (Eq, Ord) 

Aquí se dará cuenta de que he hecho lo curioso de tomar un perfecto tipo Text y luego envolverlo en tres cosas diferentes. Las tres cosas no tienen ninguna característica nueva en comparación con el antiguo Text. Solo están allí para ser diferentes. Para ser sincero, no estoy del todo seguro de si fue una buena idea para mí hacer esto. Provisionalmente creo que fue porque manipulo muchos trozos de texto diferentes por muchas razones, pero el tiempo dirá.

¿Puedes ver a qué me refiero?

+0

Sí :). Está más claro en el código donde no vemos el nombre del tipo y tiene más significado. Pero digamos que no quiere usar 'a && b' con' a' y 'b' de tipo' UseCache'; tienes que reescribir '(&&)', y todas las otras funciones 'Bool' que quieras usar. Algo podría ser la capacidad de escribir 'derivando (Bool)' en la definición de 'UseCache'. – L01man

+0

Estoy satisfecho con mi lista aleatoria de ejemplos y "¿ves? ¿Ves? "fue comprensible! Supongo que tratando de expresarme un poco con claridad, a veces es útil tener diferentes tipos (A) para los casos en los que ayudaría a evitar errores (B) para una mayor claridad. No siempre es cortante y seco (intercambios en todas partes).Y señaló correctamente que una de las ventajas es la pérdida de funciones convenientes escritas para ese tipo en particular. Es uno de esos casos (para mí) de todos modos donde su sentido de lo correcto/incorrecto sigue cambiando. Además, eche un vistazo al [paquete booleano] (http://hackage.haskell.org/package/Boolean) – kowey

+0

'Bool' podría ser una instancia de' Boolean'. O 'Bool' no podría existir en absoluto ... O simplemente ser usado en' Boolean'. – L01man

Cuestiones relacionadas