如何使用`local`和`Reader` monad和Scrap您的Boilerplate(SYB)?

我怎样才能使用SYB(或其他Haskell的泛型包)在一个Reader monad中编写一个转换,它使用local来修改子电子环境? GenericMeverywhereM的类型(带有a -> ma )似乎不支持使用local (类型为ma -> ma )来包装子计算。 如果可能的话,我想要一个使用“标准”/“现成”转换的解决方案。

代表性的例子

一个(神秘的)递归数据类型:

{-# 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参数放在Reader环境中,并使用SYB自动处理(:@) weaken1的样板递归情况。

重写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自动地everywhereM这里工作。
  • Var案例使用环境,但不对其进行修改: everywhereM在此处工作,方法是将mkM应用于特定于Var案例的Exp -> Reader Int Exp
  • Lam案例在递归之前修改了环境: everywhereM在这里不起作用(据我所知)。 Binder类型告诉我们需要使用local ,以便我们可能希望将mkM应用于Binder Exp -> Reader Int (Binder Exp)特定案例,但我无法弄清楚。
  • 这里有更多的例子,包括上面的代码。


    通过创建新的SYB遍历, everywhereMM MM:

    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)
    

    的定义everywhereMMmkMM类似于everywhereMmkMData.Generics.SchemesData.Generics.Aliases ; 区别在于everywhereMM使用mmGenericMM )来转换结果。

    现在我们可以写一个weaken3 。 我们的想法是将ExpVar案例的标准GenericM与适用于Binder案例中的local的新GenericMMBinder

    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
    

    但是这个解决方案有点不尽人意:创建新的遍历需要深入挖掘SYB库代码。

    再次,这里是一个包含更多示例的Gist,包括上面的代码。

    链接地址: http://www.djcxy.com/p/71723.html

    上一篇: How to use `local` and a `Reader` monad with Scrap Your Boilerplate (SYB)?

    下一篇: unique validation in DDD