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 的事情,尽管编译器当然不需要这样做。每个表达式 r1 和 r2 都按预期计算为 Success(5)。 r3 的表达式无法编译。错误是:
type mismatch; found : x.type (with underlying type Any) required: String with Int
问题的本质在于x的类型,for-comprehension中f(x)的参数。这是一个逆变位置(函数的参数)但是 xs 和 fs 是 co 变体( Seq 的元素类型)。因此 x 具有类型 Any,但是 f 需要类型 String with Int.
这是一个无人居住的复合类型,我一直没能找到一个干净的方法来解决这个问题。我可以将值 f1a 和 f2a 转换为 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 使用起来有点笨拙,但这将解决这种特殊类型问题。
我正在编写一些相当通用的代码(还有另一个 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 的事情,尽管编译器当然不需要这样做。每个表达式 r1 和 r2 都按预期计算为 Success(5)。 r3 的表达式无法编译。错误是:
type mismatch; found : x.type (with underlying type Any) required: String with Int
问题的本质在于x的类型,for-comprehension中f(x)的参数。这是一个逆变位置(函数的参数)但是 xs 和 fs 是 co 变体( Seq 的元素类型)。因此 x 具有类型 Any,但是 f 需要类型 String with Int.
这是一个无人居住的复合类型,我一直没能找到一个干净的方法来解决这个问题。我可以将值 f1a 和 f2a 转换为 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 使用起来有点笨拙,但这将解决这种特殊类型问题。