2012-07-06 4 views
5

Non è offen vedere Maybe List tranne che per la gestione degli errori, ad esempio, perché le liste sono un po 'se stessi Maybe: hanno il loro "Nothing ': [] e la propria' Just": (:). Ho scritto un tipo di lista usando Maybe e funzioni per convertire liste standard e "sperimentali". toStd . toExp == id.Liste definite come Forse in Haskell? Perchè 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) 

Cosa ne pensi, come un tentativo di ridurre la ripetizione, per generalizzare?

Alberi troppo potrebbe essere definito l'utilizzo di questi elenchi:

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

non ho ancora testato questo ultimo pezzo di codice, però.

+3

'Sup dawg ho sentito che ti piace Monads, così ho messo una Monade nella tua Monad ...! hai visto 'Maybe String' è in realtà di tipo' Maybe [Char] ', ma penso che stai reinventando i trasformatori Monad (vedi http://en.wikibooks.org/wiki/Haskell/Monad_transformers) ma io sono Non sono sicuro di come io stesso non abbia molta familiarità con le monadi in questo momento. – epsilonhalbe

+0

Oh, ho visto molti 'Maybe String' (http://www.haskell.org/hoogle/?hoogle=Maybe+%5BChar%5D)[here] ma hanno un significato diverso. Ho appena sottolineato che '[]' è una specie di 'Nothing' e cose del genere, quindi ho pensato di usare' Nothing' per rimuovere la "ridefinizione". – L01man

+0

Non è una ridefinizione. Elenca e forse ha semantica diversa se usata come monade. Anche sfocare la linea e gettare via lo zucchero di sintassi (soprattutto i modelli di lista) è assolutamente stupido. –

risposta

9

Tutti ADT sono isomorfi (quasi - vedi end) per una qualche combinazione di (,), Either, (), (->), Void e Mu dove

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

e Mu calcola il punto fisso di un funtore

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

quindi ad esempio

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

è uguale

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

che si è isomorfo a

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

dal

data Maybe a = Nothing | Just a 

è isomorfo a

newtype Maybe a = Maybe (Either() a) 

avete

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

che può essere inline nei mu per

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

e la tua definizione

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

è solo lo svolgersi del Mu e l'eliminazione del esterna Forse (corrispondente a liste non vuote)

e il gioco è fatto ...

un paio di cose

  1. Uso ADT personalizzati aumenta la chiarezza e il tipo di sicurezza

  2. Questa universalità è utile: vedo GHC.Generic


Va bene, detto quasi isomorfo. Non è esattamente, e cioè

hmm = List (Just undefined) 

non ha valore equivalente nella [a] = [] | (a:[a]) definizione di liste. Questo perché i tipi di dati Haskell sono coinduttivi ed è stato a point of criticism of the lazy evaluation model. È possibile aggirare questi problemi solo con le somme e prodotti severi (e chiamare da funzioni di valore), e l'aggiunta di un costruttore dati speciali "pigro"

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 

e poi tutti gli isomorfismi possono essere codificati con fedeltà.

+0

Come si esprimono tipi di dati non regolari, come ad esempio 'Albero dati a = Zero a | Succ (Tree (Node a)) 'dove' data Node a = Nodo2 a a | Nodo3 a a' (cioè albero 2-3)? – Vitus

+0

@Vitus Hai ragione. La ricorsione polimorfica è più complicata. –

+0

@Vitus. Ho dovuto testare questo funziona davvero: 'newtype Mu2 (f :: (* -> *) -> (* -> *)) a = Mu2 (f (Mu2 f) a)' quindi puoi definire 'dati TreeF fa = ZeroF a | SuccF (f (Nodo a)) 'e' newtype Tree 'a = Tree' (Mu2 TreeF a) '. Quindi hai bisogno di un'operazione "Mu" più alta, ma questo non è essenziale (e non penso che tu abbia bisogno di qualcosa di più alto di questo, ma potrei sbagliarmi). –

7

È possibile definire elenchi in termini di Maybe, ma non in questo modo. Il tuo tipo non può essere vuoto. O intendevi sostituire Maybe (List a)[a]. Questo sembra male poiché non distingue l'elenco e forse i tipi.

Questo potrebbe funzionare

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

Questo ha qualche problema. Il primo utilizzo di questo sarebbe più verboso delle solite liste, e in secondo luogo, il dominio non è isomorfo alle liste dato che abbiamo una coppia (che può essere indefinita, aggiungendo un livello extra nel dominio).

+0

La lista vuota è 'Nothing'! E il tipo è ancora 'Forse (Elenco a)'. 'Nothing' funziona come' [] '. Ho separato il caso 'Nothing' di proposito, perché è già un costruttore di dati di' Maybe'. Hai ragione; non possiamo avere una lista vuota di tipo 'List a', e questa nuova lista non è identica a quella standard. – L01man

+1

Forse non mi piace e lista di avere lo stesso tipo. – augustss

+0

Hanno una semantica diversa? – L01man

9

È possibile definire elenchi in vari modi in Haskell. Ad esempio, come funzioni:

{-# 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 

Il trucco per questa implementazione è che gli elenchi sono stati rappresentati come funzioni che eseguono una piega sugli elementi della lista:

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

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

In ogni caso, ciò che realmente è importante il contratto (o leggi) che seguono il tipo e le sue operazioni e la prestazione di implementazione. In sostanza, qualsiasi implementazione che soddisfi il contratto ti fornirà programmi corretti e implementazioni più veloci ti daranno programmi più veloci.

Qual è il contratto delle liste?Beh, io non ho intenzione di esprimere in dettaglio completo, ma le liste obbedire a dichiarazioni come questi:

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

EDIT: E per legare questo per augustss' risposta:

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

Se si tratta di una lista, dovrebbe essere un esempio di Functor, giusto?

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 

Ecco un problema: si può fare List un'istanza di Functor, ma il vostro forse elenco non è: anche se Maybe non era già un caso di Functor a sé stante, non è possibile effettuare direttamente una costruzione come Maybe . List in un'istanza di qualsiasi cosa (avresti bisogno di un tipo di wrapper).

Analogamente per altri tipi di caratteri.


Detto questo, con la formulazione si può fare questo, che non si può fare con standard di Haskell liste:

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

Un Forse elenco ancora non sarebbe comonadic però.

+0

È un problema? Possiamo ancora fare 'fmap (fmap (+2)) (toExp [1..3])', che fornisce 'Just (Lista 3 (Just (Lista 4 (Just (Lista 5 Nothing)))))'. Con la corrispondenza dei pattern, puoi usare direttamente la 'Lista'. Se 'Maybe List' è considerato la lista o solo' List'? – L01man

+0

È un problema se è necessario passare l'elenco al codice generico che utilizza l'istanza del typeclass. Se stai usando 'List a' come una lista non vuota di' a's va bene (e puoi fare cose che non puoi fare con gli elenchi standard di Haskell). Ma se stai usando 'Maybe (List a)' come una lista di 'a's possibilmente vuota è un problema, perché l'istanza di typeclass si attacca al' Maybe' – dave4420

+0

È un problema perché dobbiamo double-fmap, destra? – L01man

1

Quando ho iniziato a utilizzare Haskell, ho provato a rappresentare le cose nei tipi esistenti il ​​più possibile sulla base del fatto che è opportuno evitare la ridondanza. La mia attuale comprensione (spostare l'obiettivo!) Tende a coinvolgere maggiormente l'idea di una rete multidimensionale di compromessi. Non darò alcuna "risposta" qui tanto quanto incollare esempi e chiedere "capisci cosa intendo?" Spero che aiuti comunque.

Diamo un'occhiata a un po 'di codice di Darcs:

data UseCache = YesUseCache | NoUseCache 
    deriving (Eq) 

data DryRun = YesDryRun | NoDryRun 
    deriving (Eq) 

data Compression = NoCompression 
       | GzipCompression 
    deriving (Eq) 

Avete notato che questi tre tipi potrebbero tutti sono stati Bool 's? Perché pensi che gli hacker di Darcs abbiano deciso di introdurre questo tipo di ridondanza nel loro codice? Come altro esempio, ecco un pezzo di codice abbiamo cambiato qualche anno fa:

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

Perché pensi abbiamo deciso che il secondo codice è stato un miglioramento rispetto al primo?

Infine, ecco un po 'di codice da alcuni dei miei lavori di giornata. Esso utilizza la sintassi che newtype augustss menzionato,

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

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

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

Qui si noterà che ho fatto la cosa curiosa di prendere un buon perfettamente Text tipo e poi avvolgendolo in tre cose diverse. Le tre cose non hanno nuove funzionalità rispetto al semplice vecchio Text. Sono lì solo per essere diversi. Ad essere onesti, non sono del tutto sicuro se fosse una buona idea per me farlo. Ritengo provvisoriamente che sia stato perché ho manipolato un sacco di bit e parti di testo diversi per molte ragioni, ma il tempo lo dirà.

Riesci a vedere cosa sto cercando di ottenere?

+0

Sì :). È più chiaro nel codice in cui non vediamo il nome del tipo e ha più significato. Ma diciamo che non dovresti usare 'a && b' con' a' e 'b' di tipo' UseCache'; devi riscrivere '(&&)' e tutte le altre funzioni 'Bool' che vuoi usare. Qualcosa potrebbe essere la capacità di scrivere 'derivando (Bool)' nella definizione di 'UseCache'. – L01man

+0

Sono contento del mio casuale grab-bag di esempi e "vedi? vedere? "era comprensibile! Immagino che provi ad esprimermi un po 'chiaramente, a volte è utile avere diversi tipi (A) per i casi in cui contribuirebbe a evitare errori (B) per maggiore chiarezza. Non è sempre tagliato e asciutto (compromessi ovunque).E hai giustamente sottolineato che un compromesso è la perdita di comode funzioni scritte per quel particolare tipo. È uno di quei casi (per me) comunque in cui il senso di ciò che è giusto/sbagliato continua a cambiare. Inoltre, date un'occhiata al [pacchetto booleano] (http://hackage.haskell.org/package/Boolean) – kowey

+0

'Bool' potrebbe essere un'istanza di' Boolean'. Oppure "Bool" non potrebbe esistere affatto ... O semplicemente essere usato in 'Boolean'. – L01man