【问题标题】:Minimum of Two Maybes最少两个可能
【发布时间】:2014-09-10 15:51:25
【问题描述】:

我想获得两个可能值中的最小值,或者如果一个什么都没有,则获得一个非无,或者如果两个输入都没有,则什么都不返回。我可以编写一个简单的函数来做到这一点,但我怀疑有一种方法可以在不编写自定义函数的情况下做到这一点。抱歉,如果这是一个小问题,但有没有比使用这个自定义函数更简单的方法?

minMaybe :: Ord a => Maybe a -> Maybe a -> Maybe a
minMaybe Nothing b = b
minMaybe a Nothing = a
minMaybe (Just a) (Just b) = Just $ min a b

【问题讨论】:

  • 在我被那个猪工投反对票之前 ;) ... 怎么样:let minM a b = maybe a Just $ maybe b Just $ min a b
  • 也许just this
  • n.m,这行得通,但它并不比我开始的简单。

标签: haskell maybe


【解决方案1】:

使用Control.Applicative 中的运算符可以满足规范。

myMin :: Ord x => Maybe x -> Maybe x -> Maybe x
myMin a b = min <$> a <*> b <|> a <|> b

&lt;|&gt;Maybe 在哪里实现“偏好”

Nothing <|> b  = b
a       <|> _  = a

事情是这样的

min <$> Just a <*> Just b = Just (min a b)

但是

min <$> Just a <*> Nothing = Nothing

这导致对这个问题的一些错误答案。使用&lt;|&gt; 可以让您在计算出的min 值可用时更喜欢它,但是当只有一个Just 时,您可以使用任一个人来恢复。

但是你应该问问这样使用Maybe是否合适。除了 Monoid 实例这个不光彩的例外,Maybe 被设置为对容易发生故障的计算进行建模。您在这里拥有的是现有Ord 的扩展,带有“顶部”元素。

data Topped x = Val x | Top deriving (Show, Eq, Ord)

您会发现min for Topped x 正是您所需要的。将类型不仅视为数据的表示,而且将其视为具有结构的数据的设备,这很好。 Nothing 通常代表某种失败,因此最好使用不同的类型。

【讨论】:

  • Topped x 有一个合理的max,但是如果你想添加一个 bottom 元素,那么预期的语义就会改变,那么为什么类型应该保持不变?当然,我们可以做一次“从半群制作幺半群”构造,然后将maxmin 等包装为半群结构。
  • min &lt;$&gt; a &lt;*&gt; b == min a b Maybes。然而,后者看起来确实比“更简单”更“打高尔夫球”......
  • @chi Huh,这意味着如果问题使用max 而不是minmax 将是正确答案。或者换一种说法,Bottomed 就是 Maybe 本身。
  • 我知道我回答这个问题已经很晚了,但这到底是怎么回事。 Hackage 上的 monoid-extras 包提供了 this type(以及 Bottomed 变体)和一些使用它的小实用程序。
【解决方案2】:

您不能为此使用ApplicativeMonad 实例,因为这些上下文中的任何Nothing 都会使您的总结果为Nothing。话虽这么说,“更简单”这个词是高度自以为是的,你的功能很好。

【讨论】:

  • 注意:社区 wiki 因为“更简单”是高度固执己见的。
  • 由于max 像@clay 想要的那样工作(“偏爱” Just values to Nothings),也许如果@clay 使用 Data.Ord.Down 那么他们会有他们想要的行为,而仍然以其他答案想要的方式“更简单”。
【解决方案3】:

您可以使用MaybeAlternative 实例编写它:

minMaybe a b = liftA2 min a b <|> a <|> b

或者,您可以使用maxBound 作为默认值,所以它总是会选择另一个:

minMaybe a b = liftA2 min (d a) (d b)
  where d x = x <|> Just maxBound

但我不建议这样做。

【讨论】:

    【解决方案4】:

    问题是关于提升功能 min :: Ord a ⇒ a → a → a 以使用 Maybes 上下文。它具有关联性,因此 Semigroup 实例完全符合您的要求:

    min' :: forall a. Ord a => Maybe a -> Maybe a -> Maybe a
    min' = coerce ((<>) @(Maybe (Min a)))
    

    这需要ScopedTypeVariablesTypeApplicationscoerce 来自 Data.Coerce。下面给出了更老式的解决方案。但是上面的版本应该更高效:coerce 在运行时不存在。虽然 GHC 可能会消除 fmaps 但不能保证:

    min'' :: Ord a => Maybe a -> Maybe a -> Maybe a
    min'' x y = fmap getMin (fmap Min x <> fmap Min y)
    

    附:我会说你的解决方案很好。

    【讨论】:

      【解决方案5】:

      我认为 radomaj 有一个 good idea

      import Data.Ord (Down (..))
      import Data.Function (on)
      
      minMaybes mx my =
        getDown <$> (max `on` fmap Down) mx my
      getDown (Down x) = x
      

      我们使用max 优先于Just 而不是Nothing,然后使用Down,所以如果两者都是Just,我们实际上会得到最小值。


      这是另一种类似的方法,看起来更简洁。 Maybe 可以看作是一种将额外的最小值 Nothing 附加到任意 Ord 类型的方法。我们可以编写自己的类型来附加最大值:

      data AddMax a = TheMax | Plain a deriving Eq
      instance Ord a => Ord (AddMax a) where
        TheMax <= Plain _ = False
        _ <= TheMax = True
        Plain a <= Plain b = a <= b
      
      maybeToAddMax :: Maybe a -> AddMax a
      maybeToAddMax = maybe TheMax Plain
      
      addMaxToMaybe :: AddMax a -> Maybe a
      addMaxToMaybe TheMax = Nothing
      addMaxToMaybe (Plain a) = Just a
      

      现在你可以写了

      minMaybes mx my = addMaxToMaybe $
        (min `on` maybeToAddMax) mx my
      

      你也可以耍点花招:

      {-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
      import Data.Ord
      import Data.Function
      import Data.Coerce
      
      newtype AddMax a = AddMax {unAddMax :: Down (Maybe (Down a))}
        deriving (Eq, Ord)
      

      现在

      minMaybes :: forall a. Ord a => Maybe a -> Maybe a -> Maybe a
      minMaybes = coerce (min @(AddMax a))
      

      【讨论】:

      • 这打破了关注点分离原则——涉及携带数据比较的外部结构(或IOW,不能仅使用min更改为maxMaybes)。 Down 也翻转了等于的选择.....
      • @WillNess,您可以通过加倍来修复 equals 的选择:将其包裹在外部和内部。我同意分离关注点很糟糕。
      • 我尝试了 double-Down,但无法使其工作......我所拥有的:data X a = X (a,a) 仅按第一个元素比较对;然后max [Down $ X (1,2)] [Down $ X (1,3)] 返回[Down (X (1,3))]。使用Down 两次不会改变这一点(如max (Down [Down $ X (1,2)]) (Down [Down $ X (1,3)]))。这应该怎么做?
      • @WillNess,双下,你使用min
      • 谢谢。出于某种原因,max (X (1,2)) (X (1,3)) 返回X (1,3),这就是让我失望的原因。无论如何,整个可区分的 equals 东西在纯度上都不太好。
      猜你喜欢
      • 2017-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-21
      • 2011-01-28
      • 2018-09-29
      • 2016-01-25
      • 2018-05-12
      相关资源
      最近更新 更多