This question actually has a simple answer: Most languages have embraced statements over expressions for a lot of language constructs. This causes a lot of issues: An if-statement is in essence a function from boolean to unit/() (i.e. from a barely useful type to a type with no useful information), while an if-expressions will contain enough type information to at least provide this kind of “flow typing”, (even if it…
Almost all of the languages I know that do "flow typing" (TypeScript, Flow, Hack), "smart casts" (Kotlin), or "type promotion" (Dart) are fairly statement-oriented. Expression-based languages tend to have pattern matching which provides another way to solve the same problem. Flow typing is most useful in imperative languages where code like this is common: if (foo is! Bar) return "not a Bar"; foo.someBarMethod(); Ear…
Why don't more languages offer flow typing?
111–120 of 127 posts
Re: Why don't more languages offer flow typing?
#112Earlier quoted context omitted.
> In these languages, this sort of narrowing is a great deal more complicated, because it implies a new type, which will probably require more allocation. Flow typing need not imply any kind of "narrowing", though. It could simply endow a control-flow branch with a statically-typed proof object, asserting whatever post-conditions were established in the branch. The "narrowing" could then become a separately-coded ste…
That sounds great at the napkin-sketch level. But if you mean what I think you mean, now your entire compiler suite needs to be reworked to change its internal concept of what guarantees a "type" has. Previously the compiler was able to assume it was a contiguous chunk of memory, but now it needs the concept of a non-contiguous chunk of memory representing a certain type, and it needs everything everywhere updated to…
Some things that might have triggered warnings no longer need to, because if the runtime conditional evaluated to true there's no way the warning could take effect.
Consider this little bit of C code:
void foo(int x, unsigned y) {
if (x > y) {
printf("but it's bigger!?");
}
}
Due to the idiosyncrasies of C if you call foo(-1, 1) the test will promote x to an unsigned and the test will say x is bigger than y. There's a warning in most C compilers for it, because it's the sort of invisible bug that'll wreck your code.What's annoying, though, is if you do this:
void foo(int x, unsigned y) {
if (x >= 0 && x > y) {
printf("but it's bigger!?");
}
}
And still get the warning. The compiler should be able to infer that the test x >= 0 means there's no fault path any more. Clang 13, at least, still warns.A C compiler using flow typing would not issue a warning for the second implementation of foo. Neither x nor y change representations. The execution path through your compiler for producing byte code doesn't change. A few of your tree structures might pick up an extra annotation for the provable facts the warning generator can use to improve its output, that's all.
Re: Why don't more languages offer flow typing?
#113Earlier quoted context omitted.
Yeah, the same code would look much clearer in a language with union types. Heck even Swift cribbed them. switch resp { case Result(val, meta): doStuff(val) case Error(msg, code): logger.main.debug(msg) }
You mean sum types, not union types (TypeScript has union types, your example uses sum types.)
Re: Why don't more languages offer flow typing?
#114Earlier quoted context omitted.
Yes, that's correct. In the GP example, c was const, so the type can be determined at compile time.
I was writing TypeScript, where const just means the variable is not reassigned. For instance this is valid: const c: C = Math.random()
Re: Why don't more languages offer flow typing?
#115This is not a complete answer, but covers some languages. If you program too exclusively in dynamically-typed languages, you can be too used to not thinking about how physically large your types are, because you work in a world where everything is boxed, and allocations so plentiful you don't even hardly have a way of thinking about them because your language does them at the drop of a hat, and so on. But there are m…
Isn't this sort of an orthogonal problem? Flow typing implies that your type is in some way unknown at the time the code fragment is evaluated. I think this can mostly happen in two situations: 1) The type is generic: A function may be called with a different type on each call site - but for each particular call site, the type is known at compile time. 2) The type is polymorphic - i.e. the full type is not known at c…
Take rust. You have a function that on one path of a branch returns an int, in the other, a float. Rust says that's an error. I say your function returns a union type :)
And this is totally pervasive. A function which returns an int in an if branch (w/out an else) and elsewhere returns nothing actually returns an optional.
A variable which is assigned in one branch to a reference to an int (an l-value in languages that screwed this up, like rust; a ptr int in languages that, sensibly, use types to encode this property, like algol68) in one branch, and to an int value in the other, is technically a union type. If that union is added to a to a float - that shouldn't be an error! The int should be widened, the ref int should be dereferenced, then widened.
OTOH, if you try to assign an int to that union - now that is an error, because values can't be assigned to. You can only assign to types which represent a location in memory, like ref int, unlike int.
In the above discussion, the effect of control flow on the types is critical. Languages like rust ignore this, and to my mind, their ergonomics suffer considerably because of this. C++ side-steps this through statement based syntax, which makes variant types clunky.
Union types are beautiful and powerful, but there seems to be a lack of appreciation for the subtle and deep ways they impact language design. You have to design them in right from the beginning; it's a tricky thing thing to get right, and doing so absolutely requires flow typing.
Re: Why don't more languages offer flow typing?
#116Earlier quoted context omitted.
The types of the vectors: the length is a generic parameter to the type. So a Vec 4 Int and a Vec 5 Int are different types .
Oh right, the output is parametrized by the input. I suppose this is what dependent types are. That's really awesome. Thanks for the explanation.
Dependent types is really about the ability to parameterise types over values, and reason on those values-dependent types.
Re: Why don't more languages offer flow typing?
#117I'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 type…
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.
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.
Re: Why don't more languages offer flow typing?
#118So I've been working on a personal project which is in a mix of C++ and TypeScript -- partially because of practicality and partially to learn TypeScript, and I'm really liking the language. One question I have is whether there's any possibility that TypeScript could, in the long run, gain performance advantages over pure JS? That the compiler could leave behind some type information artifacts so that V8 (or similar)…
When you write your code in a way that is akin to what you would do in a static environment, V8 emits additional type checks, if these pass, then it will run your code through a optimized version of the code that makes certain assumptions about the data types in use. If these type checks fail, then V8 will deoptimize the function/code.
Deoptimization needs to happen because the assumptions made about the execution of the code was wrong and the optimized code cannot handle this special edge case. V8 will then revert to less optimal code for the specific case but this code is more general and can handle the special case that occurred.
V8 can and will toggle between optimized and unoptimized versions of your code now and then but it has limits. If it cannot settle on a version of your function that is optimized it will stop trying to optimize the code because the cost of doing so is significant.
When you write your JavaScript as if it was more statically typed than it actually is, you do enjoy optimization benefits from V8.
Re: Why don't more languages offer flow typing?
#119Earlier quoted context omitted.
That depends on where it's used. For example, Rust's traits bounds are structurally typed, for exemple making a function that accepts a type that's Summary + Display. This "Summary + Display" type doesn't need to have a name, and in that case it's great. On the other hand sometimes you want to have two strings, one that's a name and the other the address, and not substitute one with the other. I don't think there is…
> For example, Rust's traits bounds are structurally typed, for exemple making a function that accepts a type that's Summary + Display. This "Summary + Display" type doesn't need to have a name, and in that case it's great. That's still nominative. The bound is not named, but it's based on names, not on structure. Otherwise you couldn't have marker types in such a bound, or would always have all of them and be unable…
From my understanding, nominal vs structural typing is about how you consider group of types. For structural typing, types that contain the same thing are the same. For nominal, that isn't the case.
Re: Why don't more languages offer flow typing?
#120Earlier quoted context omitted.
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 |…