haskell是否有一个与rust的if let pattern = mightMatch语法等价的语法?

mgdq6dx1  于 2022-12-23  发布在  其他
关注(0)|答案(4)|浏览(170)

在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的回答详细地阐述了正确的思维方式。

t1rydlwq

t1rydlwq1#

我不明白为什么你要提到return mx两次,这相当于:

peek :: Monad m => ConduitT a o m (Maybe a)
peek = do
  mx <- await
  case mx of
    Just x -> leftover x
    _ -> pure ()
  return mx

因此,如果你愿意,return mx是“外部do块”的一部分,pure ()充当空操作,有时也被称为skipyield

azpvetkf

azpvetkf2#

当你想要匹配一个MaybeJust时(或者其他一些情况,比如EitherRight),就像你在例子中所做的那样,你可以使用traverse_

import Data.Conduit
import Data.Foldable (traverse_)

peek :: Monad m => ConduitT a o m (Maybe a)
peek = do
  mx <- await
  traverse_ leftover mx
  return mx

如果你想在内部调用多个函数,你可以使用for_(等价于flip traverse_),并在一个方便的lambda中写出更多的步骤,如下所示:

import Data.Conduit
import Data.Foldable (for_)

peek :: Monad m => ConduitT a o m (Maybe a)
peek = do
  mx <- await
  for_ mx $ \x ->
    foo x
    bar x
    baz x
  return mx

为什么这样做:Foldable包含一定数量的其类型的值,可以提取这些值,然后对其进行运算。MaybeEither各自包含零(NothingLeft)或一个(JustRight)。traverse_取一个Foldable,将提供的函数应用于它的每个值以获得上下文,然后应用所有的上下文,如果值为零,它什么也不做,只返回pure (),如果值为一,它对这个值运行函数。

4dbbbstv

4dbbbstv3#

记住return mx不仅仅是一个语句,它还是一个 value。作为一个整体,case表达式(就像if表达式一样)也是一个值;它等于匹配条件对应的值,这意味着每个可能的分支都必须有一个值,忽略一个没有任何意义。
你想要说的是“如果mxJust x,那么做leftover x,如果是Nothing,那么什么也不做;然后返回mx“。Haskell完全可以接受您这样说,并且它不需要特殊的语法;你只需要通过在caseNothing臂中提供一个对应于“不做任何事情”的 value 来实际说出它。pure ()return ()(它们是等价的,但pure是更现代的表达方式)是一个一元值,没有副作用,返回值很无聊。

peek :: Monad m => ConduitT a o m (Maybe a)
peek = do
  mx <- await
  case mx of
    Nothing -> return ()
    Just x -> do
      leftover x
  return mx

现在你可能想知道when是如何避免要求你显式地提供“do nothing”的情况的,这并不神奇,when的实现是这样的:

when      :: (Applicative f) => Bool -> f () -> f ()
when p s  = if p then s else pure ()

when提供了pure () do-nothing case(实际上是整个if表达式),因此调用者不必这样做。如果您发现自己想要重复使用“when Just x do something with x,orse do nothing”模式,只需执行您一直在执行的操作以避免重复:将那些永远不会改变的位分解成一个函数,该函数对这些位进行硬编码,并将 * 会 * 改变的位作为参数:

whenJust :: Applicative f => Maybe a -> (a -> f ()) -> f ()
whenJust Nothing _ = pure ()
whenJust (Just x) f = f x

when仅采用简单的f ()参数来说明如果布尔值为真则要做什么,因为布尔值不包含除了真/假区分之外的有用数据。Maybe的不同之处在于Just情况实际上具有我们几乎肯定要使用的值,因此,为了封装整个case分支并传递给whenJust,我们需要一个 function,它将接收Just中的值。

peek :: Monad m => ConduitT a o m (Maybe a)
peek = do
  mx <- await
  whenJust mx leftover
  return mx

或者,如果Just的情况更加复杂,则可能如下所示:

peek :: Monad m => ConduitT a o m (Maybe a)
peek = do
  mx <- await
  whenJust mx $ \x -> do
    a <- step1 x
    b <- step2 x
    etc
  return mx

事实上,如果我search Hoogle for "whenJust",我看到许多其他包的作者也有同样的想法;如果其中任何一个适合您依赖,您可以重用它们定义中的一个,但是它非常小且显而易见,如果您更喜欢编写自己的定义而不是添加依赖项,我不会担心重复。
当然,您可以将类似的模式应用于Either或任何其他类型,其中您可能希望封装特定的模式匹配模式,以便不必每次都完整地重复它。
另一种看待这个问题的方法是记住MaybeTraversableFoldable; Joseph Sible的答案涵盖了这一点,使用traverse_for_可能会更好(或者如果你想要信息更丰富的特殊情况名称,可以定义whenJust = for_),但这个答案希望能帮助你思考这个问题的更一般的情况,以及当组合子不归结为现有的标准函数时,如何创建自己的组合子。

ghg1uchk

ghg1uchk4#

其他答案讨论了解决这一特定任务的实用方法,我对此表示赞同,但我想指出的是,“在Hoogle上查找”是解决许多Haskell问题的实用方法,往往会导致惯用的解决方案,这是一种值得练习的有用技能。
假设你还不知道你要找的函数是for_traverse_(从其他答案中),类型引导的方法通常可以为你指出一个关于Hoogle的好方向,如果有一个函数已经做了你要找的事情,它会是什么类型?
你有一个Maybe a和一个(a -> m b)作为输入,对于ab,和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,这很好地回答了你最初的问题

相关问题