在过去的几个月里,我一直在花一些空闲时间来阅读有关 Monad 的文章。自从大学时代以来,我就没有使用过函数式语言。所以我不太记得 Haskell,当然也不知道 Scalaz。
学习洋葱、墨西哥卷饼和三明治,同时尝试将这些食物与大量 Haskell 代码关联起来,这是一段艰难的时光。幸运的是,我偶然发现了两篇关键文章,这让我大吃一惊:图片中的单子,以及另一个来自命令式编码背景的人,他简单地写了相当于以下内容的内容:
a -> b becomes m[a] -> m[b] (functor)
a -> m[b] becomes m[a] -> m[b] (monad)
and applicative is just a "function with context"
Run Code Online (Sandbox Code Playgroud)
连同描述bind目的的最简单的句子一起,内函子的许多类别爆炸并变得简单。
Identity、List、Maybe 等突然变得有意义了。在这些知识的推动下,我开始尝试使用一元类型在 C++ 中尝试一些 TMP,看看它会如何实现。
很快我就想传播状态。我想我现在读到的关于状态 monad 和 monad 转换器的文章比我一开始读的关于 monad 的文章还要多,但我一生都无法理解它们。我确信许多键盘已经磨损了,因为这些主题上输入的所有单词都回答了像我这样的人。
但我请你再受一点苦。
a -> s -> (b, s)
Run Code Online (Sandbox Code Playgroud)
函数接受一个值和某个状态,返回一个新值和(可能)修改后的状态。Bind 显然需要将返回值和状态传播到下一个函数中。
我的问题是这样的: monad 的工作是因为它们强加了一个结构。坚持结构并遵守法律会带来乐趣和彩虹。然而 a -> s -> (b, s) 看起来并不像一元 a -> m[b]。
即使应用'a',它仍然是s -> (b, s)。区别在于后一个函数采用(单子?)STATE 并返回带有值的状态。而常规形式是:接受一个 VALUE 并返回一个 WRAPPED 值。
传入的参数以及返回类型的形式都会有所不同。然而许多文章都说这显然看起来像一个单子。对我来说肯定不是。
如果我要放松一下我被告知必须持有的观点:它不是将 m[a] 绑定到 …
我可以编写一个 State Monad(在 Scala/Java 中),并且当我看到其他人使用它时,我几乎可以遵循逻辑。我不完全理解它正在解决的问题。它是一个包裹 funciton 的 monad S => (S,A)。
因此,当您通过 flatMap 嵌套导致 State Monad 的函数时,它会为您提供一组对数据执行的指令/操作(S)(但尚未执行它们)。直到最后,当你给它一个S并告诉它运行时,它才起作用。
与仅编写函数来完成相同的事情相比,这样做有什么好处?
Monad 不会组合,因此以这种方式拥有这些功能似乎对设计有很大的影响。如果您提供示例代码,请使用 Java 或 Scala(因为我不明白如何阅读 Haskell 或其他严格的函数式语言)。汉克斯!
考虑以下程序。
import Control.Monad.State
import Control.Monad.Catch
ex1 :: StateT Int IO ()
ex1 = do
modify (+10)
liftIO . ioError $ userError "something went wrong"
ex2 :: StateT Int IO ()
ex2 = do
x <- get
liftIO $ print x
ex3 :: StateT Int IO ()
ex3 = ex1 `onException` ex2
main :: IO ()
main = evalStateT ex3 0
Run Code Online (Sandbox Code Playgroud)
当我们运行程序时,我们得到以下输出。
$ runhaskell Test.hs
0
Test.hs: user error (something went wrong)
Run Code Online (Sandbox Code Playgroud)
但是,我预计输出如下。
$ runhaskell Test.hs
10
Test.hs: user error (something …Run Code Online (Sandbox Code Playgroud) 我正在尝试为(>>=)自定义类型的绑定运算符创建一个实例ST a
我找到了这种方法,但我不喜欢那个硬编码的0。
有没有办法在没有硬编码0和尊重函数类型的情况下实现它?
newtype ST a = S (Int -> (a, Int))
-- This may be useful to implement ">>=" (bind), but it is not mandatory to use it
runState :: ST a -> Int -> (a, Int)
runState (S s) = s
instance Monad ST where
return :: a -> ST a
return x = S (\n -> (x, n))
(>>=) :: ST a -> (a -> ST b) -> ST …Run Code Online (Sandbox Code Playgroud) 在 Haskell 中,这是一个结合了 State 和 Maybe monad 的 monad:
type StatefulMaybe a = StateT Int Maybe a
Run Code Online (Sandbox Code Playgroud)
这是一个可以成功(返回一个值)或失败的计算。如果成功,它会携带一个状态以及返回值。
我想写一个函数
choice :: StatefulMaybe a -> StatefulMaybe a -> StatefulMaybe a
Run Code Online (Sandbox Code Playgroud)
这需要两次这样的计算并返回第一个成功的(如果有的话)。只有成功计算的状态变化才会被推进。
事实上,经过一些实验,我想出了如何写这个。这里是:
orMaybe :: Maybe a -> Maybe a -> Maybe a
orMaybe (Just x) _ = Just x
orMaybe Nothing x = x
choice :: StatefulMaybe a -> StatefulMaybe a -> StatefulMaybe a
choice mx my = StateT (\s ->
(runStateT mx s) `orMaybe` (runStateT my s)
)
Run Code Online (Sandbox Code Playgroud)
有用:
foo …Run Code Online (Sandbox Code Playgroud) 根据State Monad的get函数提出的一个问题:
如果我跑
runState get 1
我得到了结果
(1,1)
这对我来说没问题,因为get函数将结果值设置为状态,在这种情况下状态为1.因此,(1,1)是结果.好.
但是,如果我跑
runState(do {(a,b)< - get; return a})(False,0)
我得到了结果
(假,(假,0))
这个我不明白.
get函数将结果值设置为状态并保持状态不变.所以我期待的是这样的事情
((假,0),(假,0))
与此相同
runState(do {(a,b)< - get; return b})(False,0)
结果是
(0,(假,0))
如上所述,我不再理解这一点.
所以,如果你能解释我这个奇怪的结果,那将是非常好的.;)
提前致谢
最好的祝福,
吉米
在真实世界的Haskell 章节中,他们给出了(>>)这样的理由:
当我们想要按特定顺序执行操作时,我们使用此函数,但不关心它的结果是什么.
然后他们给出了一个很好的例子来证明它:
ghci > print "foo" >> print "bar"
"foo"
"bar"
Run Code Online (Sandbox Code Playgroud)
在本章的后面,他们使用相同的概念在State monad中生成随机值:
getRandom :: Random a => RandomState a
getRandom =
get >>= \gen ->
let (val, gen') = random gen in
put gen' >>
return val
Run Code Online (Sandbox Code Playgroud)
但是为什么在这种情况下他们put gen' >>最终会忽略它的输出值而使用该陈述.为什么以上功能不能像这样:
getRandom :: Random a => RandomState a
getRandom =
get >>= \gen ->
let (val, gen') = random gen in
return val
Run Code Online (Sandbox Code Playgroud)
只是为了完成这个问题,我正在为上面的上下文添加相关的类型定义和函数:
newtype State s a = State {
runState :: …Run Code Online (Sandbox Code Playgroud) 我试图了解读者和/或州monad的实际需求.我见过的所有例子(包括很多关于stackoverflow,因为我已经寻找合适的例子,我可以使用以及各种书籍和博客文章)的形式(伪代码)
f = do
foo <- ask
do something with foo
g = do
foo <- ask
do something else using foo
h = runReader
(
f
g
)
Run Code Online (Sandbox Code Playgroud)
换句话说,调用两个函数并(可能)从一个调用到下一个调用保持某种状态.但是,我没有发现这个例子特别令人信服,因为(我认为)我可以让f返回一些状态,然后将该状态传递给g.
我希望看到一个例子,使用一个整数(比如说)作为要保留的状态,而不是两个顺序调用f然后从中心位置调用g,而不是调用f然后在内部调用g然后在主程序中更改了状态(如果状态monad).
我见过的大多数(实际上是所有)示例花费了大量时间专注于monad的定义,然后展示如何设置单个函数调用.对我来说,这就是能够进行嵌套调用并让状态随身携带,以证明它为什么有用.
我正在扭曲我的大脑,试图理解如何将Statemonad与... 结合起来Maybe.
让我们从一个具体(并且故意琐碎/不必要)的例子开始,我们使用Statemonad来查找数字列表的总和:
import Control.Monad.State
list :: [Int]
list = [1,4,5,6,7,0,3,2,1]
adder :: Int
adder = evalState addState list
addState :: State [Int] Int
addState = do
ms <- get
case ms of
[] -> return 0
(x:xs) -> put xs >> fmap (+x) addState
Run Code Online (Sandbox Code Playgroud)
凉.
现在让我们修改它,以便Nothing在列表包含数字时返回a 0.换句话说,evalState addState' list应该返回Nothing(因为list包含a 0).我觉得它可能看起来像这样......
addState' :: State [Int] (Maybe Int)
addState' = do
ms <- get
case …Run Code Online (Sandbox Code Playgroud) 此代码来自本文
我已经能够遵循它直到这一部分.
module Test where
type State = Int
data ST a = S (State -> (a, State))
apply :: ST a -> State -> (a,State)
apply (S f) x = f x
fresh = S (\n -> (n, n+1))
instance Monad ST where
-- return :: a -> ST a
return x = S (\s -> (x,s))
-- (>>=) :: ST a -> (a -> ST b) -> ST b
st >>= f = S (\s -> let …Run Code Online (Sandbox Code Playgroud) state-monad ×10
haskell ×8
monads ×7
exception ×1
io-monad ×1
maybe ×1
reader-monad ×1
scala ×1