Scala

时间:2016-03-10 15:47:54

标签: scala contravariance

我正在研究一些相当通用的代码(还有另一个CSV文件阅读器),而且我遇到了一些逆变问题。我已将代码删除到我认为在此处演示问题的内容(我从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的类型来简化读者的事情,当然,编译器并不需要这些类型。每个表达式 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 <​​/ em>变体( Seq的元素类型)。因此 x 的类型为 Any ,但 f 需要类型 String with Int

这是一种无人居住的复合型,我无法找到解决此问题的简洁方法。我可以将值 f1a f2a 转换为 Any =&gt; Int =&gt; String ,但这不是我们喜欢的方式在Scala做事!

1 个答案:

答案 0 :(得分:2)

根据我的假设你想要做的事情,Scala编译器可以避免一个错误。您的for理解是元素和功能的笛卡尔积。这意味着,期望Int的函数将使用String调用一次,并且期望String的函数将使用Int调用一次。

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

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