【问题标题】:Abstracting monad composition as a transformer将单子组合抽象为变压器
【发布时间】:2011-11-16 19:58:07
【问题描述】:

对不起,如果这个问题看起来有点微不足道......它不适合我。 我很高兴地编写了以下单子:

type SB i a = ReaderT ( AlgRO i ) (State ( AlgState i ) ) a

这是一个行为良好的 monad。 ReaderT 是一个 monad 转换器,State 是 State monad,AlgRO 和 AlgState 是在 i 中参数化的数据类型,分别用于可变和只读状态。现在,如果我想用 newtype 把它做成一个整洁的 monad 转换器,像这样:

newtype SbT m i a = SbT {
    runSbT:: m ( SB i a )
}

我应该如何进行?我什至无法将(Monad 类型类的)绑定方法放在一起,更不用说(MonadTrans 的)“提升”了......我想自动推导可能会有所帮助,但我想了解它在这种情况下是如何工作的。

提前致谢。

【问题讨论】:

    标签: haskell monads composition monad-transformers


    【解决方案1】:

    您是否打算在新类型中的东西周围再包装一个m?我建议如下:

    newtype Sb i a = Sb { runSb :: SB i a }
    

    ...这应该使您的instance Monad (Sb i) 更容易编写。如果你真的想写一个 monad 转换器,那么你应该一直使用转换器;例如,

    type SBT m i a = ReaderT (AlgRO i) (StateT (AlgState i) m) a
    newtype SbT m i a = SbT { runSbT :: SBT m i a }
    

    作为第二个兴趣点,η-reduce type 同义词通常更可取(因为它们必须始终“完全应用”);使用 SBSBT 执行此操作将如下所示:

    type SB i = ReaderT (AlgRO i) (State (AlgState i))
    type SBT m i = ReaderT (AlgRO i) (StateT (AlgState i) m)
    

    【讨论】:

    • 顺便说一句,你不能用这种方式用SbT 制作一个monad转换器。 Transformers 有一种(* -> *) -> * -> *,即以一个 monad 和一个类型作为参数。 i 参数必须是第一个,所以你可以部分应用它。
    【解决方案2】:

    我不认为SbT 的定义是你想要的。这定义了函子组合,并假设m 参数是FunctorApplicative,这应该保留这些属性。但一般来说,像这样的组合不会从其他两个单子中创建一个新的单子。有关该主题的更多信息,请参阅this question

    那么,如何创建你想要的 monad 转换器呢?虽然 monad 不直接组合,但可以组合 monad transformers。所以要从现有的变压器中构建一个新的变压器,你基本上只是想给那个组合起一个名字。这与您拥有的 newtype 不同,因为您直接应用 m,而不是将其传递到转换器堆栈。

    在定义 monad 转换器时要记住的一点是,它们必须以某些方式“向后”工作 - 当您将复合转换器应用到 monad 时,“最里面”的转换器会首先破解它,然后它产生的转换后的 monad 是下一个转换器可以使用的,&c。请注意,这与将组合函数应用于参数时得到的顺序没有任何不同,例如(f . g . h) x 首先将参数传递给 h,即使 f 是组合中的“第一个”函数。

    好的,所以你的复合转换器需要将它应用到的 m​​onad 传递给最里面的转换器,也就是说,嗯.... 哎呀,原来SB 已经应用于单子。难怪这不起作用。我们需要先删除它。它在哪里?不是State--我们可以删除它,但我们不想这样做,因为它是你想要的一部分。嗯,但是等等——State 又被定义为什么?哦对了:

    type State s = StateT s Identity
    

    啊哈,我们走了。让我们把Identity 拿出来。我们从您当前的定义出发:

    type SB i a = ReaderT ( AlgRO i ) (State ( AlgState i ) ) a
    

    到等价形式:

    type SB i a = ReaderT ( AlgRO i ) ( StateT ( AlgState i ) Identity ) a
    

    然后我们把这个懒惰的家伙踢出去:

    type SB' i m a = ReaderT ( AlgRO i ) ( StateT ( AlgState i ) m ) a
    type SB i a = SB' i Identity a
    

    但是现在SB' 看起来有点像一个monad 转换器定义,并且有充分的理由,因为它是。所以我们重新创建了newtype 包装器,并在那里抛出了一些实例:

    newtype SbT i m a = SbT { getSB :: ReaderT ( AlgRO i ) ( StateT ( AlgState i ) m ) a }
    
    instance (Functor m) => Functor (SbT i m) where
        fmap f (SbT sb) = SbT (fmap f sb)
    
    instance (Monad m) => Monad (SbT i m) where
        return x = SbT (return x)
        SbT m >>= k = SbT (m >>= (getSB . k))
    
    instance MonadTrans (SbT i) where
        lift = SbT . lift . lift
    
    runSbT :: SbT i m a -> AlgRO i -> AlgState i -> m (a, AlgState t)
    runSbT (SbT m) e s = runStateT (runReaderT m e) s
    

    需要注意的几点:这里的runSbT 函数不是字段访问器,而是我们所知道的堆栈中每个转换器的组合“运行”函数。同样,lift 函数必须为两个内部转换器提升一次,然后添加最终的 newtype 包装器。这两者都使它作为一个单子转换器工作,隐藏了它实际上是一个复合的事实。

    如果您愿意,也可以直接为 MonadReaderMonadState 编写实例,方法是提升组合转换器的实例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-01
      • 2013-09-13
      • 1970-01-01
      相关资源
      最近更新 更多