Hacker News new | ask | show | jobs
by jez 1720 days ago
I like James Iry’s take on checked exceptions here[1] which basically boils down to this:

> The throws clause is the only point in the entire Java language that allows union types. You can tack “throws A,B,C” onto a method signature meaning it might throw A or B or C, but outside of the throws clause you cannot say “type A or B or C” in Java.

In languages with better support for ad hoc union types, I think both the need and desire for checked exceptions fades. I wrote a little bit more about how two translate between the two concepts and what the benefits of using ad hoc union types are in this post in a blog post[2], but my point is that having a proper, fully fledged type system feature that composes with all other features of the type system is what drives most of the headaches away.

[1]: https://james-iry.blogspot.com/2012/02/checked-exceptions-mi...

[2]: https://blog.jez.io/union-types-checked-exceptions/

2 comments

It is pretty expensive performance vise
Is it? The VM is already already storing the tags on objects to be able to down cast. For example, in Java, the JVM is already storing the bits to remember that your `x` is a `Child` to safely raise an exception for

    Child castToChild(Object x) {
        return (Child)x;
    }
So at least it's not expensive from the standpoint of "uses more memory."

Maybe you're saying that it's more expensive because dynamic dispatch on a type like (A | B) will be slower than static dispatch on a type like A, but most languages lacking ad hoc union types will already have other ways to do dynamic dispatch (parent classes, interfaces, etc.) so it's not like adding ad hoc union types solely incentivizes people to write slower, dynamically dispatched code.

Maybe when you say "performance" you mean that the performance of the type checker is slower? That's definitely true—as ad hoc union types get larger (dozens, hundreds, heaven forbid thousands of variants) the type checker will definitely struggle.

That being said, there have been some impressive optimizations in practice to speed these up (I've spoken with members of the TypeScript team touting some really cool improvements to their union types, and I've seen members of my team submit similar but not quite as good improvements to the union types in Sorbet for Ruby). So I definitely agree that a priori, they're going to be slower to type check, but I think that they can be made fast in common cases, and their benefit in terms of power added to the type system is just a really, really good tradeoff.

So I'm not sure I agree on face value that adding ad hoc union types to a language necessarily come with a performance expense.

My understanding is that it is not the exceptions themselves, it is filling up stack traces which is considered performance taxing thing to do in jvm
Depends on how it is compiled. There’s no reason in principle the two can’t have the same performance, it’s just a difference in ergonomics.
class A extends UnionBase;

class B extends UnionBase;

public UnionBase someMethod();

furthermore, UnionBase might have isA() and isB() if need be.

So, strictly speaking, your statement is incorrect.

In this case UnionBase is not ad hoc: you had to declare a name for it up front. Ad hoc means you can write the union type wherever types can be written. For example, it’s not possible to write this:

    public (A | B) someMethod();
in Java.
Yes, this is the basic benefit of typed languages. I would not want to refactor a 20 year old code base without type safety - I do want to know what's being returned.

I also want my tools to know what's being returned, as well as my compiler.

But in languages supporting this you do know that it's either A or B and you can use match expressions to deal with both cases.

Usually these are the languages with more focus on type safety, like OCaml, Haskell and Rust.

To be fair, in OCaml, Haskell or Rust the type "A | B" also had to be declared beforehand.
Not exactly. For example, in Rust, you can do unions for traits: https://play.rust-lang.org/?version=stable&mode=debug&editio.... OCaml has the same system for its objects, where objects are structurally typed.
Yes, but it doesn't really change the way you could handle it, and what I wanted to point out is that it actually helps with type safety, enabling you to create total functions.