I'm trying to write a function which re-uses the implicit conversions which I have for Object A -> Object B when they are wrapped in an Option in a generic way so that Option[A] -> Option[B] conversions also work.
What I've come up with is:
implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_))
This works when I assign a Some(..) to a value but not when I assign an Option val; see the following console output:
scala> trait T
defined trait T
scala> case class Foo(i: Int) extends T
defined class Foo
scala> case class Bar(i: Int) extends T
defined class Bar
scala> implicit def fromFooToBar(f: Foo):Bar = Bar(f.i)
fromFooToBar: (f: Foo)Bar
scala> implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i)
fromBarToFoo: (b: Bar)Foo
scala> implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_))
fromOptionToOption: [A, B](from: Option[A])(implicit conversion: (A) => B)Option[B]
scala> val foo: Option[Foo] = Some(Bar(1))
foo: Option[Foo] = Some(Foo(1))
// THIS WORKS as expected
scala> val fooOpt = Some(Foo(4))
fooOpt: Some[Foo] = Some(Foo(4))
scala> val barOpt2: Option[Bar] = fooOpt
<console>:16: error: type mismatch;
found : Some[Foo]
required: Option[Bar]
val barOpt2: Option[Bar] = fooOpt
^
//THIS FAILS.
I don't really see the difference between the first and second conversion. Somehow it doesn't invoke the implicit conversion in the latter. I guess it has something to do with the type system, but I can't see how just yet. Any ideas? -Albert (I'm on scala 2.9.1)
Here's clue:
And another:
Looks like a legitimately odd bug. I'd pop open a smaller test case and open an issue (or search for one in JIRA).
As an aside:
You could use some category theory to handle lots of different types of "Option-ish" things.
That's a bit more advanced, as you're mapping some category theory FP onto the problem, but it's a more general solution to lift implicit conversations into containers as needed. Notice how they chain by using one implicit conversation method that takes a more limited implicit argument.
ALSO, this should make the examples work:
And make your usage of
Some
more dangerous:That's telling you that variance is critical, and interacts with implicits. My guess is you ran into a very rare, probably hard to fix bug that can be avoided using other techniques.
You might not be aware of it, but there's a flag for that:
-Xlog-implicits
. And this is what it says:And there you go -- it doesn't know what type
B
must be. 0__ mentioned that this problem doesn't happen with invariant collections, and that makes some sense. In invariant collections,B
must be exactlyBar
, while for covariant collections it could be any subtype ofBar
.So, why does
val foo: Option[Foo] = Some(Bar(1))
work? Well, there's a flag for that too...-Ytyper-debug
. Not for the weak, however, given the extreme verbosity.I waddled through anyway, comparing what happens in both cases, and the answer is rather simple... it's not the
Option
that is being converted in that case, butBar
! Remember, you declared an implicit conversion fromBar => Foo
, so it is applying that conversion before passing the result toSome
!Indeed it's a very strange problem. I tried to use another type than
Option
, and it turns out that the problem is thatOption
is covariant in its type parameter. This works all:But if instead I define
A
asThe case (2) fails. Case (1) always succeeds, because Scala already converts
X
toY
before wrapping it in anA
.Now that we know the problem source, you need to wait for a type guru to explain why this is actually a problem... The conversion is still valid, you see:
It doesn't work because the Scala Language Specification defines view as follows:
Compiler doesn't seem to find converter with both destination and source having generic type.fromOptionToOption
doesn't conform to the three categories since it takes an implicit parameter.Defining a view from
Option[Foo]
toOption[Bar]
works as expected.Running this prints out:
However, all is not lost. It's not as nice as general
Option
toOption
, but we can do something like anything that can turn intoBar
toOption[Bar]
by view bound.Here's another workaround that can be used for general
Option
toOption
but requires extra.convert
call:I improved @jseureth answer and added support for
Traversable
:Now you can implicitly convert options and traversables: