2016-03-03 26 views
5

Voglio implementare le mie monade e funtori compatibili per la comprensione in Scala.Come rendere la scala monad conforme alla propria comprensione?

Prendiamo due stupide monadi come esempio. Una monade è una monade di stato che contiene un "Int" che puoi mappare o mappare.

val maybe = IntMonad(5) 
maybe flatMap(a => 3 * (a map (() => 2 * a))) 
// returns IntMonad(30) 

Un altro monade si fa composizione funzione in questo modo ...

val func = FunctionMonad(() => println("foo")) 
val fooBar = func map (() => println("bar")) 
fooBar() 
// foo 
// bar 
// returns Unit 

L'esempio può avere alcuni errori, ma si ottiene l'idea.

Voglio essere in grado di utilizzare questi due diversi tipi di Monade composte all'interno di una comprensione per Scala. Come questo:

val myMonad = IntMonad(5) 
for { 
    a <- myMonad 
    b <- a*2 
    c <- IntMonad(b*2) 
} yield c  
// returns IntMonad(20) 

io non sono un maestro Scala, ma si ottiene l'idea

risposta

9

Per un tipo da utilizzare all'interno di un per-comprensione, è davvero solo bisogno di definire map e flatMap metodi per esso che restituiscono istanze dello stesso tipo. Sintatticamente, la per-comprensione viene trasformata dal compilatore in una serie di flatMap s seguita da un finale map per il yield. Finché questi metodi sono disponibili con la firma appropriata, funzionerà.

Io non sono davvero sicuro di quello che stai dopo con i tuoi esempi, ma qui è un esempio banale che è equivalente a Option:

sealed trait MaybeInt { 
    def map(f: Int => Int): MaybeInt 
    def flatMap(f: Int => MaybeInt): MaybeInt 
} 

case class SomeInt(i: Int) extends MaybeInt { 
    def map(f: Int => Int): MaybeInt = SomeInt(f(i)) 
    def flatMap(f: Int => MaybeInt): MaybeInt = f(i) 
} 

case object NoInt extends MaybeInt { 
    def map(f: Int => Int): MaybeInt = NoInt 
    def flatMap(f: Int => MaybeInt): MaybeInt = NoInt 
} 

Ho un tratto comune con due sottotipi (I potrebbe avere quante ne ho voluto, però). Il tratto comune MaybeInt impone che ciascun sottotipo sia conforme all'interfaccia map/flatMap.

scala> val maybe = SomeInt(1) 
maybe: SomeInt = SomeInt(1) 

scala> val no = NoInt 
no: NoInt.type = NoInt 

for { 
    a <- maybe 
    b <- no 
} yield a + b 

res10: MaybeInt = NoInt 

for { 
    a <- maybe 
    b <- maybe 
} yield a + b 

res12: MaybeInt = SomeInt(2) 

Inoltre, è possibile aggiungere foreach e filter. Se si desidera gestire anche questo (non resa):

for(a <- maybe) println(a) 

Si potrebbe aggiungere foreach. E se si desidera utilizzare if guardie:

for(a <- maybe if a > 2) yield a 

Si avrebbe bisogno filter o withFilter.

Un esempio completo:

sealed trait MaybeInt { self => 
    def map(f: Int => Int): MaybeInt 
    def flatMap(f: Int => MaybeInt): MaybeInt 
    def filter(f: Int => Boolean): MaybeInt 
    def foreach[U](f: Int => U): Unit 
    def withFilter(p: Int => Boolean): WithFilter = new WithFilter(p) 

    // Based on Option#withFilter 
    class WithFilter(p: Int => Boolean) { 
     def map(f: Int => Int): MaybeInt = self filter p map f 
     def flatMap(f: Int => MaybeInt): MaybeInt = self filter p flatMap f 
     def foreach[U](f: Int => U): Unit = self filter p foreach f 
     def withFilter(q: Int => Boolean): WithFilter = new WithFilter(x => p(x) && q(x)) 
    } 
} 

case class SomeInt(i: Int) extends MaybeInt { 
    def map(f: Int => Int): MaybeInt = SomeInt(f(i)) 
    def flatMap(f: Int => MaybeInt): MaybeInt = f(i) 
    def filter(f: Int => Boolean): MaybeInt = if(f(i)) this else NoInt 
    def foreach[U](f: Int => U): Unit = f(i) 
} 

case object NoInt extends MaybeInt { 
    def map(f: Int => Int): MaybeInt = NoInt 
    def flatMap(f: Int => MaybeInt): MaybeInt = NoInt 
    def filter(f: Int => Boolean): MaybeInt = NoInt 
    def foreach[U](f: Int => U): Unit =() 
} 
+0

sarebbe meglio utilizzare il tipo di Scallaz Monade invece di definire il proprio? –

+0

'filter' api richiede anche di" decomprimere "gli elementi monad. Ad esempio se si utilizza un MaybeAB simile a MaybeInt ma per un 'caso di classe AB (a: Int, b: Int)', il filtro è richiesto per fare 'for (AB (a, b) <- maybAB) ... ' –