What's the difference between `trait ValueHolder { type ValueType }` and `trait ValueHolder[T] {}` [duplicate]

StackOverflow https://stackoverflow.com/questions/17586451

  •  02-06-2022
  •  | 
  •  

質問

When I read the source code of Liftweb, I found some trait declarations:

trait ValueHolder {
  type ValueType
  def get: ValueType
}

trait PValueHolder[T] extends ValueHolder {
  type ValueType = T
}

My question is, for the following two trait declarations:

trait ValueHolder {
    type ValueType
}

trait ValueHolder[T] {
}

I think they are equal to each other, but is there any difference between them? Does one can do or offer something that another one can't?

役に立ちましたか?

解決

The first one is called abstract type member and the second one is a close analog to Java generics, but there are not complete the same. This is two different ways to achieve the same goal. As Martin Odersky explained is his interview, one reason for having both abstract type members and generic type parameters is orthogonality:

There have always been two notions of abstraction: parameterization and abstract members. In Java you also have both, but it depends on what you are abstracting over. In Java you have abstract methods, but you can't pass a method as a parameter. You don't have abstract fields, but you can pass a value as a parameter. And similarly you don't have abstract type members, but you can specify a type as a parameter. So in Java you also have all three of these, but there's a distinction about what abstraction principle you can use for what kinds of things. And you could argue that this distinction is fairly arbitrary.

What we did in Scala was try to be more complete and orthogonal. We decided to have the same construction principles for all three sorts of members. So you can have abstract fields as well as value parameters. You can pass methods (or "functions") as parameters, or you can abstract over them. You can specify types as parameters, or you can abstract over them. And what we get conceptually is that we can model one in terms of the other. At least in principle, we can express every sort of parameterization as a form of object-oriented abstraction. So in a sense you could say Scala is a more orthogonal and complete language.

He also described a difference between abstract type members and generic type parameters that can show up in practice:

But in practice, when you use type parameterization with many different things, it leads to an explosion of parameters, and usually, what's more, in bounds of parameters. At the 1998 ECOOP, Kim Bruce, Phil Wadler, and I had a paper where we showed that as you increase the number of things you don't know, the typical program will grow quadratically. So there are very good reasons not to do parameters, but to have these abstract members, because they don't give you this quadratic blow up.

I think that a great and easy example was given by Bill Veners (the creator of ScalaTest):

// Type parameter version
trait FixtureSuite[F] {
  // ...
}

and

// Type member version
trait FixtureSuite {
  type F
  // ...
}

In either case, F would be the type of the fixture parameter to pass into the tests, which suite subclasses would make concrete. Here's an example of a concrete suite of tests that needs a StringBuilder passed into each test, using the type parameter approach:

// Type parameter version
class MySuite extends FixtureSuite[StringBuilder] {
  // ...
}

And here's an example of a concrete suite of tests that needs a StringBuilder passed into each test using the abstract type member approach:

// Type member version
class MySuite extends FixtureSuite {
  type F = StringBuilder
  // ...
}

For example, if you want to pass three different fixture objects into tests, you'll be able to do so, but you'll need to specify three types, one for each parameter. Thus the type parameter approach was choosen, your suite classes could have ended up looking like this:

// Type parameter version
class MySuite extends FixtureSuite3[StringBuilder, ListBuffer, Stack] with MyHandyFixture {
  // ...
}

Whereas with the type member approach it will look like this:

// Type member version
class MySuite extends FixtureSuite3 with MyHandyFixture {
  // ...
}

So that shows two approaches to the goal in achieving great modular abstraction. More on this topic can be read in this legendary paper on Scalable Components

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top