Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Making implicit conversion A->B work for Option[A] -> Option[B]

Tags:

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)

like image 709
Albert Avatar asked Jan 11 '12 17:01

Albert


People also ask

What is an implicit conversion function in Scala?

Implicit conversions in Scala are the set of methods that are apply when an object of wrong type is used. It allows the compiler to automatically convert of one type to another. Implicit conversions are applied in two conditions: First, if an expression of type A and S does not match to the expected expression type B.

What is the implicit conversion?

An implicit conversion sequence is the sequence of conversions required to convert an argument in a function call to the type of the corresponding parameter in a function declaration. The compiler tries to determine an implicit conversion sequence for each argument.

Where does Scala look for Implicits?

When an implicit value is required, the Scala compiler would first look into the current scope to find the required implicit value that can act as a function for the type conversion required. The current scope used as implicit scope include: Local scope.

What is implicit class in Scala?

Scala 2.10 introduced a new feature called implicit classes. An implicit class is a class marked with the implicit keyword. This keyword makes the class's primary constructor available for implicit conversions when the class is in scope. Implicit classes were proposed in SIP-13.


1 Answers

Here's clue:

scala> val fooOpt: Option[Bar] = Option(Foo(1)) fooOpt: Option[Bar] = Some(Bar(1)) 

And another:

scala> implicit def foobar(x: String): Int = augmentString(x).toInt foobar: (x: String)Int  scala> val y: Option[String] = Option(1) y: Option[String] = Some(1)  scala> val y: Option[Int] = Option("1") y: Option[Int] = Some(1) 

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.

package object fun {   trait Functor[Container[_]] {     def fmap[A,B](x: Container[A], f: A => B): Container[B]   }   object Functor {      implicit object optionFunctor extends Functor[Option] {        override def fmap[A,B](x: Option[A], f: A => B): Option[B] = x map f      }      // Note: With some CanBuildFrom magic, we can support Traversables here.   }   implicit def liftConversion[F[_], A, B](x: F[A])(implicit f: A => B, functor: Functor[F]): F[B] =      functor.fmap(x,f)  } 

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:

scala> val tmp = Option(Foo(1)) tmp: Option[Foo] = Some(Foo(1))  scala> val y: Option[Bar] = tmp y: Option[Bar] = Some(Bar(1)) 

And make your usage of Some more dangerous:

scala> val tmp = Some(Foo(1)) tmp: Some[Foo] = Some(Foo(1))  scala> val y: Option[Bar] = tmp <console>:25: error: could not find implicit value for parameter functor: fun.Functor[Some]        val y: Option[Bar] = tmp                             ^ 

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.

like image 112
4 revs Avatar answered Oct 15 '22 01:10

4 revs