2013-08-23 16 views
7

Mam monadę bardzo podobną do monady kolekcji. Obecnie próbuję zaimplementować do niego transformator monadowy, ale zawodzę.Jak zaimplementować transformator monadowy `List` w Scali?

Sprawdziłem implementację ListT w Scalaz 6 i 7, ale nie mogę zrozumieć, jak to działa. Używa pewnego dodatkowego typu Step, którego cel jest dla mnie niejasny.

Czy ktoś może mi wyjaśnić, jak zaimplementować listę transformatorów monad, wyjaśniając podejście Scalaz lub korzystając z innej implementacji?

+1

Wersja 7.x nie używa 'Step', wydaje się bardzo prosta. https://github.com/scalaz/scalaz/blob/v7.0.3/core/src/main/scala/scalaz/ListT.scala – huynhjl

+0

@huynhjl Tak, byłem pewien, że widziałem 7 implementację z 'Step', choć . – ziggystar

Odpowiedz

17

Nie jestem do końca pewien, co oznacza krok w skalowaniu, ale wdrożenie ListT jest całkiem proste. W zależności od tego, ile operacji chcesz nałożyć, może to być trochę pracy, ale podstawowe operacje monady mogą być realizowane w następujący sposób.

Najpierw musimy typeclasses dla monady i funktora (możemy również dodać aplikacyjnych, ale nie jest to konieczne dla tego przykładu):

trait Functor[F[_]] { 
    def map[A,B](fa: F[A])(f: A => B): F[B] 
} 

trait Monad[F[_]] extends Functor[F] { 
    def flatMap[A,B](fa: F[A])(f: A => F[B]): F[B] 
    def pure[A](x: A): F[A] 
} 

object Monad { 

    implicit object ListMonad extends Monad[List] { 
    def map[A,B](fa: List[A])(f: A => B) = fa map f 
    def flatMap[A,B](fa: List[A])(f: A => List[B]) = fa flatMap f 
    def pure[A](x: A) = x :: Nil 
    } 

    implicit object OptionMonad extends Monad[Option] { 
    def map[A,B](fa: Option[A])(f: A => B) = fa map f 
    def flatMap[A,B](fa: Option[A])(f: A => Option[B]) = fa flatMap f 
    def pure[A](x: A) = Some(x) 
    } 

    def apply[F[_] : Monad]: Monad[F] = implicitly[Monad[F]] 

} 

Gdy mamy tych, możemy stworzyć transformator, który w zasadzie tylko opakowuje F[List[A]] i przekazuje połączenie do swojej funkcji map i flatMap do listy, wywołując map na funcie zawierającym, a następnie wywołując map lub flatMap odp. na zawartych List/s.

final case class ListT[F[_] : Monad, A](fa: F[List[A]]) { 
    def map[B](f: A => B) = ListT(Monad[F].map(fa)(_ map f)) 

    def flatMap[B](f: A => ListT[F, B]) = ListT(Monad[F].flatMap(fa) { _ match { 
    case Nil => Monad[F].pure(List[B]()) 
    case list => list.map(f).reduce(_ ++ _).run 
    }}) 

    def ++(that: ListT[F,A]) = ListT(Monad[F].flatMap(fa) { list1 => 
    Monad[F].map(that.run)(list1 ++ _) 
    }) 

    def run = fa 
} 

Gdy skończymy z modyfikacji, możemy uzyskać obiekt wynikowy przez wywołanie metody run na obiekcie ListT. Jeśli chcesz, możesz także dodać inne operacje specyficzne dla listy, takie jak w skalaz. To powinno być całkiem proste. Na przykład :: mógłby wyglądać następująco:

def ::(x: A) = ListT(Monad[F].map(fa)(x :: _)) 

Zastosowanie:

scala> ListT(Option(List(1,2,3))) 
res6: ListT[Option,Int] = ListT(Some(List(1, 2, 3))) 

scala> res6.map(_+45) 
res7: ListT[Option,Int] = ListT(Some(List(46, 47, 48))) 

scala> 13 :: res7 
res8: ListT[Option,Int] = ListT(Some(List(13, 46, 47, 48))) 

scala> res8.run 
res10: Option[List[Int]] = Some(List(13, 46, 47, 48))