Generally speaking, the more expressive a type system, the harder or more expensive it is to have full inference. If a language chooses to have performant type inference for all of its valid programs, then it often ends up with a type system that rejects more programs. A classic example in Haskell is (\x -> x x) (\y -> y)
which is not valid because inference is undecidable (although iirc you can write that with some extensions and explicit type annotations).
I chose to accept more programs instead of full-blown type inference early on in our journey for three reasons:
-
We must avoid breaking changes (i.e. rejecting valid programs today)
-
If you don’t want to write the types, you can already not write the types today (and strong arrows/type inference from patterns and guards do their best to find errors)
-
Users of languages with type inference often recommend writing the types anyways (at least for public functions, private functions can make use of other techniques such as inlining to find errors)
I believe there will be a paper at POPL 2023 on type inference of set-theoretic types which will go into more details. However, it will be too expensive to make it part of the compiler. The types it infers though are incredibly precise. For example, imagine you have a list of integers and strings, and you want to get only integers:
Enum.filter(list_of_ints_and_strs, &is_integer/1) #=> [1, 23, 56, ...]
In most programming languages, filter has the type: [a], (a -> boolean()) -> [a]
, which means both incoming and outgoing list still has the type [integer() or string()]
, even if you keep only integers. However, with set theoretic types, we could get (and infer) the type:
[a or b], (a -> true) and (b -> false) -> [a]
Which in our example it would instantiate:
[integer() or binary()], (integer() -> true) and (binary() -> false) -> [integer()]
And therefore that the type system knows you removed all strings from the list. Being this precise has a high cost during inference (and we want to be precise!). At best, we could be able to use it as an explicit command like “what is the signature for this function” but not as part of compilation.
One last note: a type system with unions and intersections do not necessarily make it a set-theoretic type system. To be set-theoretic the foundation of the type system must all be based on set semantics. You can implement set operations in other ways.