12

(+)とは;(++)の単なる特殊化です。mappend私は正しいですか?なぜ必要なのですか?Haskell にはこれらの強力な型クラスと型推論があるため、これは無駄な重複です。視覚的な利便性とタイピングの向上のために、削除(+)(++)て名前を変更するとしましょう。mappend (+)コーディングは、初心者にとってより直感的で、短く、理解しやすいものになります。

--old and new
1 + 2
--result
3

--old
"Hello" ++ " " ++ "World"
--new
"Hello" + " " + "World"
--result
"Hello World"

--old
Just [1, 2, 3] `mappend` Just [4..6]
--new
Just [1, 2, 3] + Just [4..6]
--result
Just [1, 2, 3, 4, 5, 6]

(それは私に夢を与えます。)Haskell のような抽象化を主張する美しい言語にとって、3 つ、あるいはそれ以上の関数が同じことを行うのは良いことではありません。モナドでもfmap同じ種類の繰り返しを見た. Haskell 委員会はこれについて何か計画を立てていますか? それはいくつかのコードを壊すだろうが、確かではないが、大きな変更を加えた次のバージョンがあると聞いた.これは素晴らしい機会だ. 残念すぎる… せめてフォークくらいは買えるの?map(.)liftMmapMforMfmap

編集(*)私が読んだ回答では、数字の場合、または(+)に収まる可能性が あるという事実がありますmappend。実際、それは!(*)の一部であるべきだと思います。Monoid見て:

現在、関数memptyと を忘れて、mconcatしかありませんmappend

class Monoid m where
    mappend :: m -> m -> m

しかし、私たちはそれを行うことができます:

class Monoid m where
    mappend :: m -> m -> m
    mmultiply :: m -> m -> m

次のように動作します (おそらく、まだ十分に理解していません)。

3 * 3
mempty + 3 + 3 + 3
0 + 3 + 3 + 3
9

Just 3 * Just 4
Just (3 * 4)
Just (3 + 3 + 3 +3)
Just 12

[1, 2, 3] * [10, 20, 30]
[1 * 10, 2 * 10, 3 * 10, ...]
[10, 20, 30, 20, 40, 60, ...]

実際には、'mmultiply' は 'mappend' に関してのみ定義されるため、インスタンスのMonoid場合は再定義する必要はありません! 次にMonoid、数学に近づきます。クラスに(-)andを追加することもできます。これが機能する場合、関数の重複と同様に and のケースも解決すると思います:に(/)なり、new is just . 基本的に、「プルアップ」を使用してコードをリファクタリングすることをお勧めします。ああ、新しいforも必要です。これらの演算子をクラスで抽象化し、次のように定義できます。SumProductmappend(+)mmultiply(*)mempty(*)MonoidOperatorMonoid

class (Monoid m) => MonoidOperator mo m where
    mempty :: m
    mappend :: m -> m -> m

instance MonoidOperator (+) m where
    mempty = 0
    mappend = --definition of (+)

instance MonoidOperator (*) where
    --...

class Monoid m where
    -...

これを行う方法はまだわかりませんが、これらすべてに対するクールな解決策があると思います。

4

4 に答える 4

11

ここでは、多少異なる概念を混在させようとしています。

算術演算とリスト連結は、非常に実用的な直接操作です。あなたが書く場合:

[1, 2] ++ [3, 4]

...[1, 2, 3, 4]結果として得られることを知っています。


モノイドは、より抽象的なレベルにある数学的代数概念です。つまり、mappend文字通り「これをそれに追加する」という意味である必要はありません。それは他の多くの意味を持つことができます。あなたが書くとき:

[1, 2] `mappend` [3, 4]

...これらは、その操作が生成する有効な結果の一部です。

[1, 2, 3, 4] -- concatenation, mempty is []

[4, 6]       -- vector addition with truncation, mempty is [0,0..]

[3, 6, 4, 8] -- some inner product, mempty is [1]

[3, 4, 6, 8] -- the cartesian product, mempty is [1]

[3, 4, 1, 2] -- flipped concatenation, mempty is []

[]           -- treating lists like `Maybe a`, and letting lists that
             -- begin with positive numbers be `Just`s and other lists
             -- be `Nothing`s, mempty is []

mappendfor リストが単にリストを連結するのはなぜですか? それは、Haskell Report を書いた人たちがデフォルトの実装として選んだモノイドの定義にすぎないからです。おそらく、リストのすべての要素型に意味があるからです。実際、リストをさまざまな newtype でラップすることにより、代替の Monoid インスタンスをリストに使用できます。たとえば、リストに対してデカルト積を実行する代替モノイド インスタンスがあります。

