【问题标题】:Scala: Enforcing A is not a subtype of BScala:强制执行 A 不是 B 的子类型
【发布时间】:2014-06-08 04:56:06
【问题描述】:

我正在尝试根据参数是否扩展给定类来重载方法,并且遇到了一些麻烦。使用an approach by Miles Sabin,我生成了以下代码:

object ExtendedGenericTypes {

  trait <:!<[A, B] // Encoding for "A is not a subtype of B"

  // Use ambiguity to rule out the cases we're trying to exclude
  implicit def nsubAmbig1[A, B >: A]: A <:!< B = null
  implicit def nsubAmbig2[A, B >: A]: A <:!< B = null

  // The implicit substitutions
  implicit def nsub[A, B]: A <:!< B = null
}

还有我的用例:

import ExtendedGenericTypes._

class Foo

def foobar[T](x: T)(implicit ev: T <:< Foo) = "hello"
def foobar[T](x: T)(implicit ev: T <:!< Foo) = 5

println(foobar(new Foo()))

不幸的是,这会导致歧义,并且编译器不知道要调用这两种方法中的哪一种。我正在寻找关于为什么在这种情况下存在歧义的解释(与迈尔斯的要点中概述的其他更简单的情况相反)以及如何规避这个障碍。请注意,我需要在参数级别执行此检查(而不是定义一个方法并在正文中进行检查),因为我希望有不同的返回类型。

【问题讨论】:

  • 我认为存在歧义,因为编译器发现了 Foo &lt;:&lt; FooFoo &lt;:!&lt; Foo 的隐式转换,并且它不考虑(当时)Foo &lt;:!&lt; Foo 存在模棱两可的转换。但是我不知道如何解决这个问题。两种方法都需要同名吗?
  • 是的,很遗憾。我想知道是否有另一种方法可以在不使用歧义的情况下实现A &lt;:!&lt; B 的强制执行,因此并不总是有可用的隐式方法......虽然我不知道该怎么做。

标签: scala


【解决方案1】:

第一个问题是,由于您在 REPL 中的工作方式,第二个 foobar 只是掩盖了第一个。如果你想要一个重载的定义,你需要使用:paste 来同时定义两者。

这仍然无法得到你想要的,只是一个新的错误消息:

scala> println(foobar(new Foo))
<console>:14: error: ambiguous reference to overloaded definition,
both method foobar of type [T](x: T)(implicit ev: EGT.<:!<[T,Foo])Int
and  method foobar of type [T](x: T)(implicit ev: <:<[T,Foo])String
match argument types (Foo) and expected result type Any
              println(foobar(new Foo))
                      ^

(请注意,我已缩写 ExtendedGenericTypes,因为我讨厌水平滚动条。)

您甚至可以尝试显式提供 &lt;:&lt; 实例:

scala> foobar(new Foo)(implicitly[Foo <:< Foo])
<console>:14: error: ambiguous reference to overloaded definition,
both method foobar of type [T](x: T)(implicit ev: EGT.<:!<[T,Foo])Int
and  method foobar of type [T](x: T)(implicit ev: <:<[T,Foo])String
match argument types (Foo)
              foobar(new Foo)(implicitly[Foo <:< Foo])
              ^

所以这里发生的事情是编译器不会让第二个参数列表决定使用哪个重载定义。这似乎意味着具有多个参数列表且第一个参数列表相同的重载方法本质上是无用的。这可能有一张票——乍一看,我能想到的最接近的是SI-2383

不过,这些都不重要,因为您不应该在这里使用重载方法——重载是一个可怕的“功能”,它是 Java 和 breaks all kinds of stuff 的遗留问题。

不过,还有其他可能的方法。我最喜欢的一些奇怪的 Scala 技巧依赖于这样一个事实,即您可以为隐式参数提供默认值,如果编译器找不到实例,将使用该默认值。如果我理解正确,你想要这样的东西:

class Foo

def foobar[T](x: T)(implicit ev: T <:< Foo = null) =
  Option(ev).fold[Either[Int, String]](Left(5))(_ => Right("hello"))

case class Bar(i: Int) extends Foo
case class Baz(i: Int)

然后:

scala> foobar(Bar(13))
res0: Either[Int,String] = Right(hello)

scala> foobar(Baz(13))
res1: Either[Int,String] = Left(5)

请注意,我使用的是Either,而不是让隐式的存在确定返回类型。有一些方法可以做到这一点(比如Shapelessfirst-class polymorphic function values),但在这种情况下它们可能有点矫枉过正。


更新:好的,既然你要求它:

import shapeless._

trait LowPriorityFoobar { this: Poly1 =>
  implicit def anyOld[T] = at[T](_ => 5)
}

object foobar extends Poly1 with LowPriorityFoobar {
  implicit def foo[T](implicit ev: T <:< Foo) = at[T](_ => "hello")
}

然后:

scala> foobar(Bar(13))
res6: String = hello

scala> foobar(Baz(13))
res7: Int = 5

没有包装。不过,在采用这种方法之前,您应该认真考虑一下。


更新到更新,为了完整起见:您也可以更直接(但也更冗长)执行此操作,而不使用 Shapeless 使用依赖的方法类型(同样,您需要使用 :paste 来定义这些都在一次):

class Foo

trait IsFooMapper[I] {
  type Out
  def apply(i: I): Out
}

trait LowPriorityIsFooMapper {
  implicit def isntFoo[A] = new IsFooMapper[A] {
    type Out = Int
    def apply(a: A) = 5
  }
}

object IsFooMapper extends LowPriorityIsFooMapper {
  implicit def isFoo[A](implicit ev: A <:< Foo) = new IsFooMapper[A] {
    type Out = String
    def apply(a: A) = "hello"
  }
}

def foobar[A](a: A)(implicit ifm: IsFooMapper[A]) = ifm(a)

然后:

scala> foobar(Bar(13))
res0: String = hello

scala> foobar(Baz(13))
res1: Int = 5

同样,这是相当高级的东西,应谨慎使用。

【讨论】:

  • 我考虑过使用Either,但它有点侵入性——它要求我的代码的客户端打开里面的内容。我希望客户端根据他们调用的版本(即基于参数是否是 foo 的子类型)来获取右或左类型
  • 您能详细说明一下注意事项吗?在这里我应该注意什么/常见的陷阱是什么?
  • WRT 更新到更新......这是“只使用类型类,该死”的方法,我认为这里完全合适。
  • @Kvass:依赖方法类型在 Scala 中仍然相对年轻,并且还没有真正广泛使用(目前)。它们很棒,但您想确保您的团队/使用您的代码的其他任何人都了解正在发生的事情。
  • 有什么理由 foobar 在最后被声明为隐式?
猜你喜欢
  • 2017-12-28
  • 2016-09-04
  • 2021-08-25
  • 2017-07-06
  • 2021-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-17
相关资源
最近更新 更多