Search code examples
scalapointfree

How to compose functions returning Option with orElse?


Suppose I have two functions foo: Int => Option[Int] and bar: Int => Option[Int] and use them to process a list:

val xs: List[Int] = ...
xs flatMap {x => foo(x) orElse bar(x)}

Can I write xs flatMap (foo orElse bar) instead of {x => foo(x) orElse bar(x)} ?
I can use a 3d party library.


Solution

  • You may use the combine operation (|+|) of the Semigroup typeclass from scalaz.

    The default semigroup of Option uses the semigroup of the content and combines the values if both values are present. So to imitate the orElse behaviour, you would need to wrap the Options in the Tags.FirstVal tag.

    The documentation for Tags.FirstVal states:

    Type tag to choose a scalaz.Semigroup instance that selects the first operand to append.

    You can use .subst and .unsubst methods of the Tag, to wrap and unwrap a type T from inside some F[T]. It also seems to me that you'd have to help Scala a bit with type inference.

    All in all the combined function looks like this:

    type F[T] = Int => Option[T]
    val f = Tags.FirstVal.unsubst(
      Tags.FirstVal.subst[F, Int](foo) |+| 
      Tags.FirstVal.subst[F, Int](bar))
    

    And to use it with flatMap, you have to use the implicit conversion from to Option to List in some way. So the flatMap call may look like this:

    xs flatMap (f(_))
    

    You can also make the |+| work as orElse if you override the implicit Monoid instance for Option:

    import scalaz._, Scalaz._
    implicit val inst: Monoid[Option[Int]] = 
      Tags.First.unsubst(scalaz.std.option.optionFirst[Int])
    
    val foo: Int => Option[Int] = x => if (x % 2 == 0) Some(x) else None
    val bar: Int => Option[Int] = x => if (x % 3 == 0) Some(x) else None
    val xs = List(1, 2, 3, 4, 5, 6, 7, 8)
    
    xs.flatMap((foo |+| bar)(_))