变形金刚中的类型变量位置
Type Variable Location in Transformers
考虑 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 转换器时,如何知道将 'inner' monad 的类型变量放在哪里?更概括地说,comonad transformers是否有类似的规则?
我认为 m ~ IO
:
时的差异很容易理解
s -> IO (a, s)
是一种可以读取当前状态的动作类型 s
,根据它执行 IO(例如打印当前状态,从用户那里读取一行),然后生成新状态s
,和一个 return 值 a
。
改为:
IO (s -> (a, s))
是一种在不知道当前状态的情况下立即执行IO的动作类型。所有IO结束后,它return是一个纯函数,将旧状态映射到新状态和return值。
这与之前的类型类似,因为新状态和 return 值可以同时依赖于之前的状态和 IO。但是,IO 不能依赖于当前状态:例如,不允许打印当前状态。
相反,
s -> (IO a, s)
是读取当前状态 s
的动作类型,然后根据它执行 IO(例如打印当前状态,从用户那里读取一行),然后产生一个 return 值 a
。根据当前状态,而不是 IO,会产生一个新状态。这种类型实际上同构于一对函数 (s -> IO a, s -> s)
.
在这里,IO 可以从用户那里读取一行,并根据它产生一个 return 值 a
,但是新状态不能依赖于该行。
由于第一个变体更通用,我们希望它作为我们的状态转换器。
我认为没有 "general rule" 来决定放在哪里 m
:这取决于我们想要实现的目标。
考虑 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 转换器时,如何知道将 'inner' monad 的类型变量放在哪里?更概括地说,comonad transformers是否有类似的规则?
我认为 m ~ IO
:
s -> IO (a, s)
是一种可以读取当前状态的动作类型 s
,根据它执行 IO(例如打印当前状态,从用户那里读取一行),然后生成新状态s
,和一个 return 值 a
。
改为:
IO (s -> (a, s))
是一种在不知道当前状态的情况下立即执行IO的动作类型。所有IO结束后,它return是一个纯函数,将旧状态映射到新状态和return值。
这与之前的类型类似,因为新状态和 return 值可以同时依赖于之前的状态和 IO。但是,IO 不能依赖于当前状态:例如,不允许打印当前状态。
相反,
s -> (IO a, s)
是读取当前状态 s
的动作类型,然后根据它执行 IO(例如打印当前状态,从用户那里读取一行),然后产生一个 return 值 a
。根据当前状态,而不是 IO,会产生一个新状态。这种类型实际上同构于一对函数 (s -> IO a, s -> s)
.
在这里,IO 可以从用户那里读取一行,并根据它产生一个 return 值 a
,但是新状态不能依赖于该行。
由于第一个变体更通用,我们希望它作为我们的状态转换器。
我认为没有 "general rule" 来决定放在哪里 m
:这取决于我们想要实现的目标。