monad_进行跳跃II:使用更多的Monad

monad

几周前 ,我们提出了一些重要步骤,以超越Haskell的“初学者”阶段。 我们学习了如何组织您的项目以及如何找到相关文档。 本周,我们将继续寻找另一个可以大步前进的地方。 我们将探讨如何扩展有关monad用法的词汇。

Monad是Haskell的重要组成部分。 除非您知道如何合并它们的monadic函数,否则您不能使用很多库。 这些功能通常涉及对该库定制的monad。 当您刚开始时,可​​能很难知道如何将这些monad合并到程序的其余部分中。

在本文中,我们将重点介绍许多monad和库使用的特定模式。 我将此模式称为“运行”模式。 通常,您会使用名称为runXXXrunXXXT类的函数,其中XXX是monad的名称。 这些函数将始终将单子表达式作为其第一个参数。 然后,他们还将获取其他一些初始化信息,最后返回一些输出。 此输出可以是纯格式,也可以是您已经在使用的其他monad,如IO 。 首先,我们将了解State Monad的工作方式,然后再介绍其他一些库。

一旦掌握了这个主题,它看起来就非常简单。 但是,我们中的许多人首先学习的单子都带有不良的思维模式。 例如,我了解单子的第一件事是它们有副作用。 因此,您只能从副作用相同的地方调用它们。 这适用于IO,但不适用于其他monad。 因此,即使现在看来似乎很明显,我还是开始努力学习这个想法。 但是,让我们开始看看这种模式的一些例子。

要更深入地了解monad,请查看我们有关功能数据结构的系列文章! 我们首先学习诸如函子之类的简单事物。 然后,我们最终逐步发展到monads甚至monad变压器!

“逃跑”的基础知识:州立Monad

让我们首先回顾一下State monad。 此monad具有单个类型参数,我们可以将其作为全局读/写状态访问。 这是用State monad编写的示例函数:

stateExample :: State Int (Int, Int, Int)
stateExample = do
a <- get
modify (+1)
b <- get
put 5
c <- get
return (a, b, c)

如果此功能令人困惑,则应查看State文档 。 至少会向您显示相关的类型签名。 首先,我们读取初始状态。 然后我们用一些功能对其进行修改。 最后,我们彻底改变它。

在上面的示例中,如果我们的初始状态为1,我们将返回(1,2,5)作为结果。 如果初始状态为2,我们将返回(2,3,5) 。 但是假设我们有一个纯函数。 我们如何调用状态函数?

pureFunction :: Int -> Int
pureFunction = ???

答案是runState函数。 我们可以检查文档并找到其类型:

runState :: State sa -> s -> (a, s)

此功能有两个参数。 首先是State行动。 我们将上面的函数作为此参数传递! 然后第二个是初始状态,这就是我们如何配置它。 那么结果就是纯净的。 它包含我们的结果以及状态的最终值。 因此,这里有一个示例调用,我们可以通过它在纯函数中给我们此单子表达式。 我们将从where子句中调用它,并丢弃最终状态:

pureFunction :: Int -> Int
pureFunction input = a + b + c
where
((a,b,c), _) = runState stateExample input

这是我们如何使用runXXX模式的最简单示例。

升级到变形金刚

现在,假设我们的State函数不是很纯净。 现在,它想打印一些输出,因此需要IO monad。 这意味着它将通过IO使用StateT monad转换器:

stateTExample :: StateT Int IO (Int, Int, Int)
stateTExample = do
a <- get
lift $ print “Initial Value:”
lift $ print a
modify (+1)
b <- get
lift $ putStrLn “After adding 1:”
lift $ print b
put 5
c <- get
lift $ putStrLn “After setting as 5:”
lift $ print c
return (a, b, c)

现在,我们不是从纯格式调用此函数,而是需要从IO函数调用它。 但是再次,我们将使用runXXX函数。 现在,由于我们使用的是monad变压器,因此不会得到纯净的结果。 相反,我们将在底层monad中获得结果。 这意味着我们可以从IO调用此函数。 因此,让我们检查一下runStateT函数的类型。 我们用IO代替了通用的monad参数m

runStateT :: StateT s IO a -> s -> IO (a, s)

看起来很像runState ,除了额外的IO参数! 它没有返回包含结果的纯元组,而是返回了包含该结果的IO操作。 因此,我们可以从IO monad调用它。

main :: IO ()
main = do
putStrLn “Please enter a number.”
input <- read <$> getLine
results <- runStateT stateTExample input
print results

结果将得到以下输出:

Please enter a number.
10
Initial Value:
10
After adding 1
11
After setting as 5
5
(10, 11, 5)

对库使用“运行”

这种模式通常会扩展到您使用的库中。 例如,在有关解析系列文章中 ,我们研究了Megaparsec库。 该库中的许多单独的解析器组合器都存在于ParsecParsecT monad中。 因此,我们可以将一堆不同的解析器组合在一起成为一个函数。

但是,要从常规IO代码(或另一个monad)运行该函数,您需要使用runParserT函数。 让我们看一下它的类型签名:

runParserT
:: Monad m
-> ParsecT esma
-> String -- Name of source file
-> s -- Input for parser
-> m (Either (ParseError (Token s) e) a)

您不需要了解很多类型参数。 但是结构是一样的。 我们的run功能的第一个参数是单子动作。 然后,我们将提供我们需要的其他输入。 然后我们得到一些结果,并包裹在外部monad中(例如IO )。

如果我们使用servant-client库进行客户端API调用,则可以看到相同的模式。 您对API的任何调用都将在ClientM monad中进行。 现在,这是runClientM函数的类型签名:

runClientM :: ClientM a -> ClientEnv -> IO (Either ServantError a)

同样,出现了相同的模式。 我们将编写单声道动作并将其作为第一个参数传递。 然后,我们将提供一些初始状态,在本例中为ClientEnv 。 最后,我们将得到的结果( Either ServantError a )包裹在一个外部monad( IO )中。

表达式中的单子

同样重要的是要记住,许多基本的monad都可以运行,甚至不需要runXXX函数! 例如,您可以使用MaybeEither monad提取一些错误处理逻辑:

divideIfEven :: Int -> Maybe Int
divideIfEven x = if x `mod` 2 == 0
then Just (x `quot` 2)
else Nothing
dividesBy8 :: Int -> Bool
dividesBy8 = case thirdResult of
Just _ -> True
Nothing -> False
where
thirdResult :: Maybe Int
thirdResult = do
y <- divideIfEven x
z <- divideIfEven y
divideIfEven z

结论

Monad是使用许多不同的Haskell库的关键。 但是,当您第一次入门时,如何从代码中调用这些函数可能会非常混乱。 对于某些常见的monad变压器,例如ReaderState 。 要寻找的最常见模式是runXXXT模式。 掌握这种模式,您就可以很好地理解monads并编写更好的Haskell!

要进一步了解monad和类似结构,请务必阅读我们有关“ 功能数据结构”的系列文章。 如果本文中的代码令人困惑,那么您绝对应该检查一下! 如果您从未写过Haskell但想开始,请下载我们的初学者清单

翻译自: https://hackernoon.com/making-the-jump-ii-using-more-monads-bce44a7c3550

monad

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值