状态的应用实例 - 数据流的顺序



我正在尝试为这种类型实现应用实例:

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

我对(<*>(功能有一些不同的想法。 我想到的一种实现方法是

(<*>) :: State s (a -> b) -> State s a -> State s b
State f <*> State s = State $ do
(fa, fs) <- f
let (sa, ss) = s fs
return (fa sa, ss)

(<*>) :: State s (a -> b) -> State s a -> State s b
State f <*> State s = State $ do
(sa, ss) <- s
let (fa, fs) = f ss
return (fa sa, fs)

哪一个(甚至任何一个(是正确的,为什么?

它们都经过类型检查,区别仅在于"状态"转换顺序。我找不到任何好的理由来偏爱一个而不是另一个......

首先,我建议不要使用(monadic!do定义这样的应用实例的语法,因为它掩盖了正在发生的事情。以下是仅使用标准函数语法的定义:

State f <*> State s = State $ q
-> let (fa, fs) = f q
(sa, ss) = s fs
in (fa sa, ss)

State f <*> State s = State $ q
-> let (fa, fs) = f ss
(sa, ss) = s q
in (fa sa, fs)

这也更清楚地表明,在应用实例中实际上没有任何内在的评估顺序(与 monad 实例不同(。

我相信两者都是正确的,因为据我所知,它们不违反任何适用法则。但是,首先是实际使用的内容。我认为这是因为惯例:人们期望<*>左手论证的效果首先适用,然后再适用右手论证。与IO进行比较,例如,其中

(,) <$> readLn <*> getLine :: IO (Int, String)

首先提示输入 Int,然后读取字符串。让 State 以类似的方式行事真是太好了。

两者都是合理的。请注意,您可以从另一个获取其中任何一个:

x <*2> y = flip ($) <$> y <*1> x

但是,库惯例是"效果"从左到右执行。因此,第一个版本看起来更熟悉。

好吧,这取决于Monad绑定>>=作为适用交换法的状态流,并且在评论中也指出

适用交换法

如果 f 也是 Monad,它应该满足pure = return(<*>) = ap

意思是,如果MonadState状态从左向右流动,那么在适用中也应该如此,反之亦然。

但这并不意味着应用应该取决于 monad 实现或 do 表示法,正如 @leftaroundabout 所说,这是不正确

最新更新