The answer to your main question is no . Collection with flatMap not enough for the monad. It can be a monad if it satisfies some additional conditions.
Your "minor" problem certainly violates monadicity (the correct word for "monad") Iterable . This is because many subtypes of Iterable and GenTraversableOnce are not monads. Therefore, Iterable not a monad.
Your "main" problem is not a problem at all. For example, the argument to the List monad flatMap flatMap takes List elements one at a time. Each list item generates a list of results, and these lists are combined together at the end.
Fortunately, it’s really easy to judge whether something is a monad! We just need to know the exact definition of the monad.
Monad Requirements
- The monad must be a constructor of type
F[_] , which takes one type argument. For example, F could be List , Function0 , Option , etc. - Monadic unit. This is a function that takes a value of any type
A and returns a value of type F[A] . - Operation monadic composition. This is an operation that performs a function of type
A => F[B] and a function of type B => F[C] and creates a composite function of type A => F[C] .
(There are other ways to state this, but I find this wording understandable)
Consider them for Iterable . It definitely takes one type argument. It has a sort function in the Iterable(_) function. And although its flatMap operation flatMap not strictly matched, we could write:
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)
But this does not make it a monad, since the monad must additionally satisfy certain laws:
- Associativity:
compose(compose(f, g), h) = compose(f, compose(g, h)) - Identity:
compose(unit, f) = F = compose(f, unit)
An easy way to break these laws, as lmm already pointed out , is to mix Set and List as Iterable in these expressions.
"Semimonads"
While a type construct with just flatMap (not unit ) is not a monad, it can form what is called a Clisley semigroup. The requirements are the same as for the monad, except without the unit operation and without the law of identity.
(Note on terminology: the monad forms the Claysley category, and the semigroup forms the category without identities.)
For-insights
Scala for concepts technically have even fewer requirements than flatMap (just map and flatMap operations that do not obey any laws). But using them with things that are not at least semi-groupoids has very strange and amazing effects. For example, this means that you cannot embed definitions for understanding. If you
val p = for { x <- foo y <- bar } yield x + y
And the definition of foo was
val foo = for { a <- baz b <- qux } yield a * b
If the law of associativity is not satisfied, we cannot rely on the ability to rewrite this as:
val p = for { a <- baz b <- qux y <- bar } yield a * b + y
The inability to make such a replacement is extremely incompatible. Therefore, most of the time when we work with understanding, we assume that we work in a monad (probably even if we don’t know this) or at least a semi-group Kleisley.
But note that this kind of lookup doesn’t work at all for 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)
For more information about monads
For more information on monads in Scala, including everything that it means and why we should care, I recommend that you take a look at Chapter 11 of my book.