如何实现隐式方法的中间类型?

发布于 2024-10-20 22:36:14 字数 563 浏览 2 评论 0原文

假设我想在我无法控制的现有类型 A 上提供方法 foo。据我所知,在 Scala 中执行此操作的规范方法是实现从 A 到某种实现 foo 的类型的隐式转换。现在我基本上看到两个选择。

  1. 为此目的定义一个单独的,甚至可能是隐藏的类:

    受保护的类 Fooable(a : A) {
      def foo(...) = { ... }
    }
    隐式 def a2fooable(a : A) = new Fooable(a)
    
  2. 定义一个内联匿名类:

    隐式 def a2fooable(a : A) = new { def foo(...) = { ... } }
    

变体 2) 肯定是更少的样板,尤其是当发生大量类型参数时。另一方面,我认为它应该会产生更多的开销,因为(概念上)每个转换创建一个类,而不是 1) 中全局创建一个类。

有一般准则吗?没有区别吗,因为编译器/VM 消除了 2) 的开销?

Assume I want to offer method foo on existing type A outside of my control. As far as I know, the canonical way to do this in Scala is implementing an implicit conversion from A to some type that implements foo. Now I basically see two options.

  1. Define a separate, maybe even hidden class for the purpose:

    protected class Fooable(a : A) {
      def foo(...) = { ... }
    }
    implicit def a2fooable(a : A) = new Fooable(a)
    
  2. Define an anonymous class inline:

    implicit def a2fooable(a : A) = new { def foo(...) = { ... } }
    

Variant 2) is certainly less boilerplate, especially when lots of type parameters happen. On the other hand, I think it should create more overhead since (conceptually) one class per conversion is created, as opposed to one class globally in 1).

Is there a general guideline? Is there no difference, because compiler/VM get rid of the overhead of 2)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

仅此而已 2024-10-27 22:36:14

使用单独的类可以提高性能,因为替代方案可以使用反射。

考虑一下

new { def foo(...) = { ... } }

,现在

new AnyRef { def foo(...) = { ... } }

AnyRef 没有方法foo。在 Scala 中,这种类型实际上是 AnyRef { def foo(...): ... },如果删除 AnyRef,您应该将其识别为 结构类型

在编译时,这个时间可以来回传递,并且在任何地方都知道方法 foo 是可调用的。然而,JVM 中没有结构类型,并且添加接口需要代理对象,这会导致一些问题,例如破坏引用相等(即,对象与其自身的结构类型版本不相等)。

解决这个问题的方法是使用结构类型的缓存反射调用。

因此,如果您想对任何性能敏感的应用程序使用 Pimp My Library 模式,请声明一个类。

Using a separate class is better for performance, as the alternative uses reflection.

Consider that

new { def foo(...) = { ... } }

is really

new AnyRef { def foo(...) = { ... } }

Now, AnyRef doesn't have a method foo. In Scala, this type is actually AnyRef { def foo(...): ... }, which, if you remove AnyRef, you should recognize as a structural type.

At compile time, this time can be passed back and forth, and everywhere it will be known that the method foo is callable. However, there's no structural type in the JVM, and to add an interface would require a proxy object, which would cause some problems such as breaking referential equality (ie, an object would not be equal with a structural type version of itself).

The way found around that was to use cached reflection calls for structural types.

So, if you want to use the Pimp My Library pattern for any performance-sensitive application, declare a class.

一场春暖 2024-10-27 22:36:14

我相信 1 和 2 会被编译为相同的字节码(除了在情况 2 中生成的类名之外)。
如果 Fooable 的存在只是为了让您能够将 A 隐式转换为 Fooable(并且您永远不会直接创建和使用 Fooable),那么我会选择选项 2。

但是,如果您控制 A(意味着 A 不是一个不能子类化的 java 库类)我会考虑使用特征而不是隐式转换来向 A 添加行为。

更新
我必须重新考虑我的答案。我会使用你的代码的变体 1,因为变体 2 结果使用反射(Linux 上的 scala 2.8.1)。

