最近、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))
要素が順序付けられていないため、Set
sが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)
これは、map
sでの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
タイプの代替可能性を主張することを躊躇します!
Set
がFunctor
であることに対する別の議論-Functor
であることにより、形状を維持しながら「コレクション」の「要素」を変換できることが広く受け入れられています。たとえば、Haskell wikiのこの引用(Traversable
はFunctor
の一般化であることに注意してください)
「
Foldable
を使用すると、要素を処理する構造を通過できますが、形状を破棄できますが、Traversable
を使用すると、形状を保持したり、新しい値を入力したりしながら、それを実行できます。 「」「
Traversable
は、構造をそのまま維持することです。」
そして実世界のハスケルでは
「... [A]ファンクターは形状を保持する必要があります。コレクションの構造はファンクターの影響を受けないようにする必要があります。コレクションに含まれる値のみを変更する必要があります。」
明らかに、Set
のファンクターインスタンスは、セット内の要素の数を減らすことにより、形状を変更する可能性があります。
しかし、Set
sは本当にファンクターである必要があるようです(現時点ではOrd
要件を無視します-絶対的な要件ではなく、セットを効率的に操作したいという私たちの願望によって課せられた人為的な制限としてたとえば、関数のセットは考慮すべき完全に賢明なものです。いずれにせよ、Oleg 示されていますSet
の効率的なFunctorおよびMonadインスタンスを作成する方法Ord
制約が必要です)。それらのNiceの用途は多すぎます(存在しないMonad
インスタンスについても同じことが言えます)。
誰かがこの混乱を片付けることができますか? Set
はFunctor
である必要がありますか?もしそうなら、ファンクターの法則を破る可能性についてどうしますか? Eq
の法則はどうあるべきか、そしてそれらはFunctor
および特にSet
インスタンスの法則とどのように相互作用しますか?
Set
がFunctor
であることに対する別の議論-Functor
であることにより、形状を維持しながら「コレクション」の「要素」を変換できることが広く受け入れられています。 [...]明らかに、Setのファンクターインスタンスは、セット内の要素の数を減らすことで、形状を変更する可能性があります。
これは、そうでない場合の定義条件として「形状」のアナロジーをとる場合であると私は恐れています。数学的に言えば、べき集合ファンクターのようなものがあります。 ウィキペディアから :
べき集合:べき集合関数P:Set→Setはそれぞれをマップしますべき集合に設定し、各関数f:X→Yをマップに設定します。マップはU⊆Xをそのイメージに送信しますf(U)⊆Y。
関数P(f)(べき集合ファンクターの_fmap f
_)は、引数セットのサイズを保持しませんが、それでもこれはファンクターです。
よく考えられていない直感的なアナロジーが必要な場合は、次のように言うことができます。リストのような構造では、各要素は他の要素との関係を「気にし」、偽のファンクターがその関係を壊した場合は「気分を害します」 。しかし、セットは限定的なケースです。要素が互いに無関心である構造であるため、それらを「怒らせる」ためにできることはほとんどありません。唯一のことは、偽のファンクターが、その要素を含むセットを、その「音声」を含まない結果にマップする場合です。
(わかりました、今すぐ黙ります...)
編集:回答の先頭であなたを引用したときに、次のビットを切り捨てました:
たとえば、Haskell wikiのこの引用(
Traversable
はFunctor
の一般化であることに注意してください)「
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
インスタンスは、提案された法則に違反します。これは、空でないすべてのセットが同じShape
(Contents
が[()]
であるセット)を持つためです。 。このことから、セットをreassemble
しようとすると、空のセットまたはシングルトンしか戻らないことを簡単に証明できます。
レッスン? Traversable
は、Functor
よりも非常に具体的で強力な意味で「形状を保持」します。
セットは、Haskのサブカテゴリのファンクター(Functor
ではない)であり、Eq
は「Nice」です(つまり、合同、置換、保持されるサブカテゴリ)。制約の種類がway backからの場合、おそらく設定はある種のFunctor
になります。
ええと、Setは共変ファンクターとして、そして逆変ファンクターとして扱うことができます。通常、それは共変ファンクターです。そして、それが平等に関して振る舞うためには、実装が何であれ、それが行うことを確認する必要があります。
Set.Zipに関して-それはナンセンスです。 Set.headと同様に(Scalaにあります)。存在すべきではありません。