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

前端 未结 5 998
一个人的身影
一个人的身影 2020-12-14 08:25

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 Opti

5条回答
  •  盖世英雄少女心
    2020-12-14 08:58

    It doesn't work because the Scala Language Specification defines view as follows:

    Implicit parameters and methods can also define implicit conversions called views. A view from type S to type T is defined by an implicit value which has function type S=>T or (=>S)=>T or by a method convertible to a value of that type.

    fromOptionToOption doesn't conform to the three categories since it takes an implicit parameter. Compiler doesn't seem to find converter with both destination and source having generic type.

    Defining a view from Option[Foo] to Option[Bar] works as expected.

    trait T
    case class Foo(i: Int) extends T
    case class Bar(i: Int) extends T
    
    object Main {
      implicit def fromFooToBar(f: Foo):Bar = Bar(f.i)
      implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i)
      // implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] =
      //  from.map(conversion(_))
      implicit def fromOptionFooToOptionBar(o: Option[Foo]): Option[Bar] = o map { foo => foo } 
    
      def test(): Option[Bar] = {
        val fooOpt = Some(Foo(4))
        val barOpt2: Option[Bar] = fooOpt
    
        barOpt2
      }
    }
    
    println(Main.test)
    

    Running this prints out:

    $ scala so.scala
    Some(Bar(4))
    

    However, all is not lost. It's not as nice as general Option to Option, but we can do something like anything that can turn into Bar to Option[Bar] by view bound.

    trait T
    case class Foo(i: Int) extends T
    case class Bar(i: Int) extends T
    
    object Main {
      implicit def fromFooToBar(f: Foo):Bar = Bar(f.i)
      implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i)
      implicit def fromOptionToOptionBar[A <% Bar](from: Option[A]): Option[Bar] =
        from map { foo => foo }
    
      def test(): Option[Bar] = {
        val fooOpt = Some(Foo(4))
        val barOpt2: Option[Bar] = fooOpt
    
        barOpt2
      }
    }
    
    println(Main.test)
    

    Here's another workaround that can be used for general Option to Option but requires extra .convert call:

    trait T
    case class Foo(i: Int) extends T
    case class Bar(i: Int) extends T
    
    case class Converter[A](x: Option[A]) {
      def convert[B](implicit ev: Function1[A, B]): Option[B] = x map { a: A => ev(a) }
    }
    
    object Main {
      implicit def optionToConverter[A](x: Option[A]) = Converter(x)
      implicit def fooToBar(x: Foo) = Bar(x.i)
    
      def test(): Option[Bar] = {
        val fooOpt = Some(Foo(4))
        val barOpt: Option[Bar] = fooOpt.convert
        barOpt
      }
    }
    
    println(Main.test)
    

提交回复
热议问题