在rust中,我们可以说。
if Some(inner_val) = option { ... }
haskell 有没有一个对应的词?
我将给予一个小背景,以防这看起来像是一件愚蠢的事情(请随时告诉我它是愚蠢的)。我正在阅读管道自述文件,并在沿着过程中做它的练习。其中一个指导读者“实现一个peek函数,从上游获取下一个值(如果可用),然后将其放回流中。”我这样做了:
peek :: Monad m => ConduitT a o m (Maybe a)
peek = do
mx <- await
case mx of
Nothing -> return mx
Just x -> do
leftover x
return mx
我不喜欢我必须在两个手臂上都说return mx
。人们可以很容易地想象手臂之间分担的工作比简单的return
多得多的情况。我希望有条件地执行未分担的工作,然后继续前进。我可以说
peek2 :: Monad m => ConduitT a o m (Maybe a)
peek2 = do
mx <- await
when
(isJust mx)
( do
let Just x = mx
leftover x
)
return mx
但这在mx
上匹配了两次,更不用说编译器会对我生气。而且,它是特定于Maybe
的,但我可能想匹配任意类型,只在它是特定形状时才做一些事情。
编辑:也许值得在问题文本中提到的是,使用case表达式并不会像我写这篇文章时错误地认为的那样,将后续计算“困”在它的怀里。@Willem-Van-Onsem的回答简洁地解决了这个问题,@Ben的回答详细地阐述了正确的思维方式。
4条答案
按热度按时间t1rydlwq1#
我不明白为什么你要提到
return mx
两次,这相当于:因此,如果你愿意,
return mx
是“外部do
块”的一部分,pure ()
充当空操作,有时也被称为skip
或yield
。azpvetkf2#
当你想要匹配一个
Maybe
是Just
时(或者其他一些情况,比如Either
是Right
),就像你在例子中所做的那样,你可以使用traverse_
:如果你想在内部调用多个函数,你可以使用
for_
(等价于flip traverse_
),并在一个方便的lambda中写出更多的步骤,如下所示:为什么这样做:
Foldable
包含一定数量的其类型的值,可以提取这些值,然后对其进行运算。Maybe
和Either
各自包含零(Nothing
或Left
)或一个(Just
或Right
)。traverse_
取一个Foldable
,将提供的函数应用于它的每个值以获得上下文,然后应用所有的上下文,如果值为零,它什么也不做,只返回pure ()
,如果值为一,它对这个值运行函数。4dbbbstv3#
记住
return mx
不仅仅是一个语句,它还是一个 value。作为一个整体,case
表达式(就像if
表达式一样)也是一个值;它等于匹配条件对应的值,这意味着每个可能的分支都必须有一个值,忽略一个没有任何意义。你想要说的是“如果
mx
是Just x
,那么做leftover x
,如果是Nothing
,那么什么也不做;然后返回mx
“。Haskell完全可以接受您这样说,并且它不需要特殊的语法;你只需要通过在case
的Nothing
臂中提供一个对应于“不做任何事情”的 value 来实际说出它。pure ()
或return ()
(它们是等价的,但pure
是更现代的表达方式)是一个一元值,没有副作用,返回值很无聊。现在你可能想知道
when
是如何避免要求你显式地提供“do nothing”的情况的,这并不神奇,when
的实现是这样的:when
提供了pure ()
do-nothing case(实际上是整个if
表达式),因此调用者不必这样做。如果您发现自己想要重复使用“whenJust
x do something with x,orse do nothing”模式,只需执行您一直在执行的操作以避免重复:将那些永远不会改变的位分解成一个函数,该函数对这些位进行硬编码,并将 * 会 * 改变的位作为参数:when
仅采用简单的f ()
参数来说明如果布尔值为真则要做什么,因为布尔值不包含除了真/假区分之外的有用数据。Maybe
的不同之处在于Just
情况实际上具有我们几乎肯定要使用的值,因此,为了封装整个case分支并传递给whenJust
,我们需要一个 function,它将接收Just
中的值。或者,如果
Just
的情况更加复杂,则可能如下所示:事实上,如果我search Hoogle for "whenJust",我看到许多其他包的作者也有同样的想法;如果其中任何一个适合您依赖,您可以重用它们定义中的一个,但是它非常小且显而易见,如果您更喜欢编写自己的定义而不是添加依赖项,我不会担心重复。
当然,您可以将类似的模式应用于
Either
或任何其他类型,其中您可能希望封装特定的模式匹配模式,以便不必每次都完整地重复它。另一种看待这个问题的方法是记住
Maybe
是Traversable
和Foldable
; Joseph Sible的答案涵盖了这一点,使用traverse_
或for_
可能会更好(或者如果你想要信息更丰富的特殊情况名称,可以定义whenJust = for_
),但这个答案希望能帮助你思考这个问题的更一般的情况,以及当组合子不归结为现有的标准函数时,如何创建自己的组合子。ghg1uchk4#
其他答案讨论了解决这一特定任务的实用方法,我对此表示赞同,但我想指出的是,“在Hoogle上查找”是解决许多Haskell问题的实用方法,往往会导致惯用的解决方案,这是一种值得练习的有用技能。
假设你还不知道你要找的函数是
for_
和traverse_
(从其他答案中),类型引导的方法通常可以为你指出一个关于Hoogle的好方向,如果有一个函数已经做了你要找的事情,它会是什么类型?你有一个
Maybe a
和一个(a -> m b)
作为输入,对于a
,b
,和m
类型,你想要得到一个m b
,所以我们可以尝试搜索Maybe a -> (a -> m b) -> m b
,结果第一个结果是一个名为whenJust'
的东西,但是它来自一个你不太可能想要的奇怪的库,同样的,还有一个叫做forM'
的东西,来自另一个奇怪的库。从这里开始,你可以找到几个方向。尝试搜索
whenJust
(不包括'
),看看它是否是一个流行的函数,或者类似地搜索forM
。这可能会把你带到forM_
,然后可能会带到traverse_
或for_
。或者你可以调整你的搜索。也许你可以搜索
f a
,而不是特别要求Maybe a
,然后让Hoogle为你找到一个好的f
。事实证明,这并没有多大帮助。但是你也可以搜索更具体的东西,而不是更少:你知道你的b
实际上是()
,搜索它确实有帮助。搜索f a -> (a -> m ()) -> m ()
会列出一些奇怪的函数,但它也会列出所有合理的选项。如果你对奇怪的库中的函数不感兴趣,你甚至可以通过添加module:base
来进一步细化搜索。最后你得到f a -> (a -> m ()) -> m () module:base
,这很好地回答了你最初的问题