「モノイド」の概念は、固定された意味と数学の長い歴史を持っており、Haskell でその定義を変更することは、数学的な概念から逸脱することを意味し、あってはなりません。モノイドは、単に空の要素と (文字通りの) 追加/連結操作を記述したものではありません。これは、Monoid が提供するインターフェースに準拠する幅広い概念の基礎です。


あなたが探している概念は、数値に固有のものです (たとえば、mmultiplyまたは多分mproduce/のようなものを定義できなかったため)、既に存在し、数学ではセミリングと呼ばれる概念です(まあ、あなたは本当にカバーしていませんでしたあなたの質問には結合性がありますが、とにかくあなたの例では異なる概念の間をジャンプしています.結合性に固執することもあれば、そうでないこともあります.しかし、一般的な考え方は同じです.mproductMaybe a

Haskell には、たとえばalgebraパッケージ内にセミリングの実装が既にあります。

ただし、モノイドは一般にセミリングではなく、特に足し算や掛け算以外にも実数に対するセミリングの実装が複数あります。Monoid のような非常に明確に定義された型クラスに、広く一般化された追加を追加することは、単に「きれいになる」または「いくつかのキーストロークを節約できる」という理由だけで行うべきではありません。とを別々の概念として持つ(++)のには理由があります。なぜなら、それらはまったく異なる計算上のアイデアを表しているからです。(+)mappend

于 2012-06-09T14:57:39.130 に答える
9

mappend の名前を(+)/に変更する場合(*)

(+)(*)はどちらもモノイドですが、2 つの操作に関連する追加の分配法則と、0*x = 0 などの相殺法則があります。基本的に、(+)(*)を形成します。他の型の 2 つのモノイドは、これらの環 (またはさらに弱い半環) の特性を満たさない場合があります。演算子の命名は、追加の (相互に関連する) プロパティを示唆しています。したがって、これらの名前が保持されない可能性のある追加のプロパティを示唆しているため、またはに名前を変更することによって、伝統的な数学的直観を覆すことは避けたいと思います。オーバーロードが多すぎる (つまり、一般化が多すぎる) と、直感が失われ、ユーザビリティが失われることがあります。(+)(*)mappend+*

ある種の環を形成する 2 つのモノイドがある場合Num、名前 " +" と " *" が追加のプロパティを示唆しているため、これらから のインスタンスを派生させたいと思うかもしれません。

(++) と mappend の混同について

に名前mappendを変更する(++)と、追加の精神的な荷物が少なくなるため、より適切な場合があり(++)ます。実際、リストはフリー モノイド(つまり、追加のプロパティを持たないモノイド) であるため、従来のリスト連結演算子(++)を使用してモノイドの 2 項演算を表すことは、悪い考えには思えません。

1 つの型に対して複数のモノイドを定義する場合

ご指摘のとおり、どちら(+)もモノイドですが、両方を同じ型(*)のインスタンスにすることはできません。あなたが半分提供している1つの解決策は、2つのモノイドを区別するためにクラスに追加の型パラメーターを持つことです。型クラスは、質問に示されているように式ではなく、型によってのみパラメーター化できることに注意してください。適切な定義は次のようになります。MonoidtMonoid

class Monoid m variant where
 mappend :: variant -> m -> m -> m
 mempty :: variant -> m

data Plus = Plus
data Times = Times

instance Monoid Int Plus where
    mappend Plus x y = x `intPlus` y
    mempty = 0

instance Monoid Int Times where
    mappend Times x y = x `intTimes` y
    mempty = 1

(+) = mappend Plus
(*) = mappend Times 

mappend/の適用がmempty特定の操作に解決されるためには、それぞれが特定のモノイド「バリアント」を示す型を示す値を取る必要があります。

余談ですが、質問のタイトルにはmconcat. これは、自由モノイドから他のモノイドへのモノイド準同型、つまり、 consを mappend に、mappendnilmempty に置き換える、とはまったく異なる操作です。mconcat

于 2012-06-09T15:07:04.760 に答える
4

Hackage を見てみると、これらの問題修正することを目的とした多くの 代替の Prelude 実装 が見つかります。

于 2012-06-09T15:02:07.173 に答える
3

数値には 2 つのモノイドがありProductますSum

Haskell のような抽象化を主張する美しい言語にとって、3 つ、あるいはそれ以上の関数が同じことを行うのは良いことではありません。

抽象化は、コードの重複を排除することではありません。算術演算とモノイド演算は 2 つの異なる概念であり、同じセマンティクスを共有している場合でも、それらをマージしても何も得られません。

于 2012-06-09T13:48:57.147 に答える