2

我需要一个选择非空幺半群的函数。对于列表,这将意味着以下行为:

> [1] `mor` []
[1]
> [1] `mor` [2]
[1]
> [] `mor` [2]
[2]

现在,我实际上已经实现了它,但我想知道是否存在一些标准的替代方案,因为它似乎是一种常见的情况。不幸的是,Hoogle 没有帮助。

这是我的实现:

mor :: (Eq a, Monoid a) => a -> a -> a
mor a b = if a /= mempty then a else b
4

3 回答 3

4

如果您的列表最多包含一个元素,则它们与 同构Maybe,并且为此存在“第一个非空”幺半群:Firstfrom Data.Monoid。它是值的包装器Maybe a,并mappend返回第一个Just值:

import Data.Monoid
main = do
    print $ (First $ Just 'a') <> (First $ Just 'b')
    print $ (First $ Just 'a') <> (First Nothing)
    print $ (First Nothing)    <> (First $ Just 'b')
    print $ (First Nothing)    <> (First Nothing :: First Char)

==> Output:
First {getFirst = Just 'a'}
First {getFirst = Just 'a'}
First {getFirst = Just 'b'}
First {getFirst = Nothing}

转换[a] -> Maybe a是使用Data.Maybe.listToMaybe.

附带说明:这个不限制包装类型的类型类;在您的问题中,您需要一个Eq实例来比较与mempty. Maybe当然,这是以拥有类型为代价的。

于 2012-11-28T23:03:10.090 回答
3

[这真的是一个很长的评论而不是一个答案]

在我的评论中,当我说“幺半群事物没有自省的概念”时——我的意思是你不能对幺半群执行分析(模式匹配、相等、<、> 等)。这当然是显而易见的——Monoids 的 API 只是一个单元(空的)和一个操作mappend(更抽象地说是 <>),它接受两个单调的东西并返回一个。为一个类型定义mappend是免费的用例分析,但之后你可以对 monoidal 做的所有事情就是使用 Monoid API。

在 Haskell 社区中,避免发明事物是一种民间传说,而是更喜欢使用数学和计算机科学(包括函数式编程历史)中的对象。结合 Eq(需要分析 is 参数)和 Monoid 引入了一类新事物 - 支持足够内省以允许相等的 monoid;在这一点上,有一个合理的论点是,一个 Eq-Monoid 的东西违背了它的 Monoid 超类的精神(Monoids 是不透明的)。由于这既是一类新的对象,也可能存在争议——标准实现将不存在。

于 2012-11-29T18:36:20.237 回答
2

首先,您的mor函数看起来相当可疑,因为它需要 aMonoid但从不使用mappend,因此它的约束比必要的要大得多。

mor :: (Eq a, Monoid a) => a -> a -> a
mor a b = if a /= mempty then a else b

Default你可以只用一个约束来完成同样的事情:

import Data.Default

mor :: (Eq a, Default a) => a -> a -> a
mor a b = if a /= def then a else b

我认为任何使用 ofDefault应该谨慎看待,因为我相信许多 Haskeler 抱怨,这是一个没有原则的类。

我的第二个想法是,您在这里真正处理的数据类型似乎是Maybe (NonEmpty a),不是[a],而Monoid您实际上正在谈论的是First

import Data.Monoid

morMaybe :: Maybe a -> Maybe a -> Maybe a
morMaybe x y = getFirst (First x <> First y)

然后我们可以将它与列表一起使用,就像在你的例子中一样,在and(nonEmpty, maybe [] toList)之间的同构下:[a]Maybe (NonEmpty a)

import Data.List.NonEmpty

morList :: [t] -> [t] -> [t]
morList x y = maybe [] toList (nonEmpty x `mor` nonEmpty y)
λ> mor'list [1] []
[1]

λ> mor'list [] [2]
[2]

λ> mor'list [1] [2]
[1]

(我确信更熟悉镜头库的人可以在这里提供更令人印象深刻的简洁演示,但我不立即知道如何。)

可以使用谓词进行扩展Monoid以测试元素是否为身份。

class Monoid a => TestableMonoid a
  where
    isMempty :: a -> Bool

    morTestable :: a -> a -> a
    morTestable x y = if isMempty x then y else x

不是每个幺半群都可以有 的实例TestableMonoid,但很多(包括列表)可以。

instance TestableMonoid [a]
  where
    isMempty = null

我们甚至可以写一个新类型的包装器Monoid

newtype Mor a = Mor { unMor :: a }

instance TestableMonoid a => Monoid (Mor a)
  where
    mempty = Mor mempty
    Mor x `mappend` Mor y = Mor (morTestable x y)
λ> unMor (Mor [1] <> Mor [])
[1]

λ> unMor (Mor [] <> Mor [2])
[2]

λ> unMor (Mor [1] <> Mor [2])
[1]

所以这就留下了这个TestableMonoid类是否应该存在的问题。它肯定看起来比 更“代数合法”的类Default,至少,因为我们可以给它一个与它相关的定律Monoid

  • isEmpty x当且当mappend x = id

但我确实质疑这是否真的有任何常见的用例。正如我之前所说,Monoid 约束对于您的用例来说是多余的,因为您永远不会mappend. 所以我们应该问,我们是否可以设想一种情况,在这种情况下,一个人可能同时需要 mappendisMempty因此对TestableMonoid约束有合理的需要?我可能在这里目光短浅,但我无法想象一个案例。

我认为这是因为 Stephen Tetley 在说这“违背了 Monoid 的精神”时提到了一些东西。mappend用稍有不同的括号将你的头转向类型签名:

mappend :: a -> (a -> a)

mappend是从集合成员a到函数的映射a -> a。幺半群是一种将视为这些值的函数的方式。a 幺半群是一个只有通过这些函数让我们看到的窗口才能看到的世界的视图。功能在它们让我们看到的方面非常有限。我们对它们所做的唯一事情就是应用它们。我们从不询问函数的任何其他内容(正如斯蒂芬所说,我们没有自省进入他们)。因此,虽然,是的,你可以将任何你想要的东西固定到子类上,但在这种情况下,我们要固定的东西在性质上与我们正在扩展的基类有很大不同,而且感觉不太可能在函数的用例和具有普遍相等性或谓词的事物的用例,如isMempty.

所以最后我想回到简单而精确的方式来写这个:在值级别编写代码并停止担心类。你不需要Monoid也不需要Eq,你只需要一个额外的参数:

morSimple :: (t -> Bool) -- ^ Determine whether a value should be discarded
          -> t -> t -> t
morSimple f x y = if f x then y else x
λ> morSimple null [1] []
[1]

λ> morSimple null [1] [2]
[1]

λ> morSimple null [] [2]
[2]
于 2017-09-30T05:12:03.633 回答