8

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

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
4

5 回答 5

21

可以使用来自 的运算符来满足规范Control.Applicative

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

<|>forMaybe实现“偏好”的地方

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

事情是

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

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

这导致对这个问题的一些错误答案。Using<|>允许您在计算值可用时更喜欢计算min值,但在只有一个时使用任一个人恢复Just

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

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

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

于 2014-09-10T16:17:01.213 回答
3

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

于 2014-09-10T16:02:19.503 回答
3

您可以使用以下Alternative实例编写它Maybe

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

但我不建议这样做。

于 2014-09-10T16:06:57.820 回答
2

问题是关于提升功能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)

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

于 2018-07-15T21:19:39.997 回答
0

我认为 radomaj 有一个好主意

import Data.Ord (Down (..))
import Data.Function (on)

minMaybes mx my =
  getDown <$> (max `on` fmap Down) mx my
getDown (Down x) = x

我们max习惯喜欢JustNothing然后使用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))
于 2018-07-16T02:23:55.607 回答