最近出现了一个关于 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)
可以说这不是对s的map
操作失败,而是我们定义的实例的失败,因为它不遵守替换定律,即对于A 和 B 的两个实例以及一个映射thenSet
Eq
Eq
f : A -> B
if x == y (on A) then f x == f y (on B)
这不适用于AlwaysEqual
(例如考虑f = unWrap
)。
Eq
对于我们应该尊重的类型,替代法是否是一个明智的法律?当然,我们的AlwaysEqual
类型尊重其他平等法则(对称性、传递性和自反性都得到了微不足道的满足),所以替换是我们唯一可能遇到麻烦的地方。
对我来说,替代似乎是Eq
班级非常理想的属性。另一方面,对最近 Reddit 讨论的一些评论包括
“替换似乎比必要的要强,基本上是对类型进行商化,对使用类型的每个函数提出要求。”
——南瓜神
“我也真的不想要替代/一致,因为我们想要等同但在某种程度上可以区分的价值观有许多合法用途。”
--sclv _
“替代只适用于结构平等,但没有什么坚持
Eq
是结构性的。”——爱德华克梅特
这三个在 Haskell 社区中都很有名,所以我会犹豫反对他们并坚持我的Eq
类型的可替代性!
反对Set
成为 a 的另一个论点Functor
- 人们普遍认为,成为 aFunctor
允许您在保留形状的同时转换“集合”的“元素”。例如,Haskell wiki 上的这句话(注意这Traversable
是对 的概括Functor
)
“Where
Foldable
使您能够通过结构处理元素但丢弃形状,Traversable
允许您在保留形状的同时做到这一点,例如,将新值放入其中。”“
Traversable
是关于保持原样的结构。”
在现实世界中的 Haskell
“...[A] 函子必须保持形状。集合的结构不应受到函子的影响;只有它包含的值应该改变。”
显然,任何函子实例Set
都有可能通过减少集合中的元素数量来改变形状。
但似乎Set
s 真的应该是函子(Ord
暂时忽略要求 - 我认为这是我们希望有效处理集合的人为限制,而不是任何集合的绝对要求。例如,函数集是一个完全明智的考虑。无论如何,Oleg已经展示了如何为Set
不需要Ord
约束的情况编写高效的 Functor 和 Monad 实例)。它们有太多好的用途(对于不存在的Monad
实例也是如此)。
任何人都可以清理这个烂摊子吗?应该Set
是一个Functor
?如果是这样,如何处理违反函子定律的可能性?法律应该是什么Eq
,它们如何与法律特别Functor
是Set
实例相互作用?