【问题标题】:Polymorphic Methods in Scala- why is this allowed?Scala 中的多态方法 - 为什么允许这样做?
【发布时间】:2017-02-24 15:40:19
【问题描述】:

我在scala中有以下多态方法:

  def addTwoThings[S](item1:S, item2:S) =
  {
    item1 + " | " + item2
  }

尽管我已经指定 item1 和 item2 应该是相同的类型“S”,但以下代码编译得很好。我需要对隐含的证据做些什么吗?

需要明确的是,我实际上希望编译器抱怨它们不是同一类型,但它似乎允许我继续,这让我感到困惑。谢谢。

println(addTwoThings("1",2))

【问题讨论】:

  • 答案在于Predef - String 有特殊的隐含意义
  • 感谢 dk14。你能详细说明一下吗?无论如何我可以为我的方法的客户强制执行类型安全吗?
  • 我会尝试在答案中添加解决方案

标签: scala types polymorphism type-inference


【解决方案1】:

您获得+ 运算符为您工作而无需明确使用.toString 的原因如下:What Scala feature allows the plus operator to be used on Any?Predef 中的这些额外隐含是 scala 中许多问题的根源,但很难摆脱这些遗留问题。

要找出 addTwoThings("1",2) 工作的原因 - 让我们重写它以获得 S 的准确推断:

scala> def addTwoThings[S](item1:S, item2:S): S = item1
addTwoThings: [S](item1: S, item2: S)S

scala> addTwoThings(1, "1")
res5: Any = 1

您可以注意到 S = Any 类型被推断为常见类型。

所以,这里有几个解决方案:

1) 如果您可以在方法的签名中允许两个类型参数,则解决方案如下:

def addTwoThings[S1, S2](item1:S1, item2:S2)(implicit ev: S1 =:= S2, ev2: S2 =:= S1) = {
    item1 + " | " + item2
}

注意:ev2 对于检查可能是多余的,但它提供了更完整的相等性,请参阅Scala: generic method using implicit evidence doesn't compile

实验:

scala> addTwoThings(1, "1")
<console>:18: error: Cannot prove that Int =:= String.
              addTwoThings(1, "1")
                          ^

scala> addTwoThings("2", "1")
res11: String = 2 | 1

2) 或者您可以使用 Evidence that types are not equal in Scala 排除 Any/AnyRef(所有事物的通用超类型):

trait =:!=[A, B]
implicit def neq[A, B] : A =:!= B = new =:!=[A, B] {}
implicit def neqAmbig1[A] : A =:!= A = ???
implicit def neqAmbig2[A] : A =:!= A = ???

def addTwoThings[S](item1:S, item2:S)(implicit ev: S =:!= Any, ev2: S =:!= AnyRef): S = item1

实验:

scala> addTwoThings(1, "1")
<console>:18: error: ambiguous implicit values:
 both method neqAmbig1 of type [A]=> =:!=[A,A]
 and method neqAmbig2 of type [A]=> =:!=[A,A]
 match expected type =:!=[Any,Any]
              addTwoThings(1, "1")
                          ^

scala> addTwoThings(1, 1)
res7: Int = 1

注意:这种方法不需要精确的类型相等,所以如果B1 &lt;: B2 - addTwoThings(b1, b2) - 仍然有效。它仅保护您免受不相关的类型层次结构的影响(这可能很有用)。在实践中,!= Any 没有!= AnyRef 不会在object A; object B; addTwoThings(B, A) 上给你错误。

注意2:编译器提供的错误很难阅读,更多信息在这里 - How can I customize Scala ambiguous implicit errors when using shapeless type inequalities


3) 另一种方法是柯里化:

def addTwoThings[S](item1:S)(item2:S) = ""

实验:

scala> addTwoThings(1)(1)
res8: String = ""

scala> addTwoThings(1)("1")
<console>:18: error: type mismatch;
 found   : String("1")
 required: Int
              addTwoThings(1)("1")
                              ^

类型推断不会寻找柯里化参数的常见超类型(您可以阅读更多here

【讨论】:

  • 感谢 dk14!无论如何,有没有明确要求 S 是完全相同的类型,而不是拥有一个共同的超类型?
  • @NoviceHead88 是的,我为答案添加了两种方法:证据和柯里化
  • @NoviceHead88 我解释了 3 种可能的解决方案。选择哪一个取决于您的确切要求,因为它们每个都有自己的优点和缺点
  • 太棒了 thx dk14!我想我会采用方法 #2 :) 因为我可能需要添加更多参数并可能定义多个类型参数。
  • (上面的继续)可能会开始变得笨拙,尤其是因为我只想确保所有参数都是完全相同的类型。
【解决方案2】:

这又是Scala编译器的类型推断问题

你必须给出明确的类型来引导编译器

addTwoThings[String]("1",2)

上面会报编译错误。

您的代码有效的原因

String 和 Int 的常见超类型是 Any。所以Scala编译器在函数中假设SAny

scala> def addTwoThings[S](item1:S, item2:S) =
     |   {
     |     item1 + " | " + item2
     |   }
addTwoThings: [S](item1: S, item2: S)String

scala> println(addTwoThings("1",2))
1 | 2

scala> println(addTwoThings[String]("1",2))
<console>:22: error: type mismatch;
 found   : Int(2)
 required: String
       println(addTwoThings[String]("1",2))

【讨论】:

  • 谢谢帕姆!在这种情况下,除了在调用方法时像使用 addTwoThings[String] 那样指定类型明确之外,还有其他方法可以在方法本身的定义中强制执行此操作吗?
猜你喜欢
  • 1970-01-01
  • 2011-08-17
  • 2011-10-14
  • 2013-09-07
  • 1970-01-01
  • 1970-01-01
  • 2020-05-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多