我可以让 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 方法优先。
这里一个明显的缺点是用户在创建初始对象时需要明确使用包装器类型。
我正在为 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 方法优先。
这里一个明显的缺点是用户在创建初始对象时需要明确使用包装器类型。