Scala双重定义(2个方法具有相同的类型擦除)
我在 scala 中写了这个,但它无法编译:
class TestDoubleDef{
def foo(p:List[String]) = {}
def foo(p:List[Int]) = {}
}
编译器通知:
[error] double definition:
[error] method foo:(List[String])Unit and
[error] method foo:(List[Int])Unit at line 120
[error] have same type after erasure: (List)Unit
我知道 JVM 没有对泛型的本机支持,所以我理解这个错误。
我可以为 List[String]
和 List[Int]
编写包装器,但我很懒:)
我很怀疑,但是,是否有另一种方式来表达 List [String]
与 List[Int]
类型不同?
谢谢。
I wrote this in scala and it won't compile:
class TestDoubleDef{
def foo(p:List[String]) = {}
def foo(p:List[Int]) = {}
}
the compiler notify:
[error] double definition:
[error] method foo:(List[String])Unit and
[error] method foo:(List[Int])Unit at line 120
[error] have same type after erasure: (List)Unit
I know JVM has no native support for generics so I understand this error.
I could write wrappers for List[String]
and List[Int]
but I'm lazy :)
I'm doubtful but, is there another way expressing List[String]
is not the same type than List[Int]
?
Thanks.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
我喜欢 Michael Kramer 使用隐式的想法,但我认为它可以更直接地应用:
我认为这非常可读且简单。
[更新]
还有另一种似乎可行的简单方法:
对于每个版本,您都需要一个额外的类型参数,因此这无法扩展,但我认为对于三四个版本来说就可以了。
[更新2]
对于两种方法,我发现了另一个不错的技巧:
I like Michael Krämer's idea to use implicits, but I think it can be applied more directly:
I think this is quite readable and straightforward.
[Update]
There is another easy way which seems to work:
For every version you need an additional type parameter, so this doesn't scale, but I think for three or four versions it's fine.
[Update 2]
For exactly two methods I found another nice trick:
您可以使用
Predef
中定义的DummyImplicit
来代替发明虚拟隐式值,它似乎正是为此而设计的:Instead of inventing dummy implicit values, you can use the
DummyImplicit
defined inPredef
which seems to be made exactly for that:要了解 Michael Kramer 的解决方案,有必要认识到隐式参数的类型并不重要。重要的是它们的类型是不同的。
以下代码以相同的方式工作:
在字节码级别,两个 foo 方法都变成双参数方法,因为 JVM 字节码不知道隐式参数或多参数列表。在调用站点,Scala 编译器通过查看传入列表的类型(直到该列表不会被删除)来选择要调用的适当的 foo 方法(以及要传入的适当的虚拟对象)。之后)。
虽然它比较冗长,但这种方法减轻了调用者提供隐式参数的负担。事实上,如果 dummyN 对象是 TestDoubleDef 类私有的,它甚至可以工作。
To understand Michael Krämer's solution, it's necessary to recognize that the types of the implicit parameters are unimportant. What is important is that their types are distinct.
The following code works in the same way:
At the bytecode level, both
foo
methods become two-argument methods since JVM bytecode knows nothing of implicit parameters or multiple parameter lists. At the callsite, the Scala compiler selects the appropriatefoo
method to call (and therefore the appropriate dummy object to pass in) by looking at the type of the list being passed in (which isn't erased until later).While it's more verbose, this approach relieves the caller of the burden of supplying the implicit arguments. In fact, it even works if the dummyN objects are private to the
TestDoubleDef
class.由于类型擦除的奇妙作用,方法列表的类型参数在编译期间被擦除,从而将两个方法减少为相同的签名,这是一个编译器错误。
Due to the wonders of type erasure, the type parameters of your methods' List get erased during compilation, thus reducing both methods to the same signature, which is a compiler error.
正如 Viktor Klang 已经说过的,泛型类型将被编译器删除。幸运的是,有一个解决方法:
感谢 Michid< /a> 提示!
As Viktor Klang already says, the generic type will be erased by the compiler. Fortunately, there's a workaround:
Thanks for Michid for the tip!
如果我结合 Daniel 的 响应 和 Sandor Murakozi 的回复在这里我得到:
我得到一个类型安全(ish)变体
逻辑也可能包含在类型类中(感谢jsuereth):
@annotation.implicitNotFound(msg = "Foo 不支持 ${T} 只接受 Int 和 String")
Sealed Trait Foo[T] { def apply(list : List[T]) : Unit }
其中给出:
请注意,我们必须隐式地编写[Foo[A]].apply(x) ,因为编译器认为
implicitly[Foo[A]](x)
意味着我们使用参数implicitly
调用。If I combine Daniels response and Sandor Murakozis response here I get:
I get a typesafe(ish) variant
The logic may also be included in the type class as such (thanks to jsuereth):
@annotation.implicitNotFound(msg = "Foo does not support ${T} only Int and String accepted")
sealed trait Foo[T] { def apply(list : List[T]) : Unit }
Which gives:
Note that we have to write
implicitly[Foo[A]].apply(x)
since the compiler thinks thatimplicitly[Foo[A]](x)
means that we callimplicitly
with parameters.还有(至少有一种)另一种方法,即使它不太好并且不是真正的类型安全:
隐式清单参数可用于“具体化”擦除的类型,从而绕过擦除。您可以在许多博客文章中了解更多信息,例如 这个。
发生的情况是,清单参数可以返回 T 被删除之前的值。然后,基于 T 的简单调度到各种实际实现即可完成剩下的工作。
可能有更好的方法来进行模式匹配,但我还没有看到。人们通常做的是在 m.toString 上进行匹配,但我认为保留类会更干净一些(即使它更冗长一些)。不幸的是Manifest的文档不是太详细,也许它还有一些可以简化它的东西。
它的一个很大的缺点是它并不是真正的类型安全:foo 会对任何 T 感到满意,如果你不能处理它,你就会遇到问题。我想可以通过对 T 进行一些限制来解决这个问题,但这会使事情变得更加复杂。
当然,这一切也不太好,我不确定是否值得这样做,特别是如果你很懒的话;-)
There is (at least one) another way, even if it is not too nice and not really type safe:
The implicit manifest paramenter can be used to "reify" the erased type and thus hack around erasure. You can learn a bit more about it in many blog posts,e.g. this one.
What happens is that the manifest param can give you back what T was before erasure. Then a simple dispatch based on T to the various real implementation does the rest.
Probably there is a nicer way to do the pattern matching, but I haven't seen it yet. What people usually do is matching on m.toString, but I think keeping classes is a bit cleaner (even if it's a bit more verbose). Unfortunately the documentation of Manifest is not too detailed, maybe it also has something that could simplify it.
A big disadvantage of it is that it's not really type safe: foo will be happy with any T, if you can't handle it you will have a problem. I guess it could be worked around with some constraints on T, but it would further complicate it.
And of course this whole stuff is also not too nice, I'm not sure if it worth doing it, especially if you are lazy ;-)
除了使用清单之外,您还可以使用以类似方式隐式导入的调度程序对象。在清单出现之前,我在博客中介绍了这一点:http://michid.wordpress。 com/code/implicit-double-dispatch-revisited/
这具有类型安全的优点:重载方法只能针对将调度程序导入到当前作用域的类型进行调用。
Instead of using manifests you could also use dispatchers objects implicitly imported in a similar manner. I blogged about this before manifests came up: http://michid.wordpress.com/code/implicit-double-dispatch-revisited/
This has the advantage of type safety: the overloaded method will only be callable for types which have dispatchers imported into the current scope.
我从 http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.html
作者:亚伦·诺夫斯特鲁普
[...]
Nice trick I've found from http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.html
by Aaron Novstrup
[...]
我尝试改进 Aaron Novstrup 和 Leo 的答案,使一组标准证据对象变得重要且更加简洁。
但这会导致编译器抱怨,当 foo 调用另一个需要相同类型隐式参数的方法时,隐式值的选择不明确。
因此,我只提供以下内容,在某些情况下更简洁。此改进适用于值类(
扩展 AnyVal
的值类)。如果包含的类型名称相当长,请声明一个内部
trait
以使其更加简洁。然而,值类不允许内部特征、类或对象。因此还要注意 Aaron Novstrup 和 Leo 的答案不适用于值类。
I tried improving on Aaron Novstrup’s and Leo’s answers to make one set of standard evidence objects importable and more terse.
But that will cause the compiler to complain that there are ambiguous choices for the implicit value when
foo
calls another method which requires an implicit parameter of the same type.Thus I offer only the following which is more terse in some cases. And this improvement works with value classes (those that
extend AnyVal
).If the containing type name is rather long, declare an inner
trait
to make it more terse.However, value classes do not allow inner traits, classes, nor objects. Thus also note Aaron Novstrup’s and Leo’s answers do not work with a value classes.
我没有对此进行测试,但为什么上限不起作用?
擦除翻译是否从 foo( List[Any] s ) 两次更改为 foo( List[String] s ) 和 foo( List[Int] i ):
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ108
我想我在版本 2.8 中读到过,上限现在以这种方式编码,而不是总是 Any。
要重载协变类型,请使用不变边界(Scala 中有这样的语法吗?...我认为没有,但将以下内容作为上述主要解决方案的概念附录):
然后我假设隐式转换在代码的擦除版本中被消除。
更新:问题是 JVM 删除的方法签名上的类型信息多于“必要”的信息。我提供了一个链接。它从类型构造函数中删除类型变量,甚至删除这些类型变量的具体绑定。存在概念上的区别,因为擦除函数的类型绑定没有概念上的非具体化优势,因为它在编译时已知并且不随泛型的任何实例而变化,并且调用者必须不调用类型不符合类型绑定的函数,那么如果擦除的话,JVM 如何强制执行类型绑定呢?那么一个链接表示类型绑定保留在元数据中编译器应该访问。这解释了为什么使用类型边界不能启用重载。这也意味着 JVM 是一个开放的安全漏洞,因为类型限制方法可以在没有类型限制的情况下调用(哎呀!),所以请原谅我假设 JVM 设计者不会做这样不安全的事情。
当我写这篇文章时,我不明白 stackoverflow 是一个通过答案质量对人们进行评级的系统,就像对声誉的竞争一样。我以为这是一个分享信息的地方。在我写这篇文章时,我正在从概念层面比较具体化和非具体化(比较许多不同的语言),所以在我看来,删除类型绑定没有任何意义。
I didn't test this, but why wouldn't an upper bound work?
Does the erasure translation to change from foo( List[Any] s ) twice, to foo( List[String] s ) and foo( List[Int] i ):
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ108
I think I read that in version 2.8, the upper bounds are now encoded that way, instead of always an Any.
To overload on covariant types, use an invariant bound (is there such a syntax in Scala?...ah I think there isn't, but take the following as conceptual addendum to the main solution above):
then I presume the implicit casting is eliminated in the erased version of the code.
UPDATE: The problem is that JVM erases more type information on method signatures than is "necessary". I provided a link. It erases type variables from type constructors, even the concrete bound of those type variables. There is a conceptual distinction, because there is no conceptual non-reified advantage to erasing the function's type bound, as it is known at compile-time and does not vary with any instance of the generic, and it is necessary for callers to not call the function with types that do not conform to the type bound, so how can the JVM enforce the type bound if it is erased? Well one link says the type bound is retained in metadata which compilers are supposed to access. And this explains why using type bounds doesn't enable overloading. It also means that JVM is a wide open security hole since type bounded methods can be called without type bounds (yikes!), so excuse me for assuming the JVM designers wouldn't do such an insecure thing.
At the time I wrote this, I didn't understand that stackoverflow was a system of rating people by quality of answers like some competition over reputation. I thought it was a place to share information. At the time I wrote this, I was comparing reified and non-reified from a conceptual level (comparing many different languages), and so in my mind it didn't make any sense to erase the type bound.