Ich arbeite derzeit an einem einfachen Interpreter für eine Programmiersprache und habe einen Datentyp wie diesen:
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
Und ich habe viele Funktionen, die einfache Dinge tun wie:
-- Substitute a value for a variable
substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = go
where
go (Variable x)
| x == name = Number newValue
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
-- Replace subtraction with a constant with addition by a negative number
replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = go
where
go (Sub x (Number y)) =
Add [go x, Number (-y)]
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
Aber in jeder dieser Funktionen muss ich den Teil, der den Code rekursiv aufruft, mit nur einer kleinen Änderung an einem Teil der Funktion wiederholen. Gibt es eine Möglichkeit, dies allgemeiner zu tun? Ich möchte diesen Teil lieber nicht kopieren und einfügen:
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
Und ändern Sie jedes Mal einen einzelnen Fall, da es ineffizient erscheint, Code wie diesen zu duplizieren.
Die einzige Lösung, die ich finden könnte, besteht darin, eine Funktion zu haben, die zuerst eine Funktion für die gesamte Datenstruktur und dann rekursiv für das Ergebnis wie folgt aufruft:
recurseAfter :: (Expr -> Expr) -> Expr -> Expr
recurseAfter f x =
case f x of
Add xs ->
Add $ map (recurseAfter f) xs
Sub x y ->
Sub (recurseAfter f x) (recurseAfter f y)
other -> other
substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue =
recurseAfter $ \case
Variable x
| x == name -> Number newValue
other -> other
replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd =
recurseAfter $ \case
Sub x (Number y) ->
Add [x, Number (-y)]
other -> other
Aber ich denke, es sollte wahrscheinlich schon einen einfacheren Weg geben, dies zu tun. Vermisse ich etwas
Add :: Expr -> Expr -> Expr
statt Add :: [Expr] -> Expr
und Sub
ganz loswerden .
recurseAfter
heißt ana
in der Verkleidung. Vielleicht möchten Sie sich Anamorphismen und ansehen recursion-schemes
. Davon abgesehen denke ich, dass Ihre endgültige Lösung so kurz wie möglich ist. Der Wechsel zu den offiziellen recursion-schemes
Anamorphismen spart nicht viel.