2012-09-27 10 views
5

Ho tre funzioni (getRow, getColumn, getBlock) con due argomenti (xey) che producono ciascuno un elenco dello stesso tipo. Voglio scrivere una quarta funzione che concatena le loro uscite:Come mappare un elenco di funzioni su più argomenti in Haskell?

outputList :: Int -> Int -> [Maybe Int] 
outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock] 

La funzione funziona, ma c'è un modo per riscrivere la doppia mappa (con tre '$' s) per una singola mappa?

risposta

22
import Data.Monoid 

outputList :: Int -> Int -> [Maybe Int] 
outputList = mconcat [getRow, getColumn, getBlock] 

Ti meriti una spiegazione.

In primo luogo, noterò esplicitamente che tutte queste funzioni hanno lo stesso tipo.

outputList, getRow, getColumn, getBlock :: Int -> Int -> [Maybe Int] 

Ora iniziamo con la definizione originale.

outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock] 

Queste funzioni comportano una [Maybe Int], e un elenco di tutto ciò è un monoide. La combinazione di elenchi in modo mono-analogico equivale a concatenare gli elenchi, pertanto è possibile sostituire concat con mconcat.

Un'altra cosa che è un monoide è una funzione, se il suo risultato è un monoide. Cioè, se b è un monoid, allora a -> b è un monoid pure. La combinazione monocromatica delle funzioni equivale a chiamare le funzioni con lo stesso parametro, quindi combinare monoidalmente i risultati.

quindi possiamo semplificare al

outputList x = mconcat $ map ($ x) [getRow,getColumn,getBlock] 

E poi di nuovo a

outputList = mconcat [getRow,getColumn,getBlock] 

Abbiamo finito!


Il Typeclassopedia has a section about monoids, anche se in questo caso io non sono sicuro che aggiunge che molto al di là del documentation for Data.Monoid.

4

Come primo passo osserviamo che la definizione

outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock] 

può essere riscritta utilizzando l'operatore composizione funzione (.) invece dell'operatore dell'applicazione funzione ($) come segue.

outputList x y = (concat . map ($ y) . map ($ x)) [getRow,getColumn,getBlock] 

Successivo notiamo che map è un altro nome per fmap nelle liste e soddisfa le fmap leggi, quindi, in particolare, abbiamo map (f . g) == map f . map g. Applichiamo questa legge per definire una versione utilizzando una singola applicazione di map.

outputList x y = (concat . map (($ y) . ($ x))) [getRow,getColumn,getBlock] 

Come passo finale che può sostituire la composizione di concat e map da concatMap.

outputList x y = concatMap (($ y) . ($ x)) [getRow,getColumn,getBlock] 

Infine, a mio parere, anche se i programmatori Haskell tendono ad usare molti operatori di fantasia, non è una vergogna per definire la funzione da

outputList x y = concatMap (\f -> f x y) [getRow,getColumn,getBlock] 

come si esprime chiaramente, ciò che la funzione fa. Tuttavia, l'uso di astrazioni di classe del tipo (come dimostrato nell'altra risposta) può essere una buona cosa in quanto si potrebbe osservare che il problema ha una certa struttura astratta e acquisire nuove conoscenze.

2

Vorrei andare con la risposta di @ dave4420, poiché è più concisa ed esprime esattamente cosa intendi. Tuttavia, se non si vuole fare affidamento su Data.Monoid allora si potrebbe riscrivere come segue

codice originale:

outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock] 

fusibile le due mappe:

outputList x y = concat . map (($y) . ($x)) [getRow,getColumn,getBlock] 

Sostituire concat . map con concatMap:

outputList x y = concatMap (($y) . ($x)) [getRow,getColumn,getBlock] 

E il gioco è fatto.

Modifica: aaaa e questo è esattamente la stessa risposta di @Jan Christiansen. Oh bene!

+0

A proposito, ci sono delle statiche quanto tempo ci vuole prima che una domanda riguardante Haskell abbia una risposta? Ho l'impressione che al 90 percento di tutte le domande di Haskell venga data una risposta quasi immediata. Anche se questo non dice nulla sulla qualità delle risposte, a mio parere hanno anche una qualità piuttosto elevata. –

+2

@JanChristiansen: Puoi rispondere usando lo stack exchange data explorer se vuoi. Ho fatto un'approssimazione molto approssimativa (filtro i valori anomali ovvi, ignorare le risposte auto-rispondenti, ecc.), E il tempo tipico (cioè mediano) era di circa 20 minuti fino a quando non è stata pubblicata la prima risposta. –

Problemi correlati