Ich arbeite gerade an den 20 Intermediate Haskell Exercises , was ziemlich viel Spaß macht Übung. Es beinhaltet die Implementierung verschiedener Instanzen der typeclasses Functor
und Monad
(und Funktionen, die Functor
s und Monad
s als Argumente benötigen), aber mit niedlichen Namen wie Furry
und Misty
, um zu verbergen, was wir sind tun (macht für einige interessante Code).
Ich habe versucht, etwas davon in einem Punkt-freien Stil zu tun, und ich fragte mich, ob es ein allgemeines Schema gibt, um eine punktuelle (?) Definition in eine Punkt-freie Definition umzuwandeln. Zum Beispiel ist hier die Typklasse für Misty
:
(die Funktionen unicorn
und banana
sind return
und >>=
, falls es nicht offensichtlich ist) und hier ist meine Implementierung von apple
(entspricht flip ap
):
Spätere Teile der Übungen haben Sie Versionen von liftM
, liftM2
usw. implementiert. Hier sind meine Lösungen:
Nun, banana1
(entspricht liftM
oder fmap
) konnte ich im pointfree Stil durch eine geeignete Definition von appleTurnover
implementieren. Aber mit den anderen drei Funktionen musste ich Parameter verwenden.
Meine Frage ist: Gibt es ein Rezept dafür, Definitionen wie diese in punktefreie Definitionen umzuwandeln? ? ? ?
Wie das Dienstprogramm pointfree
zeigt, ist es möglich, eine solche Konvertierung automatisch durchzuführen. Das Ergebnis wird jedoch häufiger verschleiert als verbessert. Wenn es das Ziel ist, die Lesbarkeit zu verbessern, anstatt sie zu zerstören, dann sollte das erste Ziel sein, warum ein Ausdruck eine bestimmte Struktur hat, eine geeignete Abstraktion findet und die Dinge so aufbaut.
Die einfachste Struktur verkettet einfach Dinge in einer linearen Pipeline, was eine einfache Funktionszusammensetzung ist. Das bringt uns ziemlich weit allein, aber wie du gemerkt hast, geht es nicht mit allem um.
Eine Verallgemeinerung besteht in Funktionen mit zusätzlichen Argumenten, die inkrementell aufgebaut werden können. Hier ein Beispiel: Definieren Sie onResult = (. (.))
. Wenn Sie nun onResult
n mal auf einen Anfangswert von id
anwenden, erhalten Sie eine Funktionszusammensetzung mit dem Ergebnis einer n-ary-Funktion. Also können wir comp2 = onResult (.)
definieren und dann comp2 not (&&)
schreiben, um eine NAND-Operation zu definieren.
Eine weitere Verallgemeinerung - die das Obige tatsächlich umfasst - besteht darin, Operatoren zu definieren, die eine Funktion auf eine Komponente mit einem größeren Wert anwenden. Ein Beispiel wäre hier first
und second
in Control.Arrow
, die mit 2-Tupeln arbeiten. Conal Elliotts Semantic Editor Combinators basieren auf diesem Ansatz.
Ein etwas anderer Fall liegt vor, wenn Sie eine Funktion mit mehreren Argumenten für einen Typ b
und eine Funktion a -> b
haben und diese mit a
zu einer Funktion mit mehreren Argumenten kombinieren müssen. Für den allgemeinen Fall von 2-artigen Funktionen stellt das Modul Data.Function
den on
-Kombinator bereit, mit dem Sie Ausdrücke wie compare 'on' fst
schreiben können, um 2-Tupel mit ihren ersten Elementen zu vergleichen.
Es ist ein kniffligeres Problem, wenn ein einzelnes Argument mehr als einmal verwendet wird, aber es gibt hier sinnvolle wiederkehrende Muster, die auch extrahiert werden können. Ein häufiger Fall ist hier das Anwenden mehrerer Funktionen auf ein einzelnes Argument und dann das Sammeln der Ergebnisse mit einer anderen Funktion. Dies entspricht der Applicative
-Instanz für Funktionen, mit der wir Ausdrücke wie (&&) <$> (> 3) <*> (< 9)
schreiben können, um zu überprüfen, ob eine Zahl in einen bestimmten Bereich fällt.
Wenn Sie etwas davon im tatsächlichen Code verwenden möchten, ist es wichtig zu überlegen, was der Ausdruck bedeutet und wie er sich in der Struktur widerspiegelt. Wenn Sie das tun und es dann mit sinnvollen Kombinatoren in einen Point-Free-Stil umwandeln, machen Sie die Absicht des Codes oft klarer als im Gegensatz zur typischen Ausgabe von pointfree
.
Ja! Einer der Tricks besteht darin, Ihre Punkte in Präfix-Notation anstatt Infix zu schreiben. Dann sollten Sie in der Lage sein, neue Dinge zu finden, die wie Funktionszusammensetzung aussehen. Hier ist ein Beispiel:
%Vor%Der Quellcode für das Dienstprogramm pointfree enthält mehr, aber dieser behandelt viele Fälle.
%Vor%Ich benutze das folgende Überschreibsystem:
%Vor%Es ist unvollständig (lesen Sie, warum in Büchern über kombinatorische Logik), aber es ist genug:
Hier ist Banana2:
%Vor%Rewrite als Lambda:
%Vor%Schreiben Sie (.) im Präfixstil:
%Vor%Beachten Sie, dass
%Vor% So kann Regel 3 angewendet werden. f
ist (.) appleTurnover
und g
ist banana
:
Es gibt ein pointfree
-Paket, das eine Haskell-Funktionsdefinition verwendet und versucht, es in einem pointfree
-Stil neu zu schreiben. Ich würde vorschlagen, damit zu experimentieren, um neue Ideen zu bekommen. Weitere Informationen finden Sie auf dieser Seite . Das Paket ist hier verfügbar.
Da pointfree style ein Kombinator-Stil ist, wenden Sie einfach bekannte Kombinator-Definitionen an und lesen Sie sie rückwärts, um die Ersetzung vorzunehmen:
%Vor% Zu Zeiten liftMx
, liftAx
, sequence
, sequenceA
können Dinge vereinfachen. Ich würde auch foldr
, unfoldr
, iterate
, until
usw. als grundlegende Kombinatoren betrachten.
Oft hilft auch die Verwendung von Operatorabschnitten:
%Vor%Einige Muster können vertraut werden und so direkt verwendet werden:
%Vor%usw.
Tags und Links haskell higher-order-functions combinators pointfree