Поздравляем, вы только что заново открыли анаморфизмы!
Вот ваш код, перефразированный так, чтобы он работал с пакетом recursion-schemes
. Увы, он не короче, потому что нам нужен шаблон для работы оборудования. (Там может быть какой-то автоматический способ избежать шаблон, например, с помощью дженериков. Я просто не знаю.)
Ниже ваш recurseAfter
заменен стандартным ana
.
Сначала мы определяем ваш рекурсивный тип, а также функтор, для которого он является фиксированной точкой.
{-# LANGUAGE DeriveFunctor, TypeFamilies, LambdaCase #-}
{-# OPTIONS -Wall #-}
module AnaExpr where
import Data.Functor.Foldable
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
deriving (Show)
data ExprF a
= VariableF String
| NumberF Int
| AddF [a]
| SubF a a
deriving (Functor)
Затем мы соединяем два с несколькими экземплярами, чтобы мы могли развернуть Expr
в изоморфный ExprF Expr
и сложите его обратно.
type instance Base Expr = ExprF
instance Recursive Expr where
project (Variable s) = VariableF s
project (Number i) = NumberF i
project (Add es) = AddF es
project (Sub e1 e2) = SubF e1 e2
instance Corecursive Expr where
embed (VariableF s) = Variable s
embed (NumberF i) = Number i
embed (AddF es) = Add es
embed (SubF e1 e2) = Sub e1 e2
Наконец, мы адаптируем ваш исходный код и добавим пару тестов.
substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = ana $ \case
Variable x | x == name -> NumberF newValue
other -> project other
testSub :: Expr
testSub = substituteName "x" 42 (Add [Add [Variable "x"], Number 0])
replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = ana $ \case
Sub x (Number y) -> AddF [x, Number (-y)]
other -> project other
testReplace :: Expr
testReplace = replaceSubWithAdd
(Add [Sub (Add [Variable "x", Sub (Variable "y") (Number 34)]) (Number 10), Number 4])
Альтернативой может быть определение только ExprF a
, а затем получить type Expr = Fix ExprF
. Это экономит некоторые из вышеприведенных шаблонов (например, два экземпляра) за счет необходимости использовать Fix (VariableF ...)
вместо Variable ...
, а также аналогично другим конструкторам.
Можно было бы еще больше облегчитьчто с использованием шаблонных синонимов (хотя и ценой чуть большего количества шаблонов).
Обновление: я наконец-то нашел автоматизированный инструмент, использующий шаблон Haskell. Это делает весь код достаточно коротким. Обратите внимание, что функтор ExprF
и два вышеупомянутых экземпляра все еще существуют под капотом, и мы все еще должны их использовать. Мы избавляем вас от необходимости определять их вручную, но это экономит много усилий.
{-# LANGUAGE DeriveFunctor, DeriveTraversable, TypeFamilies, LambdaCase, TemplateHaskell #-}
{-# OPTIONS -Wall #-}
module AnaExpr where
import Data.Functor.Foldable
import Data.Functor.Foldable.TH
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
deriving (Show)
makeBaseFunctor ''Expr
substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = ana $ \case
Variable x | x == name -> NumberF newValue
other -> project other
testSub :: Expr
testSub = substituteName "x" 42 (Add [Add [Variable "x"], Number 0])
replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = ana $ \case
Sub x (Number y) -> AddF [x, Number (-y)]
other -> project other
testReplace :: Expr
testReplace = replaceSubWithAdd
(Add [Sub (Add [Variable "x", Sub (Variable "y") (Number 34)]) (Number 10), Number 4])