Зараз я працюю над простим перекладачем мови програмування, і у мене є такий тип даних:
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
анаморфізми не дуже заощадить.