我可以让 Scala 更喜欢隐式转换为 Java 8 Lambda 吗?

Can I make Scala prefer an implicit conversion to a Java 8 Lambda?

我正在为 kafka-streams 库编写一个 Scala 包装器。基本方法是提供从 kafka-streams classes like KStream 到 rich KStreamOps class 的隐式转换。

例如,KStream 提供以下 map 签名:

<K1, V1> KStream<K1, V1> map(KeyValueMapper<K, V, KeyValue<K1, V1>> mapper)

其中 KeyValueMapper 是带有签名的 SAM(从 Scala 的角度来看)(K, V) => KeyValue[K1, V1]

下面是提供 map 方法的 KStreamOps 的最小实现:

class KStreamOps[K, V](underlying: KStream[K, V]) {
  def map[K1, V1](f: (K, V) => (K1, V1)): KStream[K1, V1] =
    underlying.map { (k, v) =>
      val tuple = f(k, v)
      KeyValue.pair(tuple._1, tuple._2)
    }
}

请注意,map 这里需要一个 return 是 Tuple2 的函数,并负责将其转换为 kafka-streams 特定的 KeyValue 类型,以便用户不需要直接与 KeyValue 交互。

我提供隐式转换:

implicit def ops[K, V](kstream: KStream[K, V]): KStreamOps[K, V] = new KStreamOps[K, V](kstream)

但是当我尝试在如下代码中使用我的新 map 时:

val inputStream: KStream[Int, String] = builder.stream(inputTopic)
stream.map{ (k, v) => (k, v) }

我收到以下编译错误:

[error] KStreamOpsTest.scala:47: type mismatch;
[error]  found   : (Int, String)
[error]  required: org.apache.kafka.streams.KeyValue[Int,String]
[error]       stream.map((k, v) => (k, v))
[error]                            ^
[error] KStreamOpsTest.scala:47: Could not derive subclass of org.apache.kafka.streams.kstream.KeyValueMapper[Int,String,org.apache.kafka.streams.KeyValue[Int,String]]
[error]  (with SAM `def method apply(x: Int, x: String)org.apache.kafka.streams.KeyValue[Int,String]`)
[error]  based on: ((k: Int, v: String) => scala.Tuple2(k, v)).
[error]       stream.map((k, v) => (k, v))

因此,编译器正在尝试使用 KStream#map 使用期望 KeyValue 被 returned 的 SAM 而不是我期望输出的 KStreamOps#map函数是 Tuple2.

如果我将我的方法重命名为 KStreamOps#map1 并调用 stream.map1,隐式转换会按预期工作并且代码会编译。

为什么编译器无法识别有一个隐式转换会得到一个采用正确签名函数的方法? function return type 是不是没有考虑? SAM 会永远赢吗?

我正在使用 Scala 2.11.8 进行测试,并打开 -Xexperimental 标志。

正如@Łukasz 所建议的那样,创建一个显式包装器似乎是解决问题的方法。而且我可能会提供从我的包装器类型返回到 KStream:

的隐式转换
implicit def unwrap[K, V](wrapped: WrappedKStream[K, V]): KStream[K, V] = wrapped.underlying

这样我们将同时使用 KStream 和包装器的方法,但包装器 class 方法优先。

这里一个明显的缺点是用户在创建初始对象时需要明确使用包装器类型。