SCALA:暗黙の変換を行うA-> bオプションの動作[a] - >オプション[b
-
27-10-2019 - |
質問
オブジェクトa - >オブジェクトBに備えた暗黙の変換を再利用する関数を書き込もうとしています。仕事。
私が思いついたのは:
implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_))
これは、some(..)を値に割り当てるときに機能しますが、オプション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.
最初の変換と2番目の変換の違いは本当にありません。どういうわけか、後者の暗黙の変換は呼び出されません。タイプシステムと関係があると思いますが、まだどのようにわかりません。何か案は? -Albert(私はScala 2.9.1に乗っています)
解決
これが手がかりです:
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のものを検索します)。
余談として:
いくつかのカテゴリ理論を使用して、さまざまな種類の「オプション」のものを処理できます。
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を問題にマッピングしているため、これはもう少し高度ですが、必要に応じて暗黙の会話をコンテナに持ち込むためのより一般的なソリューションです。 1つの暗黙の会話方法を使用して、彼らがどのようにチェーンをするかに注意してください 取る より限られた暗黙の議論。
また、これは例を機能させるはずです:
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
^
それはあなたにそれを言っています 分散 重要であり、インクリットと対話します。私の推測では、あなたは非常にまれであり、おそらく他のテクニックを使用して回避できるバグを修正するのが難しいことです。
他のヒント
あなたはそれに気づいていないかもしれませんが、そのための旗があります: -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
!
SCALA言語仕様が次のようにビューを定義するため、それは機能しません。
暗黙のパラメーターと方法は、呼ばれる暗黙の変換を定義することもできます ビュー. 。タイプからのビュー s 入力し t 関数タイプを持つ暗黙の値によって定義されます s => t また (=> s)=> t または、そのタイプの値に変換可能な方法によって。
コンパイラは、宛先とソースの両方が汎用型を持つコンバーターを見つけていないようです。 fromOptionToOption
暗黙的なパラメーターが必要なため、3つのカテゴリに準拠していません。
からのビューを定義します 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))
ただし、すべてが失われていません。それは一般ほど良くありません Option
に Option
, 、しかし、私たちは Bar
に 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 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)
これが一般に使用できる別の回避策です Option
に Option
しかし、余分なものが必要です .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)
確かにそれは非常に奇妙な問題です。私は別のタイプよりも使用しようとしました 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)失敗します。 Scalaはすでに変換しているため、ケース(1)は常に成功します X
に Y
anでラッピングする前に A
.
今、私たちは問題を知っています ソース, 、あなたはタイプの第一人者が説明するのを待つ必要があります どうして これは実際には問題です...変換はまだ有効です。
askForY(movea(f)) // succeeds, even with A[+B]
改善しました @jseurethの回答 そして、のサポートを追加しました 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)