web-dev-qa-db-ja.com

セット、ファンクター、方程式の混乱

最近、Setsについての議論が始まりました。SetsはScala Zipメソッドをサポートし、これがどのようにバグにつながる可能性があるかを示しています。

scala> val words = Set("one", "two", "three")
scala> words Zip (words map (_.length))
res1: Set[(Java.lang.String, Int)] = Set((one,3), (two,5))

要素が順序付けられていないため、SetsがZip操作をサポートしないことはかなり明らかだと思います。ただし、問題はSetが実際にはファンクターではなく、mapメソッドを持つべきではないということであることが示唆されました。確かに、セットをマッピングすることでトラブルに巻き込まれる可能性があります。今すぐHaskellに切り替えて、

data AlwaysEqual a = Wrap { unWrap :: a }

instance Eq (AlwaysEqual a) where
    _ == _ = True

instance Ord (AlwaysEqual a) where
    compare _ _ = EQ

そして今ghciで

ghci> import Data.Set as Set
ghci> let nums = Set.fromList [1, 2, 3]
ghci> Set.map unWrap $ Set.map Wrap $ nums
fromList [3]
ghci> Set.map (unWrap . Wrap) nums
fromList [1, 2, 3]

したがって、Setはファンクターの法則を満たしていません

    fmap f . fmap g = fmap (f . g)

これは、mapsでのSet操作の失敗ではなく、定義したEqインスタンスの失敗であると主張できます。これは、置換法、つまり、AとBのEqの2つのインスタンスとマッピングf : A -> Bその後

    if x == y (on A) then f x == f y (on B)

これはAlwaysEqualには当てはまりません(例:f = unWrap)。

置換法は、私たちが尊重しようとすべきEq型の賢明な法ですか?確かに、他の平等法則は私たちのAlwaysEqual型によって尊重されているので(対称性、推移性、反射性は簡単に満たされます)、問題が発生する可能性があるのは置換だけです。

私には、置換はEqクラスにとって非常に望ましいプロパティのように思えます。一方、 最近のRedditディスカッション に関するいくつかのコメントには次のものが含まれます

「置換は必要以上に強力であるように思われ、基本的に型を引用し、型を使用するすべての関数に要件を課します。」

-godofpumpkins

「また、私たちが同等にしたいが、どういうわけか区別できる値の正当な使用法がたくさんあるので、私は本当に置換/合同を望んでいません。」

-sclv

「置換は構造的平等のためだけに成り立つが、Eqが構造的であると主張するものは何もない。」

-edwardkmett

これら3つはすべてHaskellコミュニティでかなりよく知られているので、私はそれらに反対し、私のEqタイプの代替可能性を主張することを躊躇します!

SetFunctorであることに対する別の議論-Functorであることにより、形状を維持しながら「コレクション」の「要素」を変換できることが広く受け入れられています。たとえば、Haskell wikiのこの引用(TraversableFunctorの一般化であることに注意してください)

Foldableを使用すると、要素を処理する構造を通過できますが、形状を破棄できますが、Traversableを使用すると、形状を保持したり、新しい値を入力したりしながら、それを実行できます。 「」

Traversableは、構造をそのまま維持することです。」

そして実世界のハスケルでは

「... [A]ファンクターは形状を保持する必要があります。コレクションの構造はファンクターの影響を受けないようにする必要があります。コレクションに含まれる値のみを変更する必要があります。」

明らかに、Setのファンクターインスタンスは、セット内の要素の数を減らすことにより、形状を変更する可能性があります。

しかし、Setsは本当にファンクターである必要があるようです(現時点ではOrd要件を無視します-絶対的な要件ではなく、セットを効率的に操作したいという私たちの願望によって課せられた人為的な制限としてたとえば、関数のセットは考慮すべき完全に賢明なものです。いずれにせよ、Oleg 示されていますSetの効率的なFunctorおよびMonadインスタンスを作成する方法Ord制約が必要です)。それらのNiceの用途は多すぎます(存在しないMonadインスタンスについても同じことが言えます)。

誰かがこの混乱を片付けることができますか? SetFunctorである必要がありますか?もしそうなら、ファンクターの法則を破る可能性についてどうしますか? Eqの法則はどうあるべきか、そしてそれらはFunctorおよび特にSetインスタンスの法則とどのように相互作用しますか?

