Search code examples
scalascalazfor-comprehensionoption-typehigher-kinded-types

Scala flattening an Option around a higher kinded type, looking for a more idiomatic approach


Given a higher Kinded Type M, and a monad type class, I can operate on values within M through a for-comprehension. Working with a function that return Options, im looking for a more appropriate way to flatten these options than the solution I have. This is as follows

class Test[M[+_]:Monad](calc:Calculator[M]) {
  import Monad._

  def doSomething(x:Float):M[Option[Float]] = {
    for {
      y:Option[Float] <- calc.divideBy(x)   // divideBy returns M[Option[Float]]
      z:Option[Float] <- y.fold[M[Option[Float]]](implicitly[Monad[M]].point(None))(i => calc.divideBy(i))
    }  yield z
  }
} 

So its the following I'm looking to correct:

y.fold[M[Option[Float]]](implicitly[Monad[M]].point(None))(i => calc.divideBy(i))

Also the case where instead of calling the second divideBy, I call multiplyBy which returns M[Float]

y.fold[M[Option[Float]]](implicitly[Monad[M]].point(None))(i => calc.multipleBy(i).map(Some(_)) 

Maybe this is a case for Monad Transformers but Im not sure how to go about it.


Solution

  • It seems likely that monad transformers can help you here. For example, the following compiles and I think does roughly what you want:

    import scalaz._, Scalaz._
    
    abstract class Calculator[M[_]: Monad] {
      def divideBy(x: Float): M[Option[Float]]
      def multiplyBy(x: Float): M[Float]
    }
    
    class Test[M[_]: Monad](calc: Calculator[M]) {
      def doSomething(x: Float): OptionT[M, Float] = for {
        y <- OptionT(calc.divideBy(x))
        z <- calc.multiplyBy(y).liftM[OptionT]
      } yield z
    }
    

    Now doSomething returns an OptionT[M, Float], which is kind of wrapper for M[Option[Float]] that allows you to work with the contents all the way inside the Option monadically. To get back an M[Option[Float]] from the OptionT[M, Float] you can just use the run method.