当 Scala 已经有特征时,为什么还要有类?

发布于 2024-12-19 13:45:21 字数 2463 浏览 2 评论 0原文

这可能看起来是一个愚蠢的问题,所以请耐心等待...

考虑一下这个 REPL 会话:

scala> trait T
defined trait T

scala> val t = new T
<console>:8: error: trait T is abstract; cannot be instantiated
       val t = new T
               ^

scala> val t = new T {}
t: java.lang.Object with T = $anon$1@78db81f3

scala> class C
defined class C

scala> val c = new C
c: C = C@170a6001

我们可以像使用类一样使用特征,只不过我们必须在 之后添加 {}新的T。事实上,我们本质上是将 T 混合到 java.lang.Object 中,这对我来说实际上很有意义。

如果我们有成员,则只需添加 {}

scala> trait T2 { val s = "test" }
defined trait T2

scala> val t2 = new T2
<console>:8: error: trait T2 is abstract; cannot be instantiated
       val t2 = new T2
                ^

scala> val t2 = new T2 {}
t2: java.lang.Object with T2 = $anon$1@6a688d6f

scala> t2.s
res0: java.lang.String = test

scala> class C2 { val s = "test" }
defined class C2

scala> val c2 = new C2
c2: C2 = C2@73ea7821

scala> c2.s
res1: java.lang.String = test

如果我们有抽象成员,则特征声明实际上会短几个字符,更重要的是,在我看来更加一致(不需要记住将 abstract 放在声明前面):

scala> trait T3 { val s: String }
defined trait T3

scala> val t3 = new T3 { val s = "test" }
t3: java.lang.Object with T3 = $anon$1@1f2f0ce9

scala> abstract class C3 { val s: String }
defined class C3

scala> val c3 = new C3 { val s = "test" }
c3: C3 = $anon$1@207a8313

如果您忘记必须定义某些成员,两种方式都会给您带来编译错误:

scala> val badt3 = new T3 {}
<console>:7: error: object creation impossible, since value s in trait T3 of type String is not defined
       val badt3 = new T3 {}

scala> class BadC3 { val s: String }
<console>:8: error: class BadC3 needs to be abstract, since value s is not defined
       class BadC3 { val s: String }

如果我们尝试做更复杂的事情,那么权力特征自然变得更加明显:

scala> val t4 = new T with T2
t4: java.lang.Object with T with T2 = $anon$1@479e0994

scala> val c4 = new C with C2
<console>:9: error: class C2 needs to be a trait to be mixed in
       val c4 = new C with C2

所以我再次问,为什么当特征明显更简单、更强大时,Scala 还会为类烦恼吗?

我认为原因是与 Java 的概念和实际兼容性,但我想知道是否可以在幕后维护代码兼容性。据我了解,Scala 特征只是在幕后变成了 Java 类,那么为什么不能发生相反的情况,Scala 认为 Java 类本质上是特征呢?

与这一切相关的是,为什么不允许在不必要时删除大括号呢?例如:

val t = new T

到那时,作为用户,特征将与当前的 Scala 类无法区分,但当然更好。

This may seem like a silly question, so bear with me...

Consider this REPL session:

scala> trait T
defined trait T

scala> val t = new T
<console>:8: error: trait T is abstract; cannot be instantiated
       val t = new T
               ^

scala> val t = new T {}
t: java.lang.Object with T = $anon$1@78db81f3

scala> class C
defined class C

scala> val c = new C
c: C = C@170a6001

We can use a trait just like a class, except that we have to add {} after the new T. In fact, we're essentially mixing T into java.lang.Object, which actually makes a lot of sense to me.

If we have members, again only the {} must be added:

scala> trait T2 { val s = "test" }
defined trait T2

scala> val t2 = new T2
<console>:8: error: trait T2 is abstract; cannot be instantiated
       val t2 = new T2
                ^

scala> val t2 = new T2 {}
t2: java.lang.Object with T2 = $anon$1@6a688d6f

scala> t2.s
res0: java.lang.String = test

scala> class C2 { val s = "test" }
defined class C2

scala> val c2 = new C2
c2: C2 = C2@73ea7821

scala> c2.s
res1: java.lang.String = test

If we have abstract members then the trait declaration is actually shorter by a few characters and, more importantly, more consistent in my eyes (no need to remember to put abstract in front of your declarations):

scala> trait T3 { val s: String }
defined trait T3

scala> val t3 = new T3 { val s = "test" }
t3: java.lang.Object with T3 = $anon$1@1f2f0ce9

scala> abstract class C3 { val s: String }
defined class C3

scala> val c3 = new C3 { val s = "test" }
c3: C3 = $anon$1@207a8313

If you forget that you must define some of the members, both ways give you compile errors:

scala> val badt3 = new T3 {}
<console>:7: error: object creation impossible, since value s in trait T3 of type String is not defined
       val badt3 = new T3 {}

scala> class BadC3 { val s: String }
<console>:8: error: class BadC3 needs to be abstract, since value s is not defined
       class BadC3 { val s: String }

And if we try to do more complex things then the power of traits naturally becomes further apparent:

scala> val t4 = new T with T2
t4: java.lang.Object with T with T2 = $anon$1@479e0994

scala> val c4 = new C with C2
<console>:9: error: class C2 needs to be a trait to be mixed in
       val c4 = new C with C2

So again I ask, why does Scala bother with classes at all when traits are apparently both simpler and more powerful?

I assume the reason is conceptual and actual compatibility with Java, but I wonder whether code compatability could have been maintained behind the scenes. As I understand it, Scala traits just become Java classes behind the scenes, so why couldn't the reverse happen and Scala consider Java classes to essentially be traits?

Related to all this, why not allow dropping the curly brackets when unnecessary? For example:

val t = new T

At that point, as a user, traits would be indistinguishable from current Scala classes, but of course better.

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

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

发布评论

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

评论(1

べ映画 2024-12-26 13:45:21

特征和类之间有几个区别:

  • 特征不能采用构造函数参数。这个限制可能会在某个时候被解除,但这是一个难题。一个特征可以在层次结构中多次继承,并且每次实例化都可以为构造函数参数提供不同的值

  • 特征被编译为Java接口和实现类(携带具体方法)。这意味着它有点慢,因为所有调用都通过接口,如果它们是具体的,它们将被转发到其实现

  • 具有具体成员的特征不能在 Java 中很好地继承(可以,但它会看起来像一个接口,因此仍然需要在 Java 中实现具体成员)。

我认为类和特征之间的区别不会消失,主要是因为最后两项。但如果第一点得到解决,它们可能会变得更容易使用。关于没有 {} 的实例化,这是可以添加的一种便利,但我个人不喜欢它:每个实例化都会创建一个新的(匿名类),并且应该向程序员表明情况确实如此。

There are several differences between traits and classes:

  • a trait can not take constructor parameters. This limitation might be lifted at some point, but it's a hard problem. A trait may be inherited multiple times in a hierarchy, and each instantiation may give different values for the constructor parameters

  • a trait is compiled to a Java interface and an implementation class (carrying the concrete methods). This means it's a bit slower, because all calls go through interfaces, and if they're concrete, they are forwarded to their implementation

  • a trait with concrete members can't be nicely inherited in Java (it could, but it would look like an interface, therefore concrete members would still need to be implemented in Java).

I don't think the distinction between classes and traits will go away, mostly because of the last two items. But they may become easier to use if the first point is solved. Regarding instantiation without the {}, that's a convenience that could be added, but I personally wouldn't like it: each instantiation creates a new class (an anonymous one), and there should be an indication to the programmer that that's the case.

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