7

I wanted to create a class CompileResult, that can be treated like an Either type but has some additional useful methods. It should be a CompileSuccess or a CompileFailure (which, too, has some extended functionality). However I can't do that because Either is an abstract class and I don't want to extend it but rather use delegation and have an Either stored internally.

So my question is, why is Either an abstract class now (also why was it a trait before) and why did they not use an interface? Same goes for Option too.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
valenterry
  • 2,429
  • 16
  • 21
  • An interface allows an infinite number of possibilities. An Either or Option type can only have 2 possibilities by definition. An implementation with an abstract base class with a private constructor and two inner subclasses guarantees that by design, since no non-inner class will have access to the private constructor, and thus won't be able to extend the base class. I don't know enough about Scala to know how traits factor into this. – Doval Dec 17 '14 at 15:47
  • 3
    What's wrong with `Either[CompileFailure, CompileSuccess]`? –  Dec 17 '14 at 15:50
  • I like it more to have a speaking Resultname like `CompileResult[CompileFailure, CompileSuccess]` so that I can go for `compileResult.successfull()` instead of `compileResult.isRight()`. – valenterry Dec 17 '14 at 15:57
  • 1
    @Doval In Scala, you can declare a `sealed trait` that can only be consumed within the current compilation unit, see also [this SO answer](http://stackoverflow.com/a/11203867/1521179). Whether you use a `sealed trait` or `sealed abstract class` (as the Scala standard library does) is pretty much irrelevant. – amon Dec 17 '14 at 15:59
  • 2
    @valenterry Why don't you use pattern matching? `result match { Left(failure) => ... Right(success) => ... }`. Less error-prone too since you can't do stupid things like `if result.isLeft() result.right()`. Creating a separate type that just aliases the identifiers is pretty hacky. – Doval Dec 17 '14 at 16:08
  • I see. So there is no way to make my CompileResult behave like an Either. @Doval that may solve this specific problem in some cases, but not the general problem. What for example about a custom toString() method for the CompileResult? – valenterry Dec 17 '14 at 16:14
  • @valenterry The same could be said for any type. What if you wanted strings to have a different hashCode? That doesn't mean strings should be subclassable. Nothing stops you from writing a regular function that takes an `Either[CompileFailure, CompileSuccess]` and prints it the way you want it. "Universal" methods like `toString()` and `hashCode()` and `equals()` are mostly a convenient default. You should assume any type can be compared or hashed or printed in multiple ways (because they can be) and write code that allows custom comparators, hashers or printers to be used. – Doval Dec 17 '14 at 16:24
  • Well, yes, I can extend strings right? Would be even better if it were an interface/trait though. But okay, you say, write a regular function to print it how I want. Now, where do I implement this function? – valenterry Dec 17 '14 at 16:37
  • @valenterry Java `String`s are `final`. And that's a good thing, because given a `String`, you know exactly what it'll do, every time. If `String` were an interface, given a `String`, there's no *guarantee* it'll behave correctly since anyone can write some `BogusString` class that claims to implement the interface but doesn't; which means there's no guarantee your `String`-using code will work as intended. Anyways, you can implement the function anywhere, you just need to have it available whenever you want to stringify an `Either[CompileFailure, CompileSuccess]`. – Doval Dec 17 '14 at 16:44
  • Yes right, but I can use the `StringLike` type. So where is the corresponding `EitherLike`? Also, yes, that happens when inheritation is used. Someone can overrite incompatibel `hashCode` and `equals` to deliver inconsistent results. You can't do anything about it. Types only promise you THAT the method signature is available, not that the implementation is correct. And I don't want the stringify function anywhere. That is not how good software design works. – valenterry Dec 17 '14 at 16:49
  • `Types only promise you THAT the method signature is available, not that the implementation is correct.` A `final` class is either always correct or always incorrect, because any time you hold a variable of that type, the implementation is always the same. That's not true of non-final classes or interfaces, where the correctness depends on which particular class you're holding at the time. `And I don't want the stringify function anywhere. That is not how good software design works.` I'm not sure what's your line of thinking here. Even Java's core APIs allow custom `Comparator`s. – Doval Dec 17 '14 at 17:23
  • A `class` is not a `type`, so you are right. And yes, if you have only one class for a type (or a class only, which is essentially that), then you are also right. But normally you should not care about the implementation and just care about the type so that the implementation can change. That is good, not bad. So, when we have StringLike in Scala, where is my EitherLike? – valenterry Dec 17 '14 at 17:44
  • `But normally you should not care about the implementation and just care about the type so that the implementation can change. That is good, not bad.` It's not always good to be able to change implementations *in the middle of a program*. E.g. A `ClassLoader` needs to handle strings with class names. It would be a horrible security hole if you could feed it some bogus mutable string. The implementation details of `String` is irrelevant and can change (and it has), but it's important that all `String` variables contain the same known-to-be-correct implementation throughout a run of the program. – Doval Dec 17 '14 at 18:00
  • `ClassLoader` belongs to metaprogramming, so you should not use it unless you have no other choice (because of the mistakes of others or language limitations). If you use metaprogramming you have no compiletime safety anyways. I also can't see any security risks here, else then having a classloader load some classes, provided by external input. Bogus Strings do not change anything here if the external input can load/execute any class he wants (and therefore is able to do almost everything). – valenterry Dec 17 '14 at 18:19
  • @valenterry I didn't say you can have permissions to load anything. But if you can inject your mutable strings, you can defeat any validation by mutating the string after validation but before loading (e.g. from another thread). Class loaders are just an example; you could pull the rug from under any other kind of security validation, like file paths. Where correctness is important, you can't accept arbitrary implementations at runtime. Anyways, this discussion has grown too long, so that'll be the end of it from me. – Doval Dec 17 '14 at 19:39

1 Answers1

10

The change to Either was made on April 24th, 2008 in commit c0b21797bde81861305dd68853add2d8bd46e484 "Changed isLeft and isRight to use less memory.":

Changed isLeft and isRight to use less memory.

Changed either from a sealed trait to a sealed abstract class to allow exhaustiveness checking.

All changes per the discussion in #797.

The referenced bug report is SI-797 "lazy vals on Either should probably be defs":

This comment by David R. MacIver is most illuminating:

While I'm making suggestions, wouldn't it be better if Either were a sealed abstract class so you could get pattern match warnings?

Note that in the meantime, Scala actually does support exhaustiveness checks for sealed traits as well as sealed abstract classes, so this would no longer be necessary, however, there is also this comment by Geoffrey Alan Washburn:

Given that Left and Right are final, I cannot see any sensible way someone could be using Either as a mixin.

So, it still makes sense for Either not to be a trait.

As for Option, it was sealed in commit 0bef86d8e8b7ea7ebb790ebcec7fedcb9a24f5a8 on March 9th, 2006, however, it was an abstract class even before then.

The change from trait to abstract class was made in commit d7007f7a9607481eb73b8df587e3c52cf4272147 "Use 'mixin class' instead of 'trait'" on March, 3rd, 2006.

As for why they aren't interfaces, the answer to that is rather simple: Scala doesn't have interfaces, at least not in the sense of Java or C#.

Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318