2016-03-03 46 views
5

我想在Scala中实现我自己的理解兼容monad和函子。如何使自己的理解符合斯卡拉monad?

让我们以两个愚蠢monad为例。一个monad是一个状态monad,它包含一个可以映射或平面映射的“Int”。

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

另一个单子需要做函数组合是这样的...

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

的例子可能有一些错误,但你的想法。

我希望能够在Scala中使用这两种不同类型的Monad组成Monad。就像这样:

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

我不是Scala的大师,但你的想法

回答

9

对于要内换理解,你真的只需要定义mapflatMap方法,它使用的类型返回相同类型的实例。在语法上,编译器将for-comprehension转换为一系列flatMap s,接着是yield的最后一个map。只要这些方法具有适当的签名,它就可以工作。

我真的不知道你有你的例子后在做什么,但这里是一个简单的例子,它等效于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 
} 

我有两个子类型(我一个共同的特点虽然可以有我想要的尽可能多)。共同特征MaybeInt强制每个子类型符合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) 

此外,您可以添加foreachfilter。如果你也想解决这个(没有收益率):

for(a <- maybe) println(a) 

你会添加foreach。如果你想使用if后卫:

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

您需要filterwithFilter

完整的例子:

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

它会更好,而不是使用Scallaz单子类型定义你自己的? –

+0

'filter' api也需要“解包”单子物品。例如,如果您使用与MaybeInt类似的MaybeAB,但对于'case class AB(a:Int,b:Int)',则需要使用filter来执行for(AB(a,b)< - maybAB)... ' –