Angenommen, ich habe in Haskell eine Reihe von Funktionen, die alle vom gleichen Parametertyp abhängen:

f :: Par -> a -> b
g :: Par -> b -> c

Da ich mehr dieser Funktionen schreibe, die immer noch von diesem Parametertyp abhängen, kann ich so etwas tun

h :: Par -> a -> c
h par = myg . myf
    where myf = f par
          myg = g par

Allerdings muss ich diese where Zeilen immer wieder schreiben. Die Frage ist: Kann dies vermieden werden?

[Bearbeiten: Ich habe versucht, ein minimales Beispiel bereitzustellen, um das Problem zu veranschaulichen, aber anscheinend ist das Beispiel zu minimal, um zu veranschaulichen, was ich will. Im eigentlichen Problem ist h natürlich nicht nur die Zusammensetzung von f und g. Hier ist also ein aktueller Code:

Es gibt Funktionen

apply :: ChamberLattice -> ChLatword -> ChLatWord
reduce :: ChamberLattice -> ChLatWord -> ChLatWord

Und ich definiere eine Funktion

chaseTurn :: ChamberLattice -> Turn -> Parity -> ChLatWord -> ChLatWord
chaseTurn cl Straight _ xs = xs
chaseTurn cl t parity xs = if ((turn parity xs) == t)
                           then case myApply xs of
                               (y1:y2:ys) -> (y1:y2:(myChaseTurn t parity ys))
                               ys -> ys
                           else myReduce xs
where myApply = apply cl
      myChaseTurn = chaseTurn cl
      myReduce = reduce cl

]

(Diese Frage ist im Wesentlichen die gleiche wie Gruppierungsfunktionen in Haskell aber dort habe ich einige unglückliche Worte benutzt, die die Leute abgelenkt haben.)

3
Stefan Witzel 18 Apr. 2018 im 13:25

4 Antworten

Beste Antwort

Du machst viel h par = f par . g par und das par Zeug fängt an, unübersichtlich zu werden.

Sie können h = f . g nicht ausführen, da das Argument par ebenfalls weitergegeben werden muss.

Sie haben also einen leistungsstarken Kompositionsoperator entwickelt, der dies für Sie erledigt:

-- (.) :: (b -> c) -> (a -> b) -> a -> c
(§) :: (par -> b -> c) -> (par -> a -> b) -> par -> a -> c
(§) f g par = f par . g par

Jetzt können Sie h = f § g ausführen. Dieser Operator wurde wahrscheinlich schon früher erfunden.

Teilweise angewendete Funktionen sind übrigens Instanzen von Monade. Dies bedeutet, dass Sie Folgendes tun können:

(§) f g par = (do { fpar <- f; gpar <- g; return (fpar . gpar) }) par

Oder nur:

(§) f g = do { fpar <- f; gpar <- g; return (fpar . gpar) }

(Hier ist fpar f, auf das ein implizites par angewendet wurde. Die Monadeninstanz macht par implizit.)

Wenn wir diesen Do-Block parametrisieren würden:

(§) f g = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.) f g

Und eta-reduzieren Sie die Parameter:

(§) = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.)

Und suchen Sie bei Google nach etwas, das wie dieser Do-Block aussieht. Sie finden liftM2:

(§) = liftM2 (.)

An diesem Punkt müssen wir ihm keinen besonderen Namen geben, da liftM2 (.) bereits ziemlich kurz ist.

3
Simon Shine 18 Apr. 2018 im 14:04

In Haskell verwenden alle Funktionen ein Eingabeargument. Manchmal ist der Rückgabewert beim Anwenden einer Funktion jedoch eine neue Funktion. In einem ersten Schritt können Sie dies deutlicher machen, indem Sie den Rückgabewert Ihrer Funktionen f und g in Klammern setzen:

f :: Par -> (a -> b)
g :: Par -> (b -> c)

Funktionen sind ebenfalls Typen, daher können wir uns willkürlich für den Alias ​​a -> b zu φ ( phi anstelle von f ) und b -> c entscheiden. bis γ ( gamma anstelle von g ). (Ja, wenn Ihnen die Buchstaben ausgehen, greifen Sie nach dem griechischen Alphabet!)

Dies bedeutet, dass Sie Ihre Funktionen als Typen anzeigen können

f :: Par -> φ
g :: Par -> γ

Dies sind beide automatisch Instanzen der sogenannten Reader Monad , die auch ein (anwendbarer) Funktor ist. Insbesondere ist (->) Par oder, wenn es hilft, Par -> eine Applicative Instanz. Dies bedeutet, dass Sie pure und <*> damit verwenden können.

Als ersten Versuch können Sie so etwas schreiben

pure (\x y -> (x, y)) <*> f <*> g

Um einfach zu verstehen, wie diese Komposition funktioniert. Dieser Ausdruck hat sozusagen den Typ Par -> (φ, γ). Dieser Lambda-Ausdruck nimmt einfach x aus dem f 'Container' und y aus dem g 'Container' und kombiniert sie in einem Tupel. Das erste Element des Tupels hat den Typ φ und das zweite Element den Typ γ.

Wenn Sie die Definitionen von φ und γ eingeben, erhalten Sie den Typ Par -> (a -> b, b -> c).

Anstelle eines Rückgabewerts als Tupel von Funktionen möchten Sie diese Funktionen zusammenstellen. Sie können dazu den Funktionskompositionsoperator . verwenden:

h = pure (\x y -> y . x) <*> f <*> g

Beachten Sie, dass die Funktionen von rechts nach links zusammengesetzt sind, sodass x (a -> b) an erster Stelle steht, gefolgt von y (b -> c).

Sie können jedoch f und g umdrehen:

h = pure (\y x -> y . x) <*> g <*> f

Dieser explizite Lambda-Ausdruck kann dann auf Folgendes reduziert werden:

h = pure (.) <*> g <*> f

Anstatt pure (.) <*> zu schreiben, können Sie den Infix-Operator <$> verwenden:

h = (.) <$> g <*> f

Diese Funktion hat den Typ Par -> a -> c.

6
Mark Seemann 17 Juni 2018 im 09:56

Sie haben den Anwendungsfall für die Monade Reader entdeckt, wenn Sie Ihre Signaturen leicht anpassen können. Wenn Sie haben

f :: a -> Par -> b
g :: b -> Par -> c

Sie können sie neu definieren als

import Control.Monad.Trans.Reader

f :: a -> Reader Par b
g :: b -> Reader Par c

Anschließend können Sie h mit dem normalen Kleisli-Kompositionsoperator definieren.

import Control.Monad

h :: a -> Reader Par c
h = f >=> g

(Auch ohne die Signaturen zu ändern, denke ich, dass Sie h = flip (flip f >=> flip g) schreiben können.)

5
chepner 18 Apr. 2018 im 12:05

Dies kann mithilfe impliziter Parameter erfolgen (bei denen es sich nicht um reines Haskell, sondern um eine ghc-Spracherweiterung handelt, siehe https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters).

Der obige Code wird einfach

f :: (?p :: Par) => a -> b

g :: (?p :: Par) => b -> c

h :: (?p :: Par) => a -> c
h = g . f
1
Stefan Witzel 16 Juni 2018 im 13:04