Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good style: Type aliases vs subclassing empty classes/traits

Tags:

scala

In the Twitter Effective Scala - Type Aliases, they say:

Don’t use subclassing when an alias will do.

trait SocketFactory extends (SocketAddress => Socket)

a SocketFactory is a function that produces a Socket. Using a type alias

type SocketFactory = SocketAddress => Socket

is better. We may now provide function literals for values of type SocketFactory and also use function composition: val addrToInet: SocketAddress => Long val inetToSocket: Long => Socket

val factory: SocketFactory = addrToInet andThen inetToSocket

Note that type aliases are not new types — they are equivalent to the syntactically substituting the aliased name for its type.

The sort of thing we're talking about is:

trait Base
trait T1 extends Base // subclassing
type T2 = Base        // type alias

Obviously you can't use a type alias as a replacement when the class/trait has a body or stores information.

So using type aliases (T2) rather than extending with a trait or class (T1) has the following advantages:

  1. As they say above, we can compose using function literals.
  2. We won't be producing a .class file, there will be less for the compiler to do (theoretically).

However, it has the following disadvantages:

  1. To be available in the same namespace (package), you need to define the type in a package object, which will probably be in another file from the use site.
  2. You can't jump 'Open Type' ctrl-shift-T on an alias in Eclipse, but you can Open Declaration (F3) in Eclipse. This will probably be fixed in the future.
  3. You can't use the type alias from another language, such as Java.
  4. If the type alias is parameterized, then erasure prevents pattern matching from working in the same manner as it does for traits.

The fourth point is the most serious for me:

trait T1[T]
trait T2 extends T1[Any]
type T3 = T1[Any]

class C2 extends T2

val c = new C2
println("" + (c match { case t: T3 => "T3"; case _ => "any" }))
println("" + (c match { case t: T2 => "T2"; case _ => "any"  }))

This produces:

T3
T2

The compiler gives a warning about the first pattern match, which clearly doesn't work as expected.

So, finally, the question. Are there any other advantages or disadvantages to using type aliases rather than extending a trait/class?

like image 723
Matthew Farwell Avatar asked Oct 17 '12 20:10

Matthew Farwell


2 Answers

I think they key is actually that type aliases and traits are really different. The list of differences goes on and on and on:

  1. Shorthand syntax works for type aliases (e.g. x => x+7 would work as a type I2I = Int => Int) and not traits.
  2. Traits can carry additional data, types aliases can't.
  3. Implicits work for type aliases but not traits.
  4. Traits provide type-safety/matching in a way that type aliases do not.
  5. Type aliases have strict rules about being overridden in subclasses; same-named traits shadow instead (anything goes).

among others.

This is because you are doing dramatically different things in the two cases. Type aliases are simply a way to say, "Okay, when I type Foo, I actually mean Bar. They're the same. Got it? Cool." After you do this, you can substitute the name Foo for Bar wherever and whenever you feel like it. The only restriction is that once you decide what a type is you can't change your mind.

Traits, on the other hand, create an entirely new interface, which may expand on what the trait extends, or may not. If not, it's still a marker that this is its own type of entity, which can be pattern matched, tested for with 'isInstanceOf', and so on.

So, now that we've established that they're really different, the question is how to use each. And the answer is pretty simple: if you like the existing class just as it is except you dislike the name, use a type alias. If you want to create your own new entity that is distinct from other things, use a trait (or a subclass). The former is mostly for convenience, while the latter is for added type safety or capability. I don't think any rule saying use one instead of the other really captures the point--understand the features of both, and use each when those are the features you want.

(And then there's existential types, which provide similar capability to generics...but let's leave that for another question.)

like image 165
Rex Kerr Avatar answered Nov 13 '22 06:11

Rex Kerr


They are different in that a type alias defines type equality relationship (ie. T1 <: T2 && T1 >: T2) while trait extension defines a strict sub-type relationship (ie. T1 <: T2 && !(T1 >: T2)). Use them wisely.

like image 26
Jesper Nordenberg Avatar answered Nov 13 '22 07:11

Jesper Nordenberg