How to make your own for-comprehension compliant scala monad?

后端 未结 1 494
天涯浪人
天涯浪人 2021-01-02 04:37

I want to implement my own for-comprehension compatible monads and functors in Scala.

Let\'s take two stupid monads as an example. One monad is a state monad that co

相关标签:
1条回答
  • 2021-01-02 05:10

    For a type to be used within a for-comprehension, you really only need to define map and flatMap methods for it that return instances of the same type. Syntactically, the for-comprehension is transformed by the compiler into a series of flatMaps followed by a final map for the yield. As long as these methods are available with the appropriate signature, it will work.

    I'm not really sure what you're after with your examples, but here is a trivial example that is equivalent to 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
    }
    

    I have a common trait with two sub-types (I could have as many as I wanted, though). The common trait MaybeInt enforces each sub-type to conform to the map/flatMap interface.

    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)
    

    Additionally, you can add foreach and filter. If you want to also handle this (no yield):

    for(a <- maybe) println(a)
    

    You would add foreach. And if you want to use if guards:

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

    You would need filter or withFilter.

    A full example:

    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 讨论(0)
提交回复
热议问题