Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting a tuple of options to an option of tuple with Scalaz or Shapeless

Having

(Some(1), Some(2))

I expect to get

Some((1, 2))

and having

(Some(1), None)

I expect to get

None
like image 314
Nikita Volkov Avatar asked Sep 13 '12 14:09

Nikita Volkov


5 Answers

I realize you're asking about Scalaz, but it's worth pointing out that the standard method is not unbearably wordy:

val x = (Some(1), Some(2))

for (a <- x._1; b <-x._2) yield (a,b)

In the general case (e.g. arbitrary-arity tuples), Shapeless is best at this sort of thing.

like image 90
Rex Kerr Avatar answered Oct 05 '22 06:10

Rex Kerr


You can use the fact that Scalaz 7 provides a Bitraverse instance for tuples and then sequence as usual (but with bisequence instead of sequence):

scala> import scalaz._, std.option._, std.tuple._, syntax.bitraverse._
import scalaz._
import std.option._
import std.tuple._
import syntax.bitraverse._

scala> val p: (Option[Int], Option[String]) = (Some(1), Some("a"))
p: (Option[Int], Option[String]) = (Some(1),Some(a))

scala> p.bisequence[Option, Int, String]
res0: Option[(Int, String)] = Some((1,a))

Unfortunately Scalaz 7 currently needs the type annotation here.


In a comment Yo Eight states that the type annotation will remain mandatory here. I'm not sure what his or her reasoning is, but it's in fact perfectly easy to write your own wrapper that will provide any appropriately typed tuple with a bisequence method and won't require a type annotation:

import scalaz._, std.option._, std.tuple._    

class BisequenceWrapper[F[_, _]: Bitraverse, G[_]: Applicative, A, B](
  v: F[G[A], G[B]]
) {
  def bisequence = implicitly[Bitraverse[F]].bisequence(v)
}

implicit def bisequenceWrap[F[_, _]: Bitraverse, G[_]: Applicative, A, B](
  v: F[G[A], G[B]]
) = new BisequenceWrapper(v)

Now (some(1), some("a")).bisequence will compile just fine.

I can't think of a good reason Scalaz wouldn't include something like this. Whether or not you want to add it in the meantime is a matter of taste, but there's definitely no theoretical obstacle to letting the compiler do the typing here.

like image 35
Travis Brown Avatar answered Oct 05 '22 05:10

Travis Brown


I think that cats version will not be redundant here.

@ import cats.implicits._
import cats.implicits._

@ (4.some, 2.some).bisequence
res1: Option[(Int, Int)] = Some((4, 2))

@ (4.some, none).bisequence
res2: Option[Tuple2[Int, Nothing]] = None
like image 41
Mikhail Chugunkov Avatar answered Oct 05 '22 04:10

Mikhail Chugunkov


  • Starting Scala 2.13, this exact behavior is provided in the standard library by Option#zip:

    Some(2) zip Some('b') // Some((2, 'b'))
    Some(2) zip None      // None
    None zip Some('b')    // None
    None zip None         // None
    
  • Before Scala 2.13, Option#zip was returning an Iterable and it was possible to combine it with headOption:

    Some(2) zip Some('b') headOption // Some((2, 'b'))
    Some(2) zip None headOption      // None
    
like image 40
Xavier Guihot Avatar answered Oct 05 '22 06:10

Xavier Guihot


scala> import scalaz._
import scalaz._

scala> import Scalaz._
import Scalaz._

scala> (Tuple2.apply[Int, Int] _).lift[Option].tupled
res5: (Option[Int], Option[Int]) => Option[(Int, Int)] = <function1>

scala> res5((some(3), some(11)))
res6: Option[(Int, Int)] = Some((3,11))

scala> res5((some(3), none))
res7: Option[(Int, Int)] = None
like image 38
missingfaktor Avatar answered Oct 05 '22 04:10

missingfaktor