2015-09-09 13 views
8

Sono nuovo di Haskell e sono sconcertante su come esprimere al meglio alcune operazioni nel modo più idiomatico e chiaro. Attualmente (ce ne saranno altri a venire) Sono sconcertante su <*> (non sono nemmeno sicuro di come chiamarlo).Haskell Idioma applicativo?

Ad esempio, se ho, diciamo

f = (^2) 
g = (+10) 

come funzioni rappresentative (in pratica sono più complesse, ma la cosa fondamentale è che sono diversi e distinti), quindi

concatMap ($ [1,2,3,4,10]) [(f <$>), (g <$>) . tail . reverse] 

e

concat $ [(f <$>), (g <$>) . tail . reverse] <*> [[1,2,3,4,10]] 

ottenere la stessa cosa.

È uno di questi Haskell più idiomatico, implica qualcosa di un lettore esperto di Haskell che l'altro non fa. Forse ci sono modi (migliori) aggiuntivi per esprimere esattamente la stessa cosa. Ci sono differenze concettuali tra i due approcci che un novizio di Haskeller come me può mancare?

+0

è il vostro input sempre e solo andando essere una lista? Se è così, il primo è chiaramente migliore. Per input intendo l'elenco di numeri. – itsbruce

+0

@itsbruce: buon punto. Sì, sempre e solo una lista. – orome

+0

La combinazione dell'istanza 'Applicativo' o' Functor' per gli elenchi con 'concat' è molto sospetta. 'Functor + return + join = Monad', e per le liste,' concat = join'. Quindi se stai mappando e poi concatenando, 'concatMap' o' = << 'potrebbero essere più concisi. – dfeuer

risposta

10

Entrambe le funzioni (f <$>) e (g <$>).tail.reverse restituire un tipo monoide (lista in questo caso) in modo da poter utilizzare mconcat per convertirli in una singola funzione. Quindi è possibile applicare questa funzione direttamente alla lista di input, invece di avvolgendolo in un altro elenco e usando concatMap:

mconcat [(f <$>), (g <$>).tail.reverse] $ [1,2,3,4,10] 

Per espandere su questo, una funzione a -> b è un'istanza di Monoid se b è un monoide. La implementation di mappend per tali funzioni è:

mappend f g x = f x `mappend` g x 

o equivalentemente

mappend f g = \x -> (f x) `mappend` (g x) 

così dato due funzioni f e g che restituiscono un tipo monoid b, f mappend g restituisce una funzione che applica il suo argomento f e g e combina i risultati utilizzando l'istanza Monoid di b.

mconcat ha tipo Monoid a => [a] -> a e combina tutti gli elementi dell'elenco di input utilizzando mappend.

liste sono monoidi dove mappend == (++) così

mconcat [(f <$>), (g <$>).tail.reverse] 

restituisce una funzione come

\x -> (fmap f x) ++ (((fmap g) . tail . reverse) x) 
+1

Questo è abbastanza interessante. Mi ci è voluto un po 'di tempo per capire, ma [la fonte per l'istanza di funzione di 'Monoid'] (http://hackage.haskell.org/package/base-4.8.1.0/docs/src/GHC.Base.html # line-255) cancella tutto.'(f <$>)' restituisce '([f] <*>)' che ha tipo '[a] -> [a]', il suo tipo restituito '[a]' è un'istanza di 'Monoid', rendendo la funzione un istanza di 'Monoid' stesso. –

+0

@SamvanHerwaarden: vedo che '(f <$>) :: [a] -> [a]' ma sto avendo problemi a visualizzare che tipo di funzione 'mconcat [(f <$>), (g <$>) .tail.reverse] 'è. Vedo che deve essere '(Num b) => [b] -> [b]', ma non è in grado di visualizzare a cosa assomiglia. – orome

+1

Ho detto '[a] -> [a]' ma hai ragione riguardo al vincolo 'Num', che vale anche qui (ero sciatto e l'ho omesso). 'mconcat' interspassa una lista con' mappend' quindi otteniamo 'mconcat [(f <$>), (g <$>). coda . reverse] = \ x -> (([f] <$>) x) \ 'mappend \' (((g <$>). tail. reverse) x) 'così tutte le funzioni nella lista sono fornite con l'argomento fornito a' mconcat func', gli output di queste funzioni sono quindi concatenati con 'mappend'. Nel nostro caso l'output delle funzioni elenca quindi 'mappend = (++)', per gli altri monoidi verrebbe utilizzato il corrispondente 'mappend'. –

9

Personalmente per il tuo esempio vorrei scrivere

f = (^2) 
g = (+10) 

let xs = [1,2,3,4,10] 
in (map f xs) ++ (map g . tail $ reverse xs) 

In un "mood" molto applicativo, vorrei sostituire la parte dopo in da

((++) <$> map f <*> map g . tail . reverse) xs 

che io in realtà non credo sia più leggibile in questo caso. Se non capisci direttamente cosa significa, dedica del tempo alla comprensione dell'istanza Applicative di ((->) a) (Reader).

Penso che la scelta dipenda davvero da cosa si sta tentando di fare, cioè cosa si intende per output. Nel tuo esempio l'attività è molto astratta (in pratica mostra solo cosa può fare Applicative), quindi non è ovvio quale versione utilizzare.

La Applicative istanza di [] riferisce intuitivamente a combinazioni, quindi vorrei usarlo in una situazione come questa:

-- I want all pair combinations of 1 to 5 
(,) <$> [1..5] <*> [1..5] 

Se desiderate avere molte funzioni, e si vorrebbe provare tutte le combinazioni di queste funzioni con un certo numero di argomenti, userei effettivamente l'istanza [] di Applicative. Ma se quello che cerchi è una concatenazione di diverse trasformazioni, lo scriverei come tale (cosa che ho fatto, sopra).

Solo i miei 2 centesimi come Haskeller di media esperienza.

1

A volte ho problemi con il problema simile. Hai un singolo elemento ma più funzioni.

Di solito abbiamo elementi multipli, e singola funzione: così facciamo:

map f xs 

Ma non è questo il problema in Haskell.Il doppio è facile:

map ($ x) fs 

Il fatto, che il vostro x è in realtà una lista, e si desidera concat dopo la map, in modo da fare

concatMap ($ xs) fs 

non riesco davvero a capire ciò che accade direttamente nella seconda equazione, anche io posso ragionare che fa lo stesso del primo usando le leggi applicative.