【问题标题】:Implementing Monad instance for a nested monadic type为嵌套的单子类型实现单子实例
【发布时间】:2020-05-24 06:52:02
【问题描述】:

作为 Haskell 自学练习的一部分,我正在尝试为我的类型派生一个 Monad 实例。类型定义为:

newtype ParsePackUnpack f a = ParsePackUnpack
  {
    unparse:: State PackUnpackState (Ap f a)
  }

其中Ap f a 来自Data.Monoid。对于我的类型,我想说解析是一个有状态的操作,结果是任何monoid

到目前为止,我已经通过提升成功实现了这个 3 级深度类型的 FunctorApplicative 实例:

instance Functor f => Functor (ParsePackUnpack f) where
  fmap f ma =
    let f' = fmap f     -- lift (a -> b) to (Ap f a -> Ap f b)
     in ParsePackUnpack $ f' <$> (unparse ma)

instance Applicative f => Applicative (ParsePackUnpack f) where
  pure = ParsePackUnpack . pure . pure

  f <*> ma =
    let f' = liftA2 (<*>) . unparse $ f       -- lift Ap f (a -> b) -> Ap f a -> Ap f b to State s (Ap f a) -> State s (Ap f b)
     in ParsePackUnpack $ f' (unparse ma)     -- Apply to State s (Ap f a)

但我无法为我的类型正确派生 Monad 实例。经过一番打高尔夫球后,这是我的最新尝试:

instance Monad f => Monad (ParsePackUnpack f) where
  return = ParsePackUnpack . return . return

  ma >>= f = ParsePackUnpack . state $ \st ->
    let (a, s) = runState (unparse ma) st
        res = a >>= fst . flip runState s . unparse . f -- fst ignores state from the result
    in (res, s)

我认为这是不正确的,因为我忽略了 res 操作中的状态。

为我的类型实现&gt;&gt;= 操作的正确方法是什么?由于这是一个学习练习,我试图避免使用 Monad 变压器。如果 Monad 变压器是要走的路,你能解释一下为什么会这样吗?

【问题讨论】:

    标签: haskell monads state-monad


    【解决方案1】:

    Monad 的组合不如应用程序好。虽然f (g a)fg 存在时是一个应用程序(因此您可以编写应用程序实例),但当fg 是单子时,它通常不是单子。这就是为什么我们需要单子转换器而不是应用转换器。

    这是一个相关的练习。忘记使用库中的State,让我们手动使用它的表示。 State s (IO a) 展开为 s -&gt; (IO a, s)。要实现绑定,您将获得

    f :: s -> (IO a, s)
    g :: a -> s -> (IO b, s)
    

    您能想出如何通过“有状态”传递s 来将第一个喂给第二个吗?

    bound :: s -> (IO b, s)
    bound s0 = ??
    

    试一试。 (剧透)在你确信这是不可能的之后,想想是什么让它不可能,以及你需要如何修改类型以使其成为可能。然后使用该模式定义一个“StateIO s”monad。

    【讨论】:

    • 注:虽然FunctorApplicative 可以是很好的热身,你只需要pure(&gt;&gt;=),其余的你可以免费获得,使用liftMap 来自Control.Monad
    • 谢谢!通过你的思想实验,这是不可能的,因为我无法从IO a 得到a “unwrapped”吗?提升f 意味着提升整个f 而不仅仅是f 的第一个参数?我试图了解 monad 何时组成。
    • @skanur,其实可以得到bound :: s -&gt; IO b,所以你不需要解开a。但是最后一个裸露的s 它应该是无法创建的,因为它依赖于a (所以你是对的,但总的来说我们不应该希望能够在单子下打开东西)。
    • 很少有单子组成应用程序的简单方式——ReaderMaybe 是唯一能想到的。其他的需要以某种方式进行调整。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-30
    • 1970-01-01
    • 2013-06-17
    • 1970-01-01
    • 2015-03-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多