【问题标题】:Is PartialFunction orElse looser on its type bounds than it should be?PartialFunction orElse 的类型界限是否比应有的宽松?
【发布时间】:2014-08-19 23:25:36
【问题描述】:

让我们定义一个PartialFunction[String, String] 和一个PartialFunction[Any, String]

现在,给定orElse的定义

def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): PartialFunction[A1, B1] 

我希望不能组合这两者,因为

AString
A1Any

因此绑定的A1 &lt;: A(即Any &lt;: String)不成立。

没想到,我可以组合它们并获得一个在整个String 域上定义的PartialFunction[String, String]。这是一个例子:

val a: PartialFunction[String, String] = { case "someString" => "some other string" }
// a: PartialFunction[String,String] = <function1>

val b: PartialFunction[Any, String] = { case _ => "default" }
// b: PartialFunction[Any,String] = <function1>

val c = a orElse b
// c: PartialFunction[String,String] = <function1>

c("someString")
// res4: String = some other string

c("foo")
// res5: String = default

c(42)
// error: type mismatch;
//   found   : Int(42)
//   required: String

此外,如果我明确提供orElse 类型参数

a orElse[Any, String] b
// error: type arguments [Any,String] do not conform to method orElse's type parameter bounds [A1 <: String,B1 >: String]

编译器终于有点意思了。

我是否缺少任何类型系统巫术导致b 成为orElse 的有效参数?也就是说A1怎么会被推断为String

如果编译器从b 推断出A1,那么它一定是Any,那么导致String 的推断链从哪里开始?


更新

在玩过 REPL 之后,我注意到 orElse 在类型不匹配时返回一个交集类型 A with A1。示例:

val a: PartialFunction[String, String] = { case "someString" => "some other string" }
// a: PartialFunction[String,String] = <function1>

val b: PartialFunction[Int, Int] = { case 42 => 32 }
// b: PartialFunction[Int,Int] = <function1>

a orElse b
// res0: PartialFunction[String with Int, Any] = <function1>

由于(String with Int) &lt;:&lt; String 这有效,即使生成的函数实际上无法使用。我还怀疑String with Any 被统一为Any,因为

import reflect.runtime.universe._
// import reflect.runtime.universe._   

typeOf[String] <:< typeOf[String with Any]
// res1: Boolean = true

typeOf[String with Any] <:< typeOf[String]
// res2: Boolean = true

这就是为什么将StringAny 混合成String 的原因。

话虽如此,引擎盖下发生了什么?不匹配的类型是在什么逻辑下统一的?

更新 2

我已将问题简化为更一般的形式:

class Foo[-A] {
  def foo[B <: A](f: Foo[B]): Foo[B] = f
}

val a = new Foo[Any]
val b = new Foo[String]

a.foo(b) // Foo[String] Ok, String <:< Any
b.foo(a) // Foo[String] Shouldn't compile! Any <:!< String
b.foo[Any](a) // error: type arguments [Any] do not conform to method foo's type parameter bounds [A <: String]

【问题讨论】:

  • 很确定编译器只是在推断它可以找到的最严格的返回类型。
  • 而不是向后推断其他类型?
  • Nitpick: "String with Int" 不是联合类型——根据定义——作为一个类型需要 "String" 和 "Int" 独立地实现 "String with Int" 的接口,这并非如此。 "String" 不是 <: with int>
  • 感谢@Utaal,它确实是一个交集而不是一个联合。修好了!

标签: scala types partialfunction


【解决方案1】:

你搞错了。

您始终可以将B &lt;: A 类型的任何参数传递给需要A 类型参数的方法,即A 的任何子类型。那就是如果你有

def foo(a: Animal)

您可以将Dog 传递给foo,因为Dog &lt;: Animal

同样的,如果你有

def foo(l: List[Animal])

您可以将List[Dog] 传递给它,因为List 与其类型参数是协变的,并且由于Dog &lt;: Animal,然后List[Dog] &lt;: List[Animal]

现在如果你有

def foo(pf: PartialFunction[String, String])

您可以传递PartialFunction[Any, String],因为PartialFunction 与第一个类型参数是逆变的,而与第二个类型参数是协变的。从Any &gt;: String,然后是PartialFuncion[Any, String] &lt;: PartialFunction[String, String]

现在,对于类型边界,编译器将尝试推断A1B1,这样

  • A1A 的子类型
  • B2B 的子类型

为此,它会寻找:

  • AnyString 的最大常见子类型,因为 AA1 处于逆变位置
  • StringString 的最不常见超类型,因为 BB1 是协变位置

结果

  • A1String
  • B1String

您将PartialFunction[String, String] 与PartialFunction[Int, Int] 组合在一起的情况与上一个示例相比看起来很奇怪,其中:

  • StringInt 的最大共同子类型是String with Int,即这两种类型的交集,它是两者的子类型(在这种情况下几乎是说Nothing:是 StringInt 似乎都不太可能)
  • StringInt 最不常见的超类型是 Any

因此

val a: PartialFunction[String, String] = ...
val b: PartialFunction[Int, Int] = ...
a orElse b // PartialFunction[String with Int, Any] // as expected, although not very useful...

