Obecnie pracuję nad prostym tłumaczem dla języka programowania i mam taki typ danych:
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
I mam wiele funkcji, które wykonują proste rzeczy, takie jak:
-- 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
Ale w każdej z tych funkcji muszę powtórzyć część, która wywołuje kod rekurencyjnie, z niewielką zmianą w jednej części funkcji. Czy istnieje jakiś sposób, aby to zrobić bardziej ogólnie? Wolałbym nie kopiować i wklejać tej części:
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
I po prostu zmieniaj po jednym przypadku za każdym razem, ponieważ zduplikowanie takiego kodu wydaje się nieefektywne.
Jedyne rozwiązanie, jakie mogłem wymyślić, to mieć funkcję, która wywołuje funkcję najpierw w całej strukturze danych, a następnie rekurencyjnie w wyniku:
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
Ale wydaje mi się, że prawdopodobnie powinien istnieć już prostszy sposób, aby to zrobić. Czy coś brakuje?
Add :: Expr -> Expr -> Expr
zamiast Add :: [Expr] -> Expr
i Sub
całkowicie się pozbądź .
recurseAfter
jest ana
w przebraniu. Możesz spojrzeć na anamorfizmy i recursion-schemes
. Biorąc to pod uwagę, myślę, że twoje ostateczne rozwiązanie jest tak krótkie, jak to tylko możliwe. Przejście na oficjalne recursion-schemes
anamorfizmy niewiele zaoszczędzi.