2012-09-27 8 views
5

Ich habe drei Funktionen (getRow, getColumn, getBlock) mit zwei Argumente (x und y), die jeweils eine Liste des gleichen Typs erzeugen. Ich möchte eine vierte Funktion schreiben, die verkettet ihre Ausgänge:Wie man eine Liste von Funktionen über mehrere Argumente in Haskell abbildet?

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

Die Funktion funktioniert, aber ist es eine Möglichkeit, die doppelte Karte neu zu schreiben (mit drei ‚$‘ s) auf eine einzige Karte?

Antwort

22
import Data.Monoid 

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

verdienen Sie eine Erklärung.

Zuerst werde ich ausdrücklich darauf hinweisen, dass alle diese Funktionen den gleichen Typ haben.

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

Jetzt beginnen wir mit Ihrer ursprünglichen Definition.

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

führen diese Funktionen in einem [Maybe Int] und eine Liste von allem ist ein Monoid. Das monoallinguale Kombinieren von Listen ist das gleiche wie das Verketten der Listen, so dass wir concat durch mconcat ersetzen können.

Eine andere Sache, die ein Monoid ist, ist eine Funktion, wenn das Ergebnis ein Monoid ist. Das heißt, wenn b ein Monoid ist, dann ist a -> b auch ein Monoid. Funktionen monoidal zu kombinieren ist das Gleiche wie das Aufrufen der Funktionen mit dem gleichen Parameter, dann die Ergebnisse monoidal zu kombinieren.

So können wir auf

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

Und dann wieder zu

outputList = mconcat [getRow,getColumn,getBlock] 

Wir sind fertig vereinfachen!


Die Typeclassopedia has a section about monoids, obwohl in diesem Fall, dass ich nicht sicher bin, fügt hinzu, dass es viel über die documentation for Data.Monoid.

4

Als erster Schritt beobachten wir, dass Ihre Definition

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

neu geschrieben werden kann, um die Funktion Kompositionsoperator (.) anstelle der Funktionsanwendung Operator ($) wie folgt.

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

Als nächstes werden wir feststellen, dass map ein anderer Name für fmap auf Listen und erfüllt die fmap Gesetze daher insbesondere, haben wir map (f . g) == map f . map g. Wir wenden dieses Gesetz an, um eine Version zu definieren, die eine einzige Anwendung von map verwendet.

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

Als letzten Schritt können wir die Zusammensetzung von concat und map durch concatMap ersetzen.

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

Schließlich, meiner Meinung nach, obwohl Haskell Programmierer neigen viele Phantasien Betreiber zu verwenden, ist es nicht eine Schande, die Funktion von

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

zu definieren, wie es klar zum Ausdruck bringt, was die Funktion tut. Die Verwendung von Klassenklassen-Abstraktionen (wie in der anderen Antwort gezeigt) kann jedoch eine gute Sache sein, da Sie beobachten können, dass Ihr Problem eine gewisse abstrakte Struktur hat und neue Einsichten gewinnt.

2

Ich würde mit @ dave4420's Antwort gehen, da es am prägnantesten ist und genau das ausdrückt, was Sie meinen. wenn Sie nicht verlassen wollte jedoch auf Data.Monoid dann könnte man wie folgt umschreiben

Originalcode:

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

Sicherung der beiden Karten:

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

ersetzen concat . map mit concatMap:

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

Und du bist fertig.

Edit: uuuuund das ist genau das gleiche wie @Jan Christiansen Antwort. Naja!

+0

Übrigens, gibt es Statik, wie lange dauert es, bis eine Frage bezüglich Haskell beantwortet wird? Ich habe den Eindruck, dass 90 Prozent aller Haskell-Fragen fast sofort beantwortet werden. Das sagt zwar nichts über die Qualität der Antworten aus, aber sie haben meiner Meinung nach auch eine recht hohe Qualität. –

+2

@ JanChristiansen: Sie könnten das mit dem Stack Exchange Data Explorer beantworten, wenn Sie möchten. Ich habe eine sehr grobe Annäherung gemacht (offensichtliche Ausreißer filtern, Selbstantworten ignorieren, & c.), Und die typische (d. H. Median) Zeit war ungefähr 20 Minuten, bis die erste Antwort gepostet wurde. –