|
|
|
|
|
by shawnz
2104 days ago
|
|
How aren't Java exceptions already polymorphic? "catch (Exception e)" will also match subclasses of Exception, won't it? And in the composition example, the compiler would correctly see that f(g(...)) can throw any subclass of E1 or E2. The only problem admittedly is that there's poor support for checked exceptions when used with lambdas, but isn't that just a language problem and not a JVM issue? Although it's not possible to infer the exceptions automatically you can write a function to compose functions with exceptions and it will be checked at compilation time like you'd expect: https://gist.github.com/shawnz/5e9a0d344a6a693b46c662c5c8124... (EDIT: Actually they can be inferred to some extent.. example updated) NVM. I see you addressed the possibility of doing this already. |
|
- Subtype polymorphism lets us say things like 'throws Exception', 'catch (Exception e) {...}', etc. and this will work for any sub-class of Exception, e.g. 'FileNotFound'. This works by upcasting: essentially discarding some of the information about the type, so the intermediate code can rely on a smaller interface. If we try to downcast it later, we need to handle the possibility that it doesn't match; e.g. we can write 'catch (FileNotFound e) {...}', but that won't remove the 'throws Exception' annotation, since we haven't handled the other possibilities.
- Parametric polymorphism (AKA generics) lets us say things like 'throws E', where E can be instantiated to any specific class, e.g. 'FileNotFound'. This doesn't upcast: the full type information is propagated (but the generic steps aren't allowed to use it). We don't need to downcast: the type checker will instantiate the generic types to that specified by the source, and see if the destination type matches. If 'E' is instantiated to 'FileNotFound', and we write 'catch (FileNotFound e) {...}', then the annotation will be removed, since there's nothing else to handle.
Hopefully the problem with the generic approach is clear from your example: we have to re-implement things over and over for different numbers of exceptions ('FuncThrowingOneException', 'FuncThrowingTwoExceptions', etc.)
Thinking about it, the situation is similar to Haskell's "constraint kinds": Haskell can "constrain" types (i.e. require interfaces), e.g.
This is roughly equivalent to the Java: The GHC compiler has an extension "constraint kinds", where the constraints are treated more like normal values (similar to exceptions). The interesting part for this scenario is that each constraint is treated as a single value, so something like "(Show a, Show b)" is a single (tuple) value. Yet the type checker is smart enough to look inside such tuples, e.g. it knows that "(Show a, Show b)" implies "Show a", etc. It also doesn't care about order, e.g. "(Show b, Show a)" will work just as well; or nesting, e.g. if "c1" is "(Foo a, Bar a)" and "c2" is "(Bar a, Baz c)" then "(c1, c2)" is equivalent to "(Foo a, Bar a, Baz c)".Those are the sort of features that would make generic exceptions much nicer, since we could put 'throws E' on everything, and be able to instantiate E to a single exception (like "FooException"), or a tuple of multiple exceptions (e.g. "(FooException, BarException, BazException)"), or a tuple of no exceptions "()". There's probably a way to encode this already, but it would require manually packing, re-arranging and unpacking the exceptions at every use-site.