【问题标题】:Type Variable Location in Transformers变压器中的类型变量位置
【发布时间】:2018-09-10 05:52:10
【问题描述】:

考虑 State 类型 - 或者至少是简化版本:

newtype State s a = State { runState :: s -> (a, s) }

现在,假设我们要导出 StateT monad 转换器。 transformers 定义如下:

newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }

这里,m 被放置在函数箭头的右侧,但在元组之外。但是,如果我们不知道正确答案,我们可能会将m 放在其他地方:

newtype StateT s m a = StateT { runStateT :: m (s -> (  a,  s)) }
newtype StateT s m a = StateT { runStateT ::    s -> (m a,  s)  }

显然transformers 中的版本是正确的,但为什么呢?更一般地说,在定义 monad 转换器时,如何知道将“内部”monad 的类型变量放在哪里?再概括一下,comonad transformers 有没有类似的规则?

【问题讨论】:

    标签: haskell monads monad-transformers comonad


    【解决方案1】:

    我觉得m ~ IO:

    s -> IO (a, s)
    

    是一个动作的类型,它可以读取当前状态s,根据它执行IO(例如打印当前状态,从用户那里读取一行),然后产生新的状态s,和一个返回值a

    改为:

    IO (s -> (a, s))
    

    是立即执行 IO 的动作类型,不知道当前状态。 IO全部结束后,返回一个将旧状态映射成新状态的纯函数和返回值。

    这与之前的类型类似,因为新的状态和返回值可以同时依赖于之前的状态和 IO。但是,IO 不能依赖于当前状态:例如,不允许打印当前状态。

    相反,

    s -> (IO a,  s)
    

    是读取当前状态s的动作类型,然后根据它执行IO(例如打印当前状态,从用户那里读取一行),然后产生一个返回值a。依赖于当前状态,bot 不在 IO 上,产生一个新状态。这种类型实际上同构于一对函数(s -> IO a, s -> s)

    这里,IO 可以从用户那里读取一行,并根据该行产生一个返回值a,但新状态不能依赖于该行。

    由于第一个变体更通用,我们希望它作为我们的状态转换器。

    我不认为有一个“一般规则”来决定将m 放在哪里:这取决于我们想要实现的目标。

    【讨论】:

    • 这三个分别通过StateT s IOCompose IO (State s)Compose (State s) IO-XDerivingVia(gist)派生,通过这些via types可派生的实例可以使用全新的:instances ghci 命令列出
    猜你喜欢
    • 1970-01-01
    • 2022-01-20
    • 1970-01-01
    • 2020-10-31
    • 2020-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-25
    相关资源
    最近更新 更多