Сравнение примеров Haskell и Scala Bind/Flatmap

Следующий код bind(>>=) в Haskell не компилируется:

ghci> [[1]] >>= Just
<interactive>:38:11:
    Couldn't match type ‘Maybe’ with ‘[]’
    Expected type: [t] -> [[t]]
      Actual type: [t] -> Maybe [t]
    In the second argument of ‘(>>=)’, namely ‘Just’
    In the expression: [[1]] >>= Just

Но в Scala он действительно компилируется и запускается:

scala> List( List(1) ).flatMap(x => Some(x) )
res1: List[List[Int]] = List(List(1))

Подпись Haskell >>=:

>>= :: Monad m => m a -> (a -> m b) -> m b

Итак, в [[1]] >>= f, f тип должен быть: a -> [b].

Почему код Scala компилируется?

Ответы

Ответ 1

Как объяснил @chi Scala flatMap, более общий, чем Haskell >>=. Полная подпись из документов Scala:

final def flatMap[B, That](f: (A) ⇒ GenTraversableOnce[B])(implicit bf: CanBuildFrom[List[A], B, That]): That

Это неявно не относится к этой конкретной проблеме, поэтому мы могли бы использовать более простое определение:

final def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): List[B]

Есть только одна проблема, Option не является подклассом GenTraversableOnce, здесь происходит неявное преобразование. Scala определяет неявное преобразование из Option в Iterable, которое является подклассом Traversable который является подклассом GenTraversableOnce.

implicit def option2Iterable[A](xo: Option[A]): Iterable[A]   

Неявный определяется в объекте-компаньоне Option.

Более простой способ увидеть неявное на работе - назначить Option Iterable val:

scala> val i:Iterable[Int] = Some(1)
i: Iterable[Int] = List(1)

Scala использует некоторые правила по умолчанию, чтобы выбрать List как реализацию Iterable.

Тот факт, что вы можете комбинировать разные подтипы TraversableOnce с операциями монады, поступает из implicit class MonadOps:

  implicit class MonadOps[+A](trav: TraversableOnce[A]) {
    def map[B](f: A => B): TraversableOnce[B] = trav.toIterator map f
    def flatMap[B](f: A => GenTraversableOnce[B]): TraversableOnce[B] = trav.toIterator flatMap f
    def withFilter(p: A => Boolean) = trav.toIterator filter p
    def filter(p: A => Boolean): TraversableOnce[A] = withFilter(p)
  }

Это увеличивает каждый TraversableOnce с помощью приведенных выше методов. Подтипы могут свободно определять более эффективные версии там, они будут скрывать неявные определения. Это относится к List.

Ответ 2

Цитата из ссылки Scala для List

final def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): List[B] 

Таким образом, flatMap является более общим, чем Haskell (>>=), поскольку для создания только проходящего типа требуется только отображаемая функция f, не обязательно List.