В настоящее время я работаю над простым интерпретатором языка программирования, и у меня есть такой тип данных:
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
И у меня есть много функций, которые делают простые вещи, такие как:
-- 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
Но в каждой из этих функций я должен повторить ту часть, которая вызывает код рекурсивно, с небольшим изменением одной части функции. Есть ли какой-нибудь способ сделать это более обобщенно? Я бы предпочел не копировать и вставлять эту часть:
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
И просто меняйте один случай каждый раз, потому что кажется неэффективным дублировать код, подобный этому.
Единственное решение, которое я мог бы придумать, - это иметь функцию, которая вызывает функцию сначала для всей структуры данных, а затем рекурсивно для результата, подобного этому:
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
Но я чувствую, что, вероятно, должен быть более простой способ сделать это уже. Я что-то пропустил?
Add :: Expr -> Expr -> Expr
вместо Add :: [Expr] -> Expr
, и избавиться от Sub
всего.
recurseAfter
IS ana
в маскировке. Возможно, вы захотите посмотреть на анаморфизмы и recursion-schemes
. При этом, я думаю, ваше окончательное решение настолько короткое, насколько это возможно. Переход на официальные recursion-schemes
анаморфизмы не спасет много.