Scala 中的复合类型、反方差等

Compound types, contra variance, etc. in Scala

我正在编写一些相当通用的代码(还有另一个 CSV 文件 reader),我 运行 遇到了一些逆变性问题。我已将代码简化为我认为可以证明此处问题的代码(我从我的 Scala 工作表中复制了它):

def f1(x: Int)(s: String) = Try(x+s.toInt)
val f1a: Int=>String=>Try[Int] = f1 _
val r1 = f1a(3)("2")
def f2(x: String)(s: String) = Try(x.toInt+s.toInt)
val f2a: String=>String=>Try[Int] = f2 _
val r2 = f2a("3")("2")
val fs: Seq[String with Int=>String=>Try[Int]] = Seq(f1a, f2a)
val xs: Seq[Any] = Seq(3,"3")
val r3 = for {(f,x) <- fs zip xs} yield f(x)("2")

编辑最后一行以避免在@Lee 和@badcook 发表评论后出现不相关的问题。 我试图通过提供 val 的类型来简化 reader 的事情,尽管编译器当然不需要这样做。每个表达式 r1r2 都按预期计算为 Success(5)。 r3 的表达式无法编译。错误是:

type mismatch;  found   : x.type (with underlying type Any)  required: String with Int

问题的本质在于x的类型,for-comprehension中f(x)的参数。这是一个逆变位置(函数的参数)但是 xsfsco 变体( Seq 的元素类型)。因此 x 具有类型 Any,但是 f 需要类型 String with Int.

这是一个无人居住的复合类型,我一直没能找到一个干净的方法来解决这个问题。我可以将值 f1af2a 转换为 Any=>Int=>String 但那不是我们喜欢在 Scala 中做事的方式!

根据我假设您正在尝试做的事情,Scala 编译器使您免于一个错误。您的 for 理解是元素和函数的笛卡尔积。这意味着您期望 Int 的函数将使用 String 调用一次,而您期望 String 的函数将使用 Int 调用一次。

您可能想要的是 zip 您的两个 Seq 在一起,然后 map 在结果对上,将每个函数应用于其配对元素。不幸的是,正如您所指出的,逆变会阻止它进行类型检查。这根本上是因为 Seq 的所有元素必须是同一类型,而您希望保留元素具有不同类型并且这些类型与函数的不同类型相匹配的事实。

简而言之,如果您想走这条路,您正在寻找异构列表,也称为 HList,而不是普通的 Scala 集合。 shapeless has an implementation of this and provides map and zip methods to go along with it. 那里有一点学习曲线(我建议熟悉更高级别的类型以及 shapeless 如何实现它们)并且与普通集合相比,HList 使用起来有点笨拙,但这将解决这种特殊类型问题。