如何使用SYB(或其他一些Haskell泛型包)在Reader
monad中编写转换,使用local
来修改子计算的环境? GenericM
和everywhereM
(带a -> m a
)的类型似乎不支持使用local
(类型m a -> m a
)来包装子计算。如果可能的话,我想要一个使用“标准”/“现成”转换的解决方案。
(神秘的)递归数据类型:
{-# LANGUAGE DeriveDataTypeable , Rank2Types , ViewPatterns #-}
import Data.Generics
import Control.Applicative
import Control.Monad.Reader
import Control.Arrow
data Exp = Var Int | Exp :@ Exp | Lam (Binder Exp)
deriving (Eq , Show , Data , Typeable)
newtype Binder a = Binder a
deriving (Eq , Show , Data , Typeable)
一个递归函数,它使所有嵌入的Int
增加值
大于包裹它们的Binder
的数量:
-- Increment all free variables:
-- If G |- e:B then G,A |- weaken e:B.
weaken1 :: Exp -> Exp
weaken1 = w 0
where
w :: Int -> Exp -> Exp
-- Base case: use the environment ('i'):
w i (Var j) = wVar i j
-- Boilerplate recursive case:
w i (e1 :@ e2) = w i e1 :@ w i e2
-- Interesting recursive case: modify the environment:
w i (Lam (Binder e)) = Lam (Binder (w (succ i) e))
wVar :: Int -> Int -> Exp
wVar i j | i <= j = Var (succ j)
| otherwise = Var j
目标是将i
参数放到weaken1
Reader
环境中,并使用SYB自动处理(:@)
的样板递归情况。
使用weaken1
环境重写Reader
,但不使用SYB:
weaken2 :: Exp -> Exp
weaken2 e = runReader (w e) 0
where
w :: Exp -> Reader Int Exp
w (Var j) = do
i <- ask
return $ wVar i j
w (e1 :@ e2) = (:@) <$> w e1 <*> w e2
w (Lam (Binder e)) = Lam . Binder <$> local succ (w e)
示例的要点:
(:@)
案例是典型的样板递归:everywhereM
自动在这里工作。Var
案例使用环境,但不会对其进行修改:everywhereM
适用于此mkM
,将Exp -> Reader Int Exp
应用于Var
特定的Lam
情况下。everywhereM
案例在递归之前修改环境:Binder
不在这里工作(据我所知)。 local
类型告诉我们需要使用mkM
的位置,以便我们可能希望将Binder Exp -> Reader Int (Binder Exp)
应用于{{1}}具体案例,但我无法弄清楚如何答案 0 :(得分:4)
通过创建新的SYB遍历everywhereMM
:
newtype MM m x = MM { unMM :: m x -> m x }
mkMM :: (Typeable a , Typeable b) => (m a -> m a) -> m b -> m b
mkMM t = maybe id unMM (gcast (MM t))
-- Apply a 'GenericM' everywhere, transforming the results with a
-- 'GenericMM'.
type GenericMM m = Data a => m a -> m a
everywhereMM :: Monad m => GenericMM m -> GenericM m -> GenericM m
everywhereMM mm m x = mm (m =<< gmapM (everywhereMM mm m) x)
everywhereMM
和mkMM
的定义相似
Data.Generics.Schemes
和Data.Generics.Aliases
中的everywhereM
和mkM
;区别在于everywhereMM
使用mm
,GenericMM
来转换结果。
现在我们可以写一个weaken3
。我们的想法是为GenericM
案例合并标准Var
Exp
GenericMM
local
在Binder
案例中应用type W = Reader Int
weaken3 :: Exp -> Exp
weaken3 e = runReader (w e) 0
where
w :: GenericM W
w = everywhereMM (mkMM b) (mkM v)
b :: W (Binder Exp) -> W (Binder Exp)
b = local succ
v :: Exp -> W Exp
v (Var j) = do
i <- ask
return $ wVar i j
v e = return e
:
{{1}}
但是这个解决方案有点令人不满意:创建新的遍历需要深入研究SYB库代码。
再次,here is a Gist with more examples,包括上面的代码。