Сделайте произвольный класс в Scala как экземпляр монады

Чтобы сделать что-либо работоспособным в контексте монады, если использовать Haskell - я просто добавляю реализацию класса Monad для заданного типа в любом месте. Поэтому я вообще не касаюсь источника определения типа данных. Как (что-то искусственное)

data Z a = MyZLeft a | MyZRight a

swap (MyZLeft x) = MyZRight x
swap (MyZRight x) = MyZLeft x

instance Monad Z where
  return a = MyZRight a
  (>>=) x f = case x of
                MyZLeft s -> swap (f s)
                MyZRight s -> swap (f s)

поэтому я не касаюсь определения Z, но сделаю его монадой

Как это сделать в Scala? Кажется, что нет возможности помимо смешения некоторых признаков и определения методов map/flatMap/filter/withFilter?

Ответы

Ответ 1

Взгляните на scalaz:

// You could use implementation in the end of this answer instead of this import
import scalaz._, Scalaz._

sealed trait Z[T]
case class MyZLeft[T](t: T) extends Z[T]
case class MyZRight[T](t: T) extends Z[T]

def swap[T](z: Z[T]) = z match {
  case MyZLeft(t) => MyZRight(t)
  case MyZRight(t) => MyZLeft(t)
}

implicit object ZIsMonad extends Monad[Z] {
  def point[A](a: => A): Z[A] = MyZRight(a)
  def bind[A, B](fa: Z[A])(f: A => Z[B]): Z[B] = fa match {
    case MyZLeft(t) => swap(f(t))
    case MyZRight(t) => swap(f(t))
  }
}

Использование:

val z = 1.point[Z]
// Z[Int] = MyZRight(1)

z map { _ + 2 }
// Z[Int] = MyZLeft(3)

z >>= { i => MyZLeft(i + "abc") }
// Z[String] = MyZRight(1abc)

z >>= { i => (i + "abc").point[Z] }
// Z[String] = MyZLeft(1abc)

for-comprehensions (похож на do-notation):

for {
  i <- z
  j <- (i + 1).point[Z]
  k = i + j
} yield i * j * k
// Z[Int] = MyZRight(6)

См. также Scalaz cheatsheet и Изучение скаляса.

В scalaz нет волшебства - вы можете реализовать это без scalaz.

Связано: Typeclases в Scala и Haskell.

Простейшая реализация Monad с синтаксисом в случае, если вы не хотите использовать scalaz:

import scala.language.higherKinds

trait Monad[M[_]] {
  def point[A](a: => A): M[A]
  def bind[A, B](fa: M[A])(f: A => M[B]): M[B]
}

implicit class MonadPointer[A](a: A) {
  def point[M[_]: Monad] = implicitly[Monad[M]].point(a)
}

implicit class MonadWrapper[M[_]: Monad, A](t: M[A]) {
  private def m = implicitly[Monad[M]]
  def flatMap[B](f: A => M[B]): M[B] = m.bind(t)(f)
  def >>=[B](f: A => M[B]): M[B] = flatMap(f)
  def map[B](f: A => B): M[B] = m.bind(t)(a => m.point(f(a)))
  def flatten[B](implicit f: A => M[B]) = m.bind(t)(f)
}

Ответ 2

Чтобы быть монадой, класс scala не требуется расширять определенный класс или смешивать определенный признак. Это просто нужно

  • - тип, параметризованный по типу (SomeClass[T])
  • реализовать метод "unit" (может фактически использовать любое имя метода, но часто именуется так, чтобы он соответствовал имени класса monad - c.f. List(x) и Try(doSomething()))
  • реализовать метод flatMap (a.k.a. "bind" ):

    Object SomeClass[T] {
        def SomeClass(t: T): SomeClass[T] = ...
    }
    class SomeClass[T] {
        def flatMap[U](T => SomeClass[U]): SomeClass[U] = ...
    }
    

Это определение с помощью структурного ввода/утиного ввода в отличие от определения через расширение типа.

Кроме того, чтобы технически квалифицироваться как Монада, реализация должна удовлетворять трем монадским законам (где m имеет тип SomeClass[T] и unit = SomeClass[T](t) для некоторого t: T).

  • Закон об идентичности Монады: обязательная монада с единицей оставляет ее неизменной

      m flatMap unit = m flatMap SomeClass(_) = m
    
  • Monad Unit Law: единица привязки с произвольной функцией, такая же, как применение этой функции к единице значения

      unit flatMap f = SomeClass(t) flatMap f = f(t)           (where f: T => Any)
    
  • Закон композиции Монады: связка ассоциативна

      (m flatMap f(_)) flatMap g(_) = m flatMap (t => f(t) flatMap(u => g(u))  
    
      (where f: T => SomeClass[U] and g: U => SomeClass[V] for some U and V)
    

Ссылка: http://james-iry.blogspot.com.au/2007/10/monads-are-elephants-part-3.html


EDIT:

Если вы ищете ярлык для реализации, вы можете определить общий предок, который предоставляет стандартное определение flatMap:

trait Monad[T] {
  def map[U](f: T => U): Monad[U]
  def flatten: Monad[T]
  def flatMap[V](g: T => Monad[V]): Monad[V] = map(g) flatten
}

Но тогда вы должны определить конкретные реализации для map и flatten. Это результат дизайна - есть буквально бесконечные возможности, которые соответствуют этим сигнатурам (т.е. Не могут быть автоматически найдены в эфире и не определяются законами физики;))

Ответ 3

Обратите внимание, что даже вдаваясь в спецификацию реализации кода в Scala или Haskell, я хочу отметить, что у одной вещи есть класс, для которого вы знаете способ добавления единицы и умножения, а другой - когда есть общий случай.

В общем случае единственным решением, которое я знаю, является выброс в свободную монаду F | → 1 + F (1 + F (1 + F (...))). Который может вообще не существовать.

В противном случае вам нужно доказать, что все, что вы вводите в качестве единицы умножения, удовлетворяют законам монады (см. ответ GlenBest.