zip
方法,但使用该方法可能会导致bug,例如: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
不应该支持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)
可以说这不是Set
上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
类型所尊重(对称性、传递性和自反性显然得到满足),因此替换是唯一可能引起问题的地方。
对我来说,替换似乎是Eq
类的一个非常理想的属性。另一方面,在最近Reddit讨论中,一些评论包括:
"替换看起来比必要强,基本上等价于将类型商区分,对使用类型的每个函数都有要求。"
--godofpumpkins
"我也不希望替换/同余,因为有许多合法的值可以相等,但在某种程度上是可区分的。"
--sclv
"替换仅适用于结构相等,但没有任何东西坚持
Eq
是结构性的。"--edwardkmett
这三个人在Haskell社区中都很出名,因此我不愿违背他们,并坚持要求我的Eq
类型具有可替换性!
另一个反对Set
作为Functor
的论点是——被广泛认为Functor
允许您转换“集合”的“元素”,同时保留其形状。例如,Haskell wiki上的这句话(请注意,Traversable
是Functor
的一般化):
"在
Foldable
给您穿过结构处理元素但抛弃形状的能力时,Traversable
允许您保留形状并放入新值。""
Traversable
是关于完全保留结构的。"
以及在Real World Haskell中
"...[A] functor must preserve shape. The structure of a collection should not be affected by a functor; only the values that it contains should change."
显然,任何Set
的函子实例都有可能改变形状,从而减少集合中的元素数量。
但是似乎Set
确实应该是函子(暂不考虑Ord
要求——我认为这是我们希望有效地使用集合而强加的人为限制,而不是任何集合的绝对要求。例如,函数集合是一个完全合理的事情要考虑。无论如何,Oleg已经展示如何编写既不需要Ord
约束的有效Functor和Monad实例,适用于Set
)。只是太多了好的使用方法(非现存Monad
实例同样如此)。
有谁能澄清这一混乱?
map
操作的类型集合”。这就是Functor
的全部含义(当然,还包括一些规则)。 - Chris TayloraddOne
函数应用于每个Int
”。我怀疑Scala开发人员中最大的一部分并不会从Monad和某些抽象上下文的计算流程方面考虑flatMap/bind
,它只是调用函数,并从List[List[A]]
生成List[A]
。 - user1078671Set
的map
不是Functor
的map
! - tibbeSet
是一个函子,作用于对象为具有合理Eq
/Ord
实例的类型子范畴(这里的“合理”包括可替换性)。 - Daniel Wagner