【问题标题】:How to use `local` and a `Reader` monad with Scrap Your Boilerplate (SYB)?如何在 Scrap Your Boilerplate (SYB) 中使用`local` 和`Reader` monad?
【发布时间】:2013-05-29 05:30:49
【问题描述】:

如何使用 SYB(或其他一些 Haskell 泛型包)在使用 local 修改子计算环境的 Reader monad 中编写转换? GenericMeverywhereM(带有 a -> m a)的类型似乎不支持使用 localm 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)

一个递归函数,它用值递增所有嵌入的Ints 大于包裹它们的Binders 的数量:

-- 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 环境中,并使用SYB 自动处理(:@) 的样板递归案例。

使用Reader 环境重写weaken1,但不使用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 应用于特定于 Var 案例的 Exp -&gt; Reader Int Exp
  • Lam 案例在递归之前修改了环境:everywhereM not 在这里工作(据我所知)。 Binder 类型告诉我们需要在哪里使用 local,因此我们可能希望将 mkM 应用于 Binder Exp -&gt; Reader Int (Binder Exp) 特定情况,但我不知道如何。

Here is a Gist with more examples,包括上面的代码。

【问题讨论】:

    标签: generics haskell ghc


    【解决方案1】:

    这是一个解决方案,通过创建一个新的 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)
    

    everywhereMMmkMM 的定义类似 到everywhereMmkMData.Generics.SchemesData.Generics.Aliases;不同之处在于everywhereMM 使用mmGenericMM 来转换结果。

    现在我们可以写一个weaken3。这个想法是为Var 的情况结合一个标准的GenericM Exp 与新的 GenericMMBinder 案例中应用 local

    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 库代码。

    同样,here is a Gist with more examples,包括上面的代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-07
      • 2014-01-16
      • 2019-04-23
      • 2014-11-14
      • 1970-01-01
      • 2011-06-22
      • 2017-06-22
      • 1970-01-01
      相关资源
      最近更新 更多