我编译了相同代码的这两个版本,使用 jd-gui 将它们反编译为 java,结果如下:

带有命名类的

class NamedClass { def Foo : String = "foo" }

object test {
  implicit def StrToFooable(a: String) = new NamedClass 
  def main(args: Array[String]) { println("bar".Foo) }
}

源代码 带有匿名类的源代码

object test {
  implicit def StrToFooable(a: String) = new { def Foo : String = "foo" } 

  def main(args: Array[String]) { println("bar".Foo) }
}    

用 java-gui 编译并反编译为 java。 “命名”版本生成一个 NamedClass.class,该类被反编译为该 java:

public class NamedClass
  implements ScalaObject
{
  public String Foo()
  {
    return "foo";
  }
}

匿名生成一个 test$$anon$1 类,该类被反编译为以下 java

public final class test$anon$1
{
  public String Foo()
  {
    return "foo";
  }
}

,因此几乎相同,除了匿名是“最终”(他们显然希望为了额外确保你不会特意尝试对匿名类进行子类化...)

但是在调用站点我得到了这个用于“命名”版本的java

public void main(String[] args) 
{ 
  Predef..MODULE$.println(StrToFooable("bar").Foo());
}

,而这个是用于匿名的

  public void main(String[] args) { 
    Object qual1 = StrToFooable("bar"); Object exceptionResult1 = null;
    try { 
      exceptionResult1 = reflMethod$Method1(qual1.getClass()).invoke(qual1, new Object[0]); 
      Predef..MODULE$.println((String)exceptionResult1); 
      return; 
    } catch (InvocationTargetException localInvocationTargetException) { 
      throw localInvocationTargetException.getCause();
    }
  }

我用谷歌搜索了一下并发现其他人也报告了同样的事情但我还没有找到更多关于为什么会出现这种情况的见解。

I believe 1 and 2 get compiled to the same bytecode (except for the class name that gets generated in case 2).
If Fooable exists only for you to be able to convert implicitly A to Fooable (and you're never going to directly create and use a Fooable), then I would go with option 2.

However, if you control A (meaning A is not a java library class that you can't subclass) I would consider using a trait instead of implicit conversions to add behaviour to A.

UPDATE:
I have to reconsider my answer. I would use variant 1 of your code, because variant 2 turns out to be using reflection (scala 2.8.1 on Linux).

I compiled these two versions of the same code, decompiled them to java with jd-gui and here are the results:

source code with named class

class NamedClass { def Foo : String = "foo" }

object test {
  implicit def StrToFooable(a: String) = new NamedClass 
  def main(args: Array[String]) { println("bar".Foo) }
}

source code with anonymous class

object test {
  implicit def StrToFooable(a: String) = new { def Foo : String = "foo" } 

  def main(args: Array[String]) { println("bar".Foo) }
}    

compiled and decompiled to java with java-gui. The "named" version generates a NamedClass.class that gets decompiled to this java:

public class NamedClass
  implements ScalaObject
{
  public String Foo()
  {
    return "foo";
  }
}

the anonymous generates a test$$anon$1 class that gets decompiled to the following java

public final class test$anon$1
{
  public String Foo()
  {
    return "foo";
  }
}

so almost identical, except for the anonymous being "final" (they apparently want to make extra sure you won't get out of your way to try and subclass an anonymous class...)

however at the call site I get this java for the "named" version

public void main(String[] args) 
{ 
  Predef..MODULE$.println(StrToFooable("bar").Foo());
}

and this for the anonymous

  public void main(String[] args) { 
    Object qual1 = StrToFooable("bar"); Object exceptionResult1 = null;
    try { 
      exceptionResult1 = reflMethod$Method1(qual1.getClass()).invoke(qual1, new Object[0]); 
      Predef..MODULE$.println((String)exceptionResult1); 
      return; 
    } catch (InvocationTargetException localInvocationTargetException) { 
      throw localInvocationTargetException.getCause();
    }
  }

I googled a little and found that others have reported the same thing but I haven't found any more insight as to why this is the case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文