Scala:进行隐式转换A-> B适用于选项[A] - >选项[B]

时间:2012-01-11 17:28:58

标签: scala scala-option

我正在尝试编写一个函数,它重用了对象A的隐式转换 - >对象B,当它们以通用方式包装在Option中时,选项[A] - >选项[B]转换也有效。

我想出的是:

implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_))

当我将Some(..)赋值给某个值时,这是有效的,但是当我指定Option val时则不行;请参阅以下控制台输出:

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.

我真的没有看到第一次和第二次转换之间的区别。不知何故,它不会在后者中调用隐式转换。我想它与类型系统有关,但我还不知道它是怎么回事。有任何想法吗? - 阿尔伯特 (我在scala 2.9.1上)

5 个答案:

答案 0 :(得分:13)

这是线索:

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

另一个:

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)

看起来像是一个合法的奇怪的错误。我会打开一个较小的测试用例并打开一个问题(或在JIRA中搜索一个)。

暂且不说:

您可以使用某种类别理论来处理许多不同类型的“Option-ish”事物。

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)

}

当你将一些类别理论FP映射到问题上时,这有点先进,但它是一种更通用的解决方案,可以根据需要将隐式会话提升到容器中。注意他们如何使用一个隐式会话方法链接采用一个更有限的隐式参数。

此外,这应该使示例有效:

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

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

让您使用Some更危险:

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
                            ^

这告诉你 variance 是至关重要的,并与implicits交互。我的猜测是你遇到了一个非常罕见的,可能很难修复的bug,可以使用其他技术来避免。

答案 1 :(得分:13)

您可能没有意识到这一点,但有一个标志:-Xlog-implicits。这就是它所说的:

scala> val barOpt2: Option[Bar] = fooOpt
fromOptionToOption is not a valid implicit value for Some[Foo] => Option[Bar] because:
incompatible: (from: Option[Foo])(implicit conversion: Foo => B)Option[B] does not match expected type Some[Foo] => Option[Bar]
<console>:16: error: type mismatch;
 found   : Some[Foo]
 required: Option[Bar]
       val barOpt2: Option[Bar] = fooOpt
                                  ^

你去了 - 它不知道B必须是什么类型。 0__提到使用不变集合不会发生这个问题,这是有道理的。在不变量集合中,B必须完全Bar,而对于协变集合,它可以是Bar的任何子类型。

那么,为什么val foo: Option[Foo] = Some(Bar(1))有效?好吧,那里也有一面旗帜...... -Ytyper-debug。然而,鉴于极端冗长,不适合弱者。

无论如何,我还是比较了,比较两种情况下发生的事情,答案很简单......在这种情况下,不是Option被转换,而是Bar!请记住,您声明了Bar => Foo的隐式转化,因此它会在将结果传递给Some之前应用转化

答案 2 :(得分:1)

确实这是一个非常奇怪的问题。我尝试使用除Option之外的其他类型,事实证明问题是Option在其类型参数中是协变的。这适用于所有人:

case class A[B](value: B)  // invariant in B

case class X()
case class Y()

implicit def xtoy(x: X): Y = Y()
implicit def ytox(x: Y): X = X()
implicit def movea[U, V](from: A[U])(implicit view: U => V): A[V] =  A[V](from.value)

def test(a: A[Y]) = "ok"
test(A(X()))   // (1)
val f = A(X())
test(f)        // (2)

但如果我将A定义为

case class A[+B](value: B)  // covariant in B

案例(2)失败。情况(1)总是成功,因为Scala已经将X转换为Y,然后将其包装在A中。

现在我们知道问题来源,您需要等待类型大师解释为什么这实际上是一个问题...转换仍然有效,你看:

askForY(movea(f))  // succeeds, even with A[+B]

答案 3 :(得分:1)

它不起作用,因为Scala语言规范定义了如下视图:

  

隐式参数和方法还可以定义名为 views 的隐式转换。从类型 S 到类型 T 的视图由隐式值定义,该隐式值具有函数类型 S =&gt; T (= &gt; S)=&gt; T 或通过可转换为该类型值的方法。

fromOptionToOption不符合这三个类别,因为它采用隐式参数。编译器似乎没有找到目标和源都具有泛型类型的转换器。

定义Option[Foo]Option[Bar]的视图按预期工作。

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)

运行此打印输出:

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

然而,一切都不会丢失。它不如OptionOption那么好,但我们可以通过视图绑定做任何可以变成BarOption[Bar]的内容。

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)

以下是另一种可用于OptionOption的解决方法,但需要额外.convert次呼叫:

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)

答案 4 :(得分:1)

我改进了@jseureth answer并添加了对Traversable的支持:

trait Mappable[A, B, C[_]] {
  def apply(f: A => B): C[B]
}

package object app {

  implicit class OptionMappable[A, B, C[X] <: Option[X]](option: C[A]) extends Mappable[A, B, Option] {
    override def apply(f: A => B): Option[B] = option.map(f)
  }

  implicit class TraversableMappable[A, B, C[X] <: Traversable[X]](traversable: C[A])
    (implicit cbf: CanBuildFrom[C[A], B, C[B]]) extends Mappable[A, B, C] {
    override def apply(f: A => B): C[B] = {
      val builder = cbf(traversable)
      builder.sizeHint(traversable)
      builder ++= traversable.map(f)
      builder.result()
    }
  }

  implicit def liftConversion[C[_], A, B](x: C[A])
    (implicit f: A => B, m: C[A] => Mappable[A, B, C]): C[B] = m(x)(f)

}

现在你可以隐式转换选项和遍历:

implicit def f(i: Int): String = s"$i"

val a: Option[String] = Some(1)
val b: Seq[String] = Seq(1, 2, 3)