58
Chris Taylor

SetFunctorであることに対する別の議論-Functorであることにより、形状を維持しながら「コレクション」の「要素」を変換できることが広く受け入れられています。 [...]明らかに、Setのファンクターインスタンスは、セット内の要素の数を減らすことで、形状を変更する可能性があります。

これは、そうでない場合の定義条件として「形状」のアナロジーをとる場合であると私は恐れています。数学的に言えば、べき集合ファンクターのようなものがあります。 ウィキペディアから

べき集合:べき集合関数P:Set→Setはそれぞれをマップしますべき集合に設定し、各関数f:X→Yをマップに設定します。マップはU⊆Xをそのイメージに送信しますf(U)⊆Y。

関数P(f)(べき集合ファンクターの_fmap f_)は、引数セットのサイズを保持しませんが、それでもこれはファンクターです。

よく考えられていない直感的なアナロジーが必要な場合は、次のように言うことができます。リストのような構造では、各要素は他の要素との関係を「気にし」、偽のファンクターがその関係を壊した場合は「気分を害します」 。しかし、セットは限定的なケースです。要素が互いに無関心である構造であるため、それらを「怒らせる」ためにできることはほとんどありません。唯一のことは、偽のファンクターが、その要素を含むセットを、その「音声」を含まない結果にマップする場合です。

(わかりました、今すぐ黙ります...)


編集:回答の先頭であなたを引用したときに、次のビットを切り捨てました:

たとえば、Haskell wikiのこの引用(TraversableFunctorの一般化であることに注意してください)

Foldableを使用すると、要素を処理する構造を通過できますが、形状を破棄できますが、Traversableを使用すると、形状を保持したり、新しい値を入力したりしながら、それを実行できます。 「」

Traversableは、構造をそのまま維持することです。」

Traversableは一種のspecializedFunctorであり、その「一般化」ではないことに注意してください。 Traversable(または実際にはFoldableが拡張するTraversableについて)に関する重要な事実の1つは、任意の構造の要素が線形順序である必要があるということです。任意のTraversableをその要素のリストに変換できます(_Foldable.toList_を使用)。

Traversableに関するもう1つのあまり明白ではない事実は、次の関数が存在することです( Gibbons&Oliveira、 "The Essence of the Iterator Pattern" から採用):

_-- | A "shape" is a Traversable structure with "no content," 
-- i.e., () at all locations.
type Shape t = t ()

-- | "Contents" without a shape are lists of elements.
type Contents a = [a]

shape :: Traversable t => t a -> Shape t
shape = fmap (const ())

contents :: Traversable t => t a -> Contents a
contents = Foldable.toList

-- | This function reconstructs any Traversable from its Shape and
-- Contents.  Law:
--
-- > reassemble (shape xs) (contents xs) == Just xs
--
-- See Gibbons & Oliveira for implementation.  Or do it as an exercise.
-- Hint: use the State monad...
--
reassemble :: Traversable t => Shape t -> Contents a -> Maybe (t a)
_

セットのTraversableインスタンスは、提案された法則に違反します。これは、空でないすべてのセットが同じShapeContents[()]であるセット)を持つためです。 。このことから、セットをreassembleしようとすると、空のセットまたはシングルトンしか戻らないことを簡単に証明できます。

レッスン? Traversableは、Functorよりも非常に具体的で強力な意味で「形状を保持」します。

28
Luis Casillas

セットは、Haskのサブカテゴリのファンクター(Functorではない)であり、Eqは「Nice」です(つまり、合同、置換、保持されるサブカテゴリ)。制約の種類がway backからの場合、おそらく設定はある種のFunctorになります。

8
J. Abrahamson

ええと、Setは共変ファンクターとして、そして逆変ファンクターとして扱うことができます。通常、それは共変ファンクターです。そして、それが平等に関して振る舞うためには、実装が何であれ、それが行うことを確認する必要があります。

Set.Zipに関して-それはナンセンスです。 Set.headと同様に(Scalaにあります)。存在すべきではありません。

2
Vlad Patryshev