19

Having

(Some(1), Some(2))

I expect to get

Some((1, 2))

and having

(Some(1), None)

I expect to get

None
Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169

5 Answers5

34

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.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • Sure. I kinda feel this would be the only solution, since I can't imagine how they could implement that in Scalaz, since I see no way of specifying a unified type for tuple's items, which seems to be necessary for that problem. Just asked out of curiosity – Nikita Volkov Sep 13 '12 at 14:21
  • @NikitaVolkov - I am almost positive you could get Shapeless to do this, but I haven't actually had occasion to use it so I can't come up with an example. – Rex Kerr Sep 13 '12 at 14:23
  • @NikitaVolkov: You're right in that you can't provide a general `Traverse` instance for tuples, but `Bitraverse` lets you get both types in there. – Travis Brown Sep 13 '12 at 15:12
16

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.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Unfortunately, type annotations will remain mandatory in this situation – Yo Eight Sep 13 '12 at 15:17
  • @YoEight: Why do you say that? See my edit for a working example. – Travis Brown Sep 13 '12 at 16:35
  • @Travis Brown. You've just deferred the problem. Your wrapper holds the type information for you. The very same information you would annotate at call site. I did the same trick when I pushed MonadWriter to Scalaz. – Yo Eight Sep 13 '12 at 21:08
  • @YoEight: I don't understand. It definitely works, in the sense that the implicit kicks in, the wrapper gets built, and the method gets called, all without the type annotation. All the wrapper does is pull together the evidence that we've got the right `Bitraverse` and `Applicative` instances. – Travis Brown Sep 13 '12 at 21:50
  • @Travis Brown. I don't say it doest work; like I said, I used the same trick with MonadWriter. As you know after implicit resolution, your code is like: bisequenceWrap[Tuple2, Option, Int, String]((some(1), some("a"))).bisequence. My point is: It is not because you don't see it, it doesn't exist. Besides, your wrapper will not work in this case: val v: (String \/ Int, String \/ String) = (\/-(1), \/-("a")); v.bisequence – Yo Eight Sep 14 '12 at 08:07
7

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
6
  • 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
    
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
4
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
missingfaktor
  • 90,905
  • 62
  • 285
  • 365