在尝试将此solution到Perform a typed join in Scala with Spark Datasets隐式使用时,我遇到了一些我不理解的问题。
在下面的测试中,innerJoin
的签名为def innerJoin[U, K](ds2: Dataset[U])(f: T => K, g: U => K)(implicit e1: Encoder[(K, T)], e2: Encoder[(K, U)], e3: Encoder[(T, U)])
,但是我用f: Foo => String
和g: Bar => Int
来称呼它。我希望在编译时出现错误,但是编译就可以了。为什么会这样?
实际发生的是,当Spark尝试创建乘积编码器(我认为是针对生成的java.lang.ClassNotFoundException: scala.Any
元组)时,它编译得很好并且测试失败,并且((K, Foo),(K, Bar))
失败。我假设Any
作为Int
和String
的共同“父”出现。
import org.apache.spark.sql.{Dataset, Encoder, SparkSession}
import org.scalatest.Matchers
import org.scalatest.testng.TestNGSuite
import org.testng.annotations.Test
case class Foo(a: String)
case class Bar(b: Int)
class JoinTest extends TestNGSuite with Matchers {
import JoinTest._
@Test
def testJoin(): Unit = {
val spark = SparkSession.builder()
.master("local")
.appName("test").getOrCreate()
import spark.implicits._
val ds1 = spark.createDataset(Seq(Foo("a")))
val ds2 = spark.createDataset(Seq(Bar(123)))
val jd = ds1.innerJoin(ds2)(_.a, _.b)
jd.count shouldBe 0
}
}
object JoinTest {
implicit class Joins[T](ds1: Dataset[T]) {
def innerJoin[U, K](ds2: Dataset[U])(f: T => K, g: U => K)
(implicit e1: Encoder[(K, T)], e2: Encoder[(K, U)], e3: Encoder[(T, U)]): Dataset[(T, U)] =
{
val ds1_ = ds1.map(x => (f(x), x))
val ds2_ = ds2.map(x => (g(x), x))
ds1_.joinWith(ds2_, ds1_("_1") === ds2_("_1")).map(x => (x._1._2, x._2._2))
}
}
}
答案 0 :(得分:1)
您是正确的,Any
被推断为String
和Int
的共同父代,因此被用作K
。 Function
在输出类型上是协变的。因此,Foo => String
是Foo => Any
的有效子类。
解决这类问题的常用方法是使用两个类型参数和一个隐式=:=
。例如:
def innerJoin[U, K1, K2](ds2: Dataset[U])(f: T => K1, g: U => K2)
(implicit eq: K1 =:= K2, e1: Encoder[(K2, T)], e2: Encoder[(K2, U)], e3: Encoder[(T, U)]): Dataset[(T, U)] =
{
val ds1_ = ds1.map(x => (eq(f(x)), x))
... rest the same as before ...