【问题标题】:Sets, Functors and Eq confusion集合、函子和 Eq 混淆
【发布时间】:2013-10-11 05:02:25
【问题描述】:

最近出现了一个关于 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 上map 操作的失败,而是我们定义的Eq 实例的失败,因为它不遵守替换法则,即对于 A 和 B 上的两个 Eq 实例和一个映射 f : A -> B 然后

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

不适用于AlwaysEqual(例如考虑f = unWrap)。

对于Eq 类型,我们应该尽量尊重替代法吗?当然,我们的AlwaysEqual 类型尊重其他平等法则(对称性、传递性和自反性得到微不足道的满足),所以替换是我们唯一可能遇到麻烦的地方。

对我来说,substition 似乎是Eq 类的一个非常理想的属性。另一方面,recent Reddit discussion 上的一些 cmets 包括

“替换似乎比必要的要强,基本上是对类型进行商化,对使用该类型的每个函数提出要求。”

-- godofpumpkins

“我也真的不想要替换/全等,因为我们想要等同但在某种程度上可以区分的值有许多合法用途。”

-- sclv

“替换仅适用于结构平等,但没有任何东西坚持 Eq 是结构性的。”

-- edwardkmett

这三个在 Haskell 社区中都很有名,所以我会犹豫反对他们并坚持我的 Eq 类型的可替代性!

另一个反对 SetFunctor 的论点 - 人们普遍认为 Functor 允许您在保留形状的同时转换“集合”的“元素”。例如,Haskell wiki 上的这句话(注意 TraversableFunctor 的概括)

Foldable 使您能够通过结构处理元素但丢弃形状,Traversable 允许您在保留形状的同时做到这一点,例如,放入新值。”

Traversable 是关于保持原样的结构。”

在现实世界中的 Haskell

"...[A] 函子必须保持形状。集合的结构不应受到函子的影响;只有它包含的值应该改变。"

显然,Set 的任何仿函数实例都有可能通过减少集合中的元素数量来改变形状。

但似乎Sets 真的应该是函子(暂时忽略Ord 的要求 - 我认为这是我们希望有效使用集合的人为限制,而不是任何绝对要求例如,函数集是一个非常明智的考虑。无论如何,Oleg has shown 如何为不需要 Ord 约束的 Set 编写高效的 Functor 和 Monad 实例。它们的用途太多了(不存在的Monad 实例也是如此)。

谁能收拾这个烂摊子? Set 应该是 Functor 吗?如果是这样,如何处理违反函子定律的可能性? Eq 的法则应该是什么,它们如何与 FunctorSet 实例的法则相互作用?

【问题讨论】:

  • 也许我错了,但我不认为 Scala 中的集合是函子,实际上 Scala 中没有函子、单子、应用程序和标准中的其他东西图书馆,原因教授。奥德斯基不想拥有它们。也许在 Haskell 中它对语言来说是必不可少的,但在 Scala 中,我认为 Set 只是一个 Set,如果你需要 Functors、Monads 等。使用 Scalaz
  • @levy 而不是“函子”,您可以只阅读“支持map 操作的类型集”。这就是Functor 的全部内容(好吧,再加上一些法律)。
  • @ChrisTaylor 我同意你的观点,但是 Scala 中的 map 和 Haskell 中的 map 背后的想法是不同的。在 Haskell 中,map 与 Functors、Arrows 和所有来自 CatThreory 的数学东西有关,但在 Scala 中,map 只是“将这个 addOne 函数应用到每个 Int”。我怀疑 Scala Devs 的最大部分在 Monads 方面考虑 flatMap/bind 以及对带有上下文的计算流的一些抽象,它只是调用函数并从 List[List[A]] 生成 List[A]
  • Setmap 不是 Functor 映射!
  • 我认为我们可以说Set 是子类别上的函子,其对象是具有健全Eq/Ord 实例的类型(这里的“健全”包括可替代性)。

标签: scala haskell equality functor


【解决方案1】:

另一个反对 SetFunctor 的论点 - 人们普遍认为,作为 Functor 允许您在保留形状的同时转换“集合”的“元素”。 [...] 显然,Set 的任何函子实例都有可能通过减少集合中的元素数量来改变形状。

恐怕这是一个将“形状”类比作为定义条件的情况,而不是。从数学上讲,有幂集函子这样的东西。 From Wikipedia:

幂集: 幂集函子 P : Set → Set 将每个集合映射到其幂集,并将每个函数 f : X → Y 映射到发送 U 的映射⊆ X 到它的图像 f(U) ⊆ Y.

函数 P(f)(幂集仿函数中的fmap f)不保留其参数集的大小,但这仍然是一个仿函数。

如果你想要一个考虑不周的直观类比,我们可以这样说:在一个像列表这样的结构中,每个元素都“关心”它与其他元素的关系,如果假函子这样做,就会被“冒犯”打破这种关系。但是集合是极限情况:一个结构,其元素彼此无关,因此您几乎无法“冒犯”它们;唯一的问题是,如果假函子将包含该元素的集合映射到不包括其“声音”的结果。

(好吧,我现在就闭嘴……)


编辑:当我在答案的顶部引用您时,我截断了以下位:

例如,Haskell wiki 上的这句话(注意 TraversableFunctor 的概括)

Foldable 使您能够通过结构处理元素但丢弃形状,Traversable 允许您在保留形状的同时做到这一点,例如,放入新值。”

Traversable 是关于保持原样的结构。”

这里我想说Traversable 是一种专业 Functor,而不是它的“概括”。关于任何Traversable(或者实际上,关于FoldableTraversable 扩展)的关键事实之一是它要求任何结构的元素具有线性顺序——您可以将任何Traversable 转换为其元素列表(带有Foldable.toList)。

关于Traversable 的另一个不太明显的事实是存在以下函数(改编自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 更具体、更强烈。

【讨论】:

  • 这很有趣,但根据您的回答,SetFunctor 也很有趣? 1.Set和powerset唯一的区别是powerset不使用Eq而是实际相等,保证是可替代的。 2.“Functor保持形状”在函子定律的意义上——它只是将函数应用于元素。正如所观察到的,Set 只是一个假设Eq 的替代性的函子。
【解决方案2】:

Set“只是”一个函子(不是 Functor)来自 Hask 的子类别,其中 Eq 是“nice”(即全等、替换、成立的子类别)。如果约束类型是从回溯,那么 set 可能是某种Functor

【讨论】:

  • 谢谢 - 我认为这是一个很好的看待它的方式。我倾向于过于关注从 Hask 到 Hask 的函子,而忘记了其余的…
  • 至少Contravariant 函子在你的工具箱中很不错,因为它们有一个非常好的 Haskell 表示。不过,子类别是很好的思考工具。
  • 恕我直言,instance SubstitutiveEq e => Functor (Set e) 就足够了,如果 SubstitutiveEq 是一个只有法律的无操作类型类(我在 Haskell 中从未见过)。
  • @Blaisorblade 惊喜!从 7.10 开始,MonadPlus 是一个空类型类(好吧,除了冗余操作,它们只是其等效泛化的别名),它只提供法律:)
【解决方案3】:

好吧,Set 可以被视为协变函子,也可以被视为逆变函子;通常它是一个协变函子。为了让它在平等方面表现得很好,必须确保无论实现什么,它都能做到。

关于 Set.zip - 这是无稽之谈。以及 Set.head (你在 Scala 中有它)。它不应该存在。

【讨论】:

  • Set.head 如果您有一个非空集合并且想要获取该集合的任意元素,那么它实际上很有用,不管哪个。在单元素集的情况下,它是该集的唯一元素。当然,head 这个名字并不是最幸运的。应该叫Set.arbitrary 什么的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-26
  • 2019-07-04
  • 1970-01-01
  • 2012-04-11
  • 1970-01-01
相关资源
最近更新 更多