Attualmente sto lavorando a un semplice interprete per un linguaggio di programmazione e ho un tipo di dati come questo:
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
E ho molte funzioni che fanno cose semplici come:
-- 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
Ma in ciascuna di queste funzioni, devo ripetere la parte che chiama il codice in modo ricorsivo con solo una piccola modifica a una parte della funzione. Esiste un modo esistente per farlo in modo più generico? Preferirei non dover copiare e incollare questa parte:
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
E basta cambiare un singolo caso ogni volta perché sembra inefficiente duplicare il codice in questo modo.
L'unica soluzione che ho potuto trovare è quella di avere una funzione che chiama prima una funzione sull'intera struttura dei dati e poi ricorsivamente sul risultato in questo modo:
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
Ma credo che probabilmente ci dovrebbe essere già un modo più semplice per farlo. Mi sto perdendo qualcosa?
Add :: Expr -> Expr -> Expr
invece di Add :: [Expr] -> Expr
e sbarazzati del Sub
tutto.
recurseAfter
sia ana
sotto mentite spoglie. Potresti voler guardare anamorfismi e recursion-schemes
. Detto questo, penso che la tua soluzione finale sia la più breve possibile. Passare agli recursion-schemes
anamorfismi ufficiali non farà risparmiare molto.