Scala具有定义
的特征Iterable[A]
def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): Iterable[B]
肯定看起来就像monad上的绑定函数一样,文档暗示它是monad,但有两个反对意见,一个是小问题,一个是主要的:
GenTraversableOnce
。我认为这只是一种在评判monad-ness时可以忽略的便利。这些问题会破坏集合的单一性吗?
答案 0 :(得分:19)
" major"关注更容易回答:不,它没有,因为它不是它意味着什么。 monad不需要具有任何特定的"值"或者没有,只是以特定的方式构成功能。
对于"未成年人"一,你是关心类型的权利。正确地说,monad是一个monoid(有一些额外的约束),这意味着它是一个具有某些操作的集合。据我所知,这一组的元素是A => M[B]
类型的东西(在scalaz中这种类型被称为Kleisli
); flatMap
是幺半群的|+|
操作。
Scala中所有可能 A => Iterable[B]
的集合是否构成了与此操作相关的幺半群(以及合适的身份选择)?不,非常不是,因为有很多可能的A => Iterable[B]
违反了monad法律。对于一个简单的例子,{a: A => throw new RuntimeException()}
。更严重的例子是如果Set
链中存在flatMap
,则可能会破坏关联性:假设我们有:
f: String => Iterable[String] = {s => List(s)}
g: String => Iterable[String] = {s => Set(s)}
h: String => Iterable[String] = {s => List("hi", "hi")}
然后
((f |+| g) |+| h).apply("hi") = List("hi") flatMap h = List("hi", "hi")
但
(f |+| (g |+| h)).apply("hi") = List("hi") flatMap {s => Set("hi")} = List("hi")
令人沮丧,因为幺半群的重点在于我们可以写f |+| g |+| h
而不用担心我们评估它的方式。回到monad,重点是我们应该能够写
for {
a <- f("hi")
b <- g(a)
c <- h(b)
} yield c
并且不用担心flatMap
组成的顺序。但对于上面的f
,g
和h
,您希望上述哪个答案代码给? (我知道答案,但这很令人惊讶)。使用真正的monad,问题不会出现,除非作为scala编译器实现细节,因为答案将是相同的。
另一方面, 的特定子集可能是A => M[B]
,例如&#34;在scalazzi safe subset of scala&#34;下实施的所有A => List[B]
的集合形成了与flatMap
定义相关的monad?是(至少对于两个scala函数相等时的普遍接受的定义)。并且有几个子集适用于此。但我认为scala Iterable
s 一般在flatMap
下形成一个monad并不完全正确。
答案 1 :(得分:13)
标题问题的答案是 否 。使用flatMap
的集合不足以成为monad。如果它满足某些其他条件,可能可能是monad。
你的未成年人&#34;问题肯定打破了Iterable
的一元性(#34; monad-ness&#34;的正确用语)。这是因为Iterable
和GenTraversableOnce
的许多子类型都不是monad 。因此,Iterable
不是monad。
你的&#34;专业&#34;问题根本不是问题。例如,List
monad flatMap
的函数参数一次接收List
个元素。列表的每个元素都会生成一个完整的结果列表,这些列表最后都会连接在一起。
幸运的是,判断某些东西是否是monad非常简单!我们只需要知道 monad 的精确定义。
F[_]
,它接受一个类型参数。例如,F
可以是List
,Function0
,Option
等。A
的值并生成类型为F[A]
的值。A => F[B]
的函数和类型B => F[C]
的函数的操作,并生成类型A => F[C]
的复合函数。(还有其他方法可以说明这一点,但我觉得这个提法很容易解释)
考虑Iterable
的这些问题。它绝对需要一种类型的参数。它在函数Iterable(_)
中有一个单位。虽然它的flatMap
操作并不严格符合,但我们当然可以写:
def unit[A](a: A): Iterable[A] = Iterable(a)
def compose[A,B,C](f: A => Iterable[B],
g: B => Iterable[C]): A => Iterable[C] =
a => f(a).flatMap(g)
但这并不能使它成为一个单子,因为monad还必须满足某些法则:
compose(compose(f, g), h)
= compose(f, compose(g, h))
compose(unit, f)
= f
= compose(f, unit)
打破这些法律as lmm has already pointed out的简单方法是在这些表达式中将Set
和List
混合为Iterable
。
虽然只有flatMap
(而不是unit
)的类型构造不是monad,但它可能会形成所谓的 Kleisli半群。这些要求与 monad 的要求相同,除非没有unit
操作且没有身份法。
(关于术语的说明:monad形成 Kleisli类别,半群是没有身份的类别。)
Scala的for-comprehensions在技术上甚至比半群的更少要求(仅map
和flatMap
操作遵守 no 法律) 。但是将它们与至少不是半群的东西一起使用会产生非常奇怪和令人惊讶的效果。例如,这意味着您无法在for-comprehension中内联定义。如果你有
val p = for {
x <- foo
y <- bar
} yield x + y
foo
的定义是
val foo = for {
a <- baz
b <- qux
} yield a * b
除非关联法律成立,否则我们不能依赖于能够将其重写为:
val p = for {
a <- baz
b <- qux
y <- bar
} yield a * b + y
不能做这种替换是非常违反直觉的。因此,大多数时候,当我们使用for-understanding时,我们假设我们在monad中工作(可能即使我们不知道这一点),或者至少是Kleisli半群。
但请注意,这种替换对Iterable
一般不起作用:
scala> val bar: Iterable[Int] = List(1,2,3)
bar: Iterable[Int] = List(1, 2, 3)
scala> val baz: Iterable[Int] = Set(1,2,3)
baz: Iterable[Int] = Set(1, 2, 3)
scala> val qux: Iterable[Int] = List(1,1)
qux: Iterable[Int] = List(1, 1)
scala> val foo = for {
| x <- bar
| y <- baz
| } yield x * y
foo: Iterable[Int] = List(1, 2, 3, 2, 4, 6, 3, 6, 9)
scala> for {
| x <- foo
| y <- qux
| } yield x + y
res0: Iterable[Int] = List(2, 2, 3, 3, 4, 4, 3, 3, 5, 5, 7, 7, 4, 4, 7, 7, 10, 10)
scala> for {
| x <- bar
| y <- baz
| z <- qux
| } yield x * y + z
res1: Iterable[Int] = List(2, 3, 4, 3, 5, 7, 4, 7, 10)
有关Scala中monad的更多信息,包括的含义以及我们应该关注的原因,我建议您查看chapter 11 of my book.
答案 2 :(得分:1)
在核心Scala的上下文中回答你的问题,不包括Scalaz和类别理论,虽然核心Scala没有名为&#34; Monad&#34;的特征,类或对象,但它确实实现了面向对象的概念monad,我将作为Orderskian monad引用,因为它主要是由Martin Ordersky(和Adrian Moors根据http://igstan.ro/posts/2012-08-23-scala-s-flatmap-is-not-haskell-s.html)发明和实现的。
Orderskian monad至少需要map,flatmap和withFilter函数,如&#34; Scala编程&#34; (2Ed:PDF版本:第23章:第531页)作者:Martin Odersky他指出&#34;因此,map,flatMap和withFilter可以看作是monad功能概念的面向对象版本。&#34;基于此,Scala Collections是Orderskian monads。
要回答你的问题,包括Scalaz,它需要一个scalaz.Monad实现来扩展Monad特性并实现两个抽象函数pure和bind,以满足三个需要它们的法则(http://scalaz.github.io/scalaz/scalaz-2.9.1-6.0.2/doc/index.html#scalaz.Monad)。 Core Scala集合不符合这些要求,所以没有什么能够打破他们的scalaz.Monad-ness因为它从未存在过。在某种程度上scalaz.Monad模型类别理论monad,这个论点适用于后者。
答案 3 :(得分:1)
我认为带有flatMap的集合不一定是monad。它不一定适合monad laws。这些法律可能在Functional Programming in Scala中比我能做的更好地解释。
最近,我从一位同事那里听到了一个简单而实用的解释(有自我意识)解释Scala中的monad:something you can put in a for comprehension
。
我不是monad专家,但在我看来这不是真的,所以它适用于flatMap的集合。最明显的例子是Scala lib Either
,因为它不是正确的biaised并且它没有任何flatMap方法,直到你将它投影到一边(并且这个投影不是monadic,因为它返回Either)。据我所知,一个类型不是monad(或monoid或其他),但是一个类型可能有一个monad(甚至很多?不确定但是会对任何例子感兴趣(但也许是一个好的)一个?))。
我认为Scala是一种实用语言,有时可以忘记严格的规则并帮助程序员更轻松地完成工作。并非所有的程序员都关心monad是什么,但很多人可能想在某个时候展平List[Set[Int]]
并且flatMap可能会帮助他们。
这让我想起了Future type is considered as copointed for tests这篇博客文章。
答案 4 :(得分:0)
Scala集合方法flatMap和flatten比monadic flatMap / flatten更强大。见这里:https://www.slideshare.net/pjschwarz/scala-collection-methods-flatmap-and-flatten-are-more-powerful-than-monadic-flatmap-and-flatten