【讨论】:

  • “你把这个搞砸了。”你是用第二人称回答自己吗? :D。无论如何,这似乎可以很好地概述正在发生的事情。
  • 不错的答案!比头脑更完整,虽然我觉得这里没有必要重新解释协变和逆变,但有兴趣的人可以在其他地方查找。
  • 仍然不完全理解 String with Int 部分。也许我只是没有花足够的时间思考它,但如果你真的想让你的答案全面,你可能需要详细说明一下。
  • @UndercoverAgent 想象一下:class A extends B with C。那么以下对于val a = new A 都是正确的。 a.isInstanceOf[B]a.isInstanceOf[C]a.isInstanceOf[B with C]。您一直使用with 进行继承。它将类型与and 组合在一起。同时是 B 和 C。
  • @Kigyo,确切地说,这就是我以 SO 风格回答我的幼稚问题!关于解释逆变,好吧,我正在向 OP 解释它,它显然忘记了它是什么:P
【解决方案2】:

如果我理解正确的话,我想说PartialFunction[Any, String]PartialFunction[String, String] 的子类型,因为它是逆变器。这可以解释更新前您的问题中描述的行为,但是您让我完全混淆了这种联合类型的东西。

我什至不知道 String with Int 到底是什么意思!

【讨论】:

  • 这不是逆变的问题,类型绑定很具体:A1(即Any)必须是A(即Any)的子类型,即错误的。方差在这里无关紧要。
  • @GabrielePetronella A1 <: a pf string>
  • 我删除了我的反对票,因为我错了。但是,这个答案肯定需要一些详细说明。
  • 它是一个 subtype 但不是一个 subclass
【解决方案3】:

这当然是模糊的,只是我的拙见。建议和 cmet 表示赞赏。

取自这个 SO 问题。 (How to know if an object is an instance of a TypeTag's type?)

import scala.reflect.runtime.universe._
implicit class MyInstanceOf[U: TypeTag](that: U) {
  def myIsInstanceOf[T: TypeTag] = 
    typeOf[U] <:< typeOf[T]
}

我们有一个正确的方法来检查isInstanceOf而不删除。

val b: PartialFunction[Any, String] = { case _ => "default" }
b.myIsInstanceOf[PartialFunction[String, String]] //true

这才有意义。如果您有来自Any =&gt; String 的函数,则它接受任何输入。所以它也接受String 输入。这就是为什么它也可以被视为来自String =&gt; String 的函数。对于任何T,基本上都可以视为T =&gt; String

所以最终编译器同意A -&gt; StringA1 -&gt; String

 a.orElse[String,String](b) //works

编辑:最后的想法

您不应将A1 &lt;: A 视为一种限制。它只会推断结果PartialFunction 的类型。 没有办法orElse 不能应用。所涉及的两个 PF 在 A 上都是逆变的,因此总是可以找到满足 A1 &lt;: A 的 BOTH 的公共子类型。

我认为一个类比是分数的加法,你认为,哦,它们没有共同的分母,因此不能相加。两个分数都可以调整(或:以不同方式看待)以具有共同的分母。编译器虽然想要找到最小的公分母,而不是采用与另一个分母相乘的简单方法。 (A with A')

对于其他类型B 也是如此。两者都是协变的,因此也总能找到一个共同的超类型。 Any 在最坏的情况下。

【讨论】:

  • 从一般的角度来看,我也同意编译器。但是我不明白这里遵循的推理规则是什么。 b 的静态类型是PartialFunction[Any, String],这与PartialFunction[String, String] 不兼容,除非您决定将Any 向下转换为String。为什么编译器会这样做?提示从何而来?
  • 是的,这个评论是错误的。它非常兼容或符合要求。
  • 这两个PartialFunction是子类型,但Any绝对不是String的子类型。同样Int 不是String 的子类型,但orElse 适用于PF[Int, String]PF[String, String]
  • 谢谢,您上次的编辑看起来很合法。我仍然需要处理所有信息(我什至可能会尝试自己写下答案),但您的意见非常有帮助。
  • 我个人也没有过多关注这类东西。我在这里部分依赖我的常识。希望有更多知识和事实而不是直觉的更详细的答案。
【解决方案4】:

你已经输入了这个,但是:

scala> val a: PartialFunction[String, String] = { case "a" => "b" }
a: PartialFunction[String,String] = <function1>

scala> val b: PartialFunction[Any, String] = { case 1 => "one" }
b: PartialFunction[Any,String] = <function1>

scala> a orElse b
res0: PartialFunction[String,String] = <function1>

scala> a orElse[String,String] b
res1: PartialFunction[String,String] = <function1>

scala> a orElse[Any,String] b
<console>:10: error: type arguments [Any,String] do not conform to method orElse's type parameter bounds [A1 <: String,B1 >: String]
              a orElse[Any,String] b
              ^

scala> import reflect.runtime._ ; import universe._
import reflect.runtime._
import universe._

scala> typeOf[PartialFunction[Any,String]] <:< typeOf[PartialFunction[String,String]]
res3: Boolean = true

由于是逆变类型参数,这里可以使用 PF[Any, String]。

要回答这个问题,它在哪里说它会选择什么?

http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#local-type-inference

例如,它承诺在逆变位置推断出最大 A1,但仍符合约束 <: a.>

【讨论】:

  • 我得到了逆变点,但Any 仍然不是String 的子类型,那么A1 &lt;:&lt; A 绑定怎么能被尊重?
  • 我评论了另一个答案。 A1 和 A 都是字符串。
  • 我的意思是,很明显是因为A1 的推断类型参数是String 而不是Any,但是为什么会出现这样的推断呢?
  • 因为它有效?抱歉,我要赶上学校接机了。您表明它不适用于 A1 作为 Any。
  • 我错过了 Any 被静态推断为 String 的精确规则。我确信解释是正确的,但我仍然怀念那一点。
猜你喜欢
  • 2013-11-05
  • 2011-08-27
  • 1970-01-01
  • 2021-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-09
  • 1970-01-01
相关资源
最近更新 更多