Scala:强制执行 A 不是 B 的子类型 [英] Scala: Enforcing A is not a subtype of B

查看:54
本文介绍了Scala:强制执行 A 不是 B 的子类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图根据参数是否扩展给定类来重载方法,但遇到了一些麻烦.使用 Miles Sabin 的方法,我生成了以下代码:

I am trying to overload a method based on whether or not a parameter extends a given class, and have been having some trouble. Using an approach by Miles Sabin, I produced the following code:

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()))

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

Unfortunately, this results in ambiguity, and the compiler does not know which of the two methods to invoke. I'm looking for both an explanation as to why there is ambiguity in this case (as opposed to the other simpler cases outlined in Miles' gist) and also how to circumvent this hurdle. Note that I need to perform this check on the parameter level (rather than defining one method and doing the check in the body) because I want to have different return types.

推荐答案

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

The first problem is that because of the way you're working in the REPL, the second foobar simply shadows the first. If you want an overloaded definition, you'll need to use :paste to define both at once.

那仍然无法满足您的要求,只是一条新的错误消息:

That still won't get you what you want, just a new error message:

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,因为我讨厌水平滚动条.)

(Note that I've abbreviated ExtendedGenericTypes because I hate horizontal scrollbars.)

您甚至可以尝试显式提供 <:< 实例:

You can even try providing the <:< instance explicitly:

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.

So what's going on here is that the compiler isn't going to let the second parameter list determine which overloaded definition to use. Which seems to mean that overloaded methods with multiple parameter lists where the first parameter lists are the same are essentially useless. There's probably a ticket for this—at a glance the closest I can come up with is SI-2383.

不过,这些都不重要,因为你不应该在这里使用重载方法——重载是一个可怕的特性",它是 Java 和 破坏各种东西.

None of that really matters, though, because you just shouldn't be using overloaded methods here—overloading is a terrible "feature" that's a hangover from Java and breaks all kinds of stuff.

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

There are other possible approaches, though. Some of my favorite weird Scala tricks rely on the fact that you can provide a default value for an implicit parameter that will be used if the compiler can't find an instance. If I understand correctly, you want something like this:

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 而不是让隐式的存在决定返回类型.有很多方法可以做到这一点(例如 Shapeless一流的多态函数值),但在这种情况下它们可能是矫枉过正.

Note that I'm using an Either instead of letting the existence of the implicit determine the return type. There are ways you could accomplish that (like Shapeless's first-class polymorphic function values), but they're probably overkill in this case.

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

Update: okay, since you asked for it:

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

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

No wrapper. You should think really hard before taking this approach, though.

更新更新,为了完整起见:您还可以更直接地(但也更详细)执行此操作,而无需使用依赖方法类型的 Shapeless(同样,您需要使用 :paste 一次性定义所有这些):

Update to the update, for the sake of completeness: you can also do this more directly (but also more verbosely) without Shapeless using dependent method types (again, you'll need to use :paste to define these all at once):

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

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

Again, this is fairly advanced stuff and should be used with caution.

这篇关于Scala:强制执行 A 不是 B 的子类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