首先让我们弄清楚所使用的斐波那契算法。这个想法是从元组(0, 1)开始,然后找到下一个为(1, 0 + 1),下一个为(1, 1 + 1)、(2, 2 + 1)、(3, 3 + 2),等等。一般步长为\(a, b) -> (b, a + b)。您可以看到在这些元组中是斐波那契数。
inner do 的第一条语句里面发生了什么,即什么
(a,b)
Haskell 没有语句,只有表达式。
y <- x 不是一个完整的表达式。类似于x >>= \y ->。
y <- x
m
是一个完整的表达式,相当于x >>= \y -> m。不是y <- n 形式的行n 等效于_ <- n(不包括let 行,也许还有一些我忘记了)。
使用它,我们可以对 do-notation 进行脱糖。
fib n =
flip evalState (0, 1)
( forM
[0..(n-1)]
(\_ -> get >>= (\(a, b) -> put (b, a + b)))
>>= (\_ -> get >>= (\(a, b) -> return a)))
)
现在只需要了解>>=、return、get、put 等等。
状态实际上只是s -> (s, a) 类型的函数。它们采用初始状态并产生下一个状态以及一些其他值。
m >>= n a.k.a. "bind" 的类型为 Monad m => m a -> (a -> m b) -> m b。那么,如果我们的 Monad 是State s,则等同于:
m >>= n ::
( s -> (s, a))
-> (a -> s -> (s, b))
-> ( s -> (s, b))
m 返回的a 必须传递给n。我们还能猜到什么?我们希望状态也能传递,所以m 返回的状态也必须传递给n。函数m >>= n 必须返回n 返回的状态和值。然后我们知道如何实现绑定:
m >>= n = uncurry (flip n) . m
return :: Monad m => a -> m a 相当于return :: a -> s -> (s, a):
return = flip (,)
get :: State s s 等价于get :: s -> (s, s):
get = join (,)
put :: s -> State s () 或put :: s -> s -> (s, ()):
put s _ = (s, ())
evalState :: s -> State s a -> a 或evalState :: s -> (s -> (s, a)) -> a:
evalState s f = snd (f s)
您可以展开所有定义并准确查看示例中发生的情况。只是直觉就足够了。
forM
[0..(n-1)]
(\_ -> get >>= (\(a, b) -> put (b, a + b)))
我们不关心数字 0 到 n - 1 所以第一个参数被删除。 get 检索当前状态,然后 put 写入新状态。我们这样做n 次。
>>= (\_ -> get >>= (\(a, b) -> return a)))
我们不关心累加值(即单位),因此第一个参数被删除。然后我们获取当前状态并仅投影该对的第一个元素。这是我们正在寻找的最终答案。
flip evalState (0, 1) …
最后我们从初始状态(0, 1)开始运行。
我们可以对此实现进行一些清理。首先,我们不关心[0..(n-1)] 的范围,我们只关心重复一个动作n 次。更直接的方法如下:
replicateM n (get >>= \(a, b) -> put (b, a + b))
结果是未使用的单元列表,因此更有效的版本是:
replicateM_ n (get >>= \(a, b) -> put (b, a + b))
已经有一个用于get 后跟put 的通用模式的函数,名为modify,定义为\f -> get >>= put . f。因此:
replicateM_ n (modify (\(a, b) -> (b, a + b)))
然后是部分:
>>= (\_ -> get >>= (\(a, b) -> return a)))
任何时候我们不关心之前的结果,我们都可以使用>>。
>> get >>= (\(a, b) -> return a))
这是:
>> get >>= return . fst
m >>= return . f 简化为fmap f m:
>> fmap fst get
现在我们总共有:
fib n =
evalState
( replicateM_ n (modify (\(a, b) -> (b, a + b)))
>> fmap fst get
)
(0, 1)
为了比较,我们也可以使用:
fib n =
fst
( evalState
( replicateM_ n (modify (\(a, b) -> (b, a + b)))
>> get
)
(0, 1)
)
然后因为我很傻:
fib =
fst
. flip evalState (0, 1)
. (>> get)
. flip replicateM_ (modify (snd &&& uncurry (+)))
你为什么要在这里使用 state monad?
你不会的。这很清楚,因为我们只使用状态值;另一个值始终是单位并被丢弃。换句话说,我们只需要在开始时n(即要找到哪个斐波那契数),然后我们只需要累积的元组。
有时您想拥有一串像h . g . f 这样的组合,但您想发送两个参数而不是一个。那是State 可能适用的时候。
如果一些函数读取和一些写入状态(第二个参数),或者两者都做,那么State 符合要求。如果只有读者,则使用Reader,如果只有作者,则使用Writer。
我们可以修改示例以更好地利用 State Monad。我会让元组消失!
fib =
flip evalState 0
. foldr (=<<) (return 1)
. flip replicate (\x -> get >>= \y -> put x $> x + y)