I'm trying to design a programming language that can be compiled into C99 and it's not at all clear to me how you represent these things statically.
The idea from a programming point of view is really nice and in a dynamic programming language environment everything is an object and you can pass whatever through any function. This doesn't work all that well if you want to translate your program into a statically typed environment, in a cost effective way.
You need some way to reason about the type information that hopefully doesn't create memory allocations all over the place.
This can be done by introducing tagged data types but it can also lead to a combinatorial nightmare where you need to generate a lot of extra code.
In general, I don't think all this complexity is warranted in a statically typed environment and depending what you are doing this doesn't end up being that important. Even if, it's a nice to have, it's mostly that, a nice to have.
Typically you wouldn't change the compiled code too much, it changes what is valid to type check and inserts casts as necessary. In general, flow typing just makes more programs type check.
You have to emit the plumbing for something like flow typing to work. You can't omit the type checking because you cannot account for all the possible call sites at compile time, that will lead to a combinatorial explosion. You do end up generating code to pass arguments with temporary type information.
Take the example of a variant type, which is what I'm most interested in.
You have something like type X = int32 | string | boolean | SomeCustomType
When you then write a function that accepts X as a type you need to be able to pass any of these things into that function. To do that you need a auto user defined type that can carry the information, in C that might look like this
struct {
union {
int a;
char* b;
boolean c;
} data;
Tag type,
}
then your type assertions can work on this. If you wrote this in my fancy language:
if x != nil {
// x cannot be nil here
x.foo
}
the equivalent C code for this would be something like
void f(X x) {
if (x.type != WELL_KNOWN_NIL) {
x.data.foo // we know it's safe to access foo (assuming we typed X as Foo | nil)
}
}
Trying to solve this at compile time without any runtime checks might be possible but I don't know that it would be better, it might be too slow if I have to solve some nasty combinatorial problems. This is at least somewhat easy to explain.
The idea from a programming point of view is really nice and in a dynamic programming language environment everything is an object and you can pass whatever through any function. This doesn't work all that well if you want to translate your program into a statically typed environment, in a cost effective way.
You need some way to reason about the type information that hopefully doesn't create memory allocations all over the place.
This can be done by introducing tagged data types but it can also lead to a combinatorial nightmare where you need to generate a lot of extra code.
In general, I don't think all this complexity is warranted in a statically typed environment and depending what you are doing this doesn't end up being that important. Even if, it's a nice to have, it's mostly that, a nice to have.