"find minimum" 函数的另一个 Maybe

Another Maybe for "find minimum" functions

当我们搜索“最大”值而不方便查找最小值时,也许有用

-- update maximum could be used as "lambda"
updateMaximum :: (Ord a) => Maybe a -> Maybe a -> Maybe a
updateMaximum saved new = max new saved
    
-- update minimum could't be used as "lambda"
updateMinimum Nothing new = new
updateMinimum saved Nothing = saved
updateMininum saved new = min new saved

理想的解决方案是 standard monad 和另一个构造函数顺序,但我没有找到它:

data Maybe' a = Just' a | Notheing'
-- all standard functions implementations:
safeHaad' :: [a] -> Maybe' a
...

所以问题是:haskell 中编写“updateMinimum”函数的标准方法是什么?

P.S。 有两种可能的解决方案,但各有其缺点:

可以暂时用Down换行,倒序,取反序下的max,待会再展开:

import Data.Ord

updateMaximum :: Ord a => Maybe a -> Maybe a -> Maybe a
updateMaximum x y = max x y

updateMinimum :: Ord a => Maybe a -> Maybe a -> Maybe a
updateMinimum x y = getDown <$> max (Down <$> x) (Down <$> y)

注意微妙的部分:我们在 Maybe 下换行,因此即使我们颠倒顺序,Nothing 仍然是最小值。这使得 max 做正确的事。

一些测试证实这可以按需工作。下面,前两个值是测试输入,第三个是输出。

max: (Nothing,Nothing,Nothing)
min: (Nothing,Nothing,Nothing)
max: (Nothing,Just 1,Just 1)
min: (Nothing,Just 1,Just 1)
max: (Nothing,Just 2,Just 2)
min: (Nothing,Just 2,Just 2)
max: (Just 1,Nothing,Just 1)
min: (Just 1,Nothing,Just 1)
max: (Just 1,Just 1,Just 1)
min: (Just 1,Just 1,Just 1)
max: (Just 1,Just 2,Just 2)
min: (Just 1,Just 2,Just 1)
max: (Just 2,Nothing,Just 2)
min: (Just 2,Nothing,Just 2)
max: (Just 2,Just 1,Just 2)
min: (Just 2,Just 1,Just 1)
max: (Just 2,Just 2,Just 2)
min: (Just 2,Just 2,Just 2)

完整代码:

module Main where

import Data.Foldable
import Data.Ord

updateMaximum :: Ord a => Maybe a -> Maybe a -> Maybe a
updateMaximum x y = max x y

updateMinimum :: Ord a => Maybe a -> Maybe a -> Maybe a
updateMinimum x y = getDown <$> max (Down <$> x) (Down <$> y)

main :: IO ()
main = do
    let xs = [ Nothing, Just 1, Just 2 ] :: [Maybe Int]
    for_ xs $ \x ->
      for_ xs $ \y -> do
        putStr "max: "
        print (x, y, updateMaximum x y)
        putStr "min: "
        print (x, y, updateMinimum x y)
    pure ()

您并不是真的希望 Nothing 成为此处排序的一部分,而是在您进行二元运算时“被忽略”,或者换句话说,就像一个身份一样。这是 Maybe 的 Monoid 行为,我们可以使用 Semigroup 实例 Min 和 Max。

updateMinimum x y = fmap getMin (fmap Min x <> fmap Min y)
updateMaximum x y = fmap getMax (fmap Max x <> fmap Max y)

point-free 版本并没有真正的改进,但它看起来像这样:

updateMinimum = (fmap getMin .) . ((<>) `on` fmap Min)

的基础上什至有一个更简洁的解决方案,它仍然是无意义的,但我感觉更清楚一些,因为它隐藏了 . 的复杂双重使用到 ad-hoc来自模块 Data.Composition 的运算符 .::

import Data.Function (on)
import Data.Semigroup
import Data.Composition ((.:))
-- (.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d

updateMinimum :: (Ord a) => Maybe a -> Maybe a -> Maybe a
updateMinimum = fmap getMin .: ((<>) `on` fmap Min)

其中 updateMinimum 的定义对此数据流进行编码

                               ┌───────────┐
                              ┌┤fmap getMin├─ first input
        ┌───────────┐  ┌────┐ │└───────────┘
output ─┤fmap getMin├──┤(<>)├─┤
        └───────────┘  └────┘ │┌───────────┐
                              └┤fmap getMin├─ second input
                               └───────────┘

monoid-extras 包提供 Inf 数据类型,具有变体 PosInfNegInf,正是为了这个目的。您的两个函数如下所示:

updateMaximum :: Ord a => NegInf a -> NegInf a -> NegInf a
updateMaximum = max

updateMinimum :: Ord a => PosInf a -> PosInf a -> PosInf a
updateMinimum = min

我什至懒得给他们起名字。