2016-03-03 5 views
5

Ich möchte meine eigenen zum Verständnis kompatiblen Monaden und Funktoren in Scala implementieren.Wie kann man seine eigene scala monad verstehen?

Nehmen wir zwei dumme Monaden als Beispiel. Eine Monade ist eine Zustands-Monade, die ein "Int" enthält, das Sie abbilden oder abbilden können.

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

Eine andere Monade nimmt funktioniert Zusammensetzung wie so ...

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

Das Beispiel einige Fehler haben, aber Sie bekommen die Idee.

Ich möchte in der Lage sein, diese zwei verschiedenen Arten von erfundenen Monaden innerhalb eines Verständnisses in Scala zu verwenden. Wie folgt aus:

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

Ich bin kein Scala Meister, aber Sie bekommen die Idee

Antwort

9

Für einen Typ innerhalb eines für Verständnis verwendet werden, die Sie wirklich brauchen nur map und flatMap Methoden für sie definieren die Instanzen desselben Typs zurückgeben. Syntaktisch wird das For-Comprehension vom Compiler in eine Reihe von flatMaps umgewandelt, gefolgt von einem abschließenden map für das yield. Solange diese Methoden mit der entsprechenden Signatur verfügbar sind, wird es funktionieren.

Ich bin nicht wirklich sicher, was Sie mit Ihren Beispielen sind nach, aber hier ist ein triviales Beispiel, die Option entsprechen:

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 
} 

Ich habe ein gemeinsames Merkmal mit zwei Untertypen (I könnte aber so viele haben wie ich wollte. Das gemeinsame Merkmal MaybeInt erzwingt, dass jeder Untertyp der Schnittstelle map/flatMap entspricht.

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) 

Zusätzlich können Sie foreach und filter hinzufügen. Wenn Sie diese (keine Ausbeute) auch handhaben wollen:

for(a <- maybe) println(a) 

würden Sie foreach hinzufügen. Und wenn Sie if Wachen verwenden möchten:

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

Sie würden filter oder withFilter benötigen.

Ein vollständiges Beispiel:

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

Wäre es besser, die Scallaz Monad Art zu verwenden, anstatt Ihre eigenen definieren? –

+0

'filter' api ist auch erforderlich, um die Monad-Elemente zu" entpacken ". Wenn Sie zum Beispiel ein MaybeAB verwenden, das dem MaybeInt ähnlich ist, aber für eine 'Case-Klasse AB (a: Int, b: Int)', muss der Filter 'for (AB (a, b) <- maybAB) tun ... ' –

Verwandte Themen