Atualmente, estou trabalhando em um intérprete simples para uma linguagem de programação e tenho um tipo de dados como este:
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
E eu tenho muitas funções que fazem coisas simples como:
-- 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
Mas em cada uma dessas funções, tenho que repetir a parte que chama o código recursivamente com apenas uma pequena alteração em uma parte da função. Existe alguma maneira de fazer isso de maneira mais genérica? Prefiro não ter que copiar e colar esta parte:
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
E apenas mude um único caso de cada vez, porque parece ineficiente duplicar código como este.
A única solução que eu poderia encontrar é ter uma função que chame uma função primeiro em toda a estrutura de dados e depois recursivamente no resultado da seguinte forma:
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
Mas sinto que provavelmente já deveria haver uma maneira mais simples de fazer isso. Estou esquecendo de algo?
Add :: Expr -> Expr -> Expr
vez de Add :: [Expr] -> Expr
e livre-se Sub
completamente.
recurseAfter
está ana
disfarçado. Você pode querer olhar para anamorfismos e recursion-schemes
. Dito isto, acho que sua solução final é a mais curta possível. Mudar para os recursion-schemes
anamorfismos oficiais não vai economizar muito.