Live data from Hacker News

Why don't more languages offer flow typing?

ayazhafiz.com

21–30 of 127 posts

Re: Why don't more languages offer flow typing?

#21
post #4

This 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…

Way before 1.0 (IIRC), Rust in fact used to have structural records, and wanted to try to only have them. But due to the challenges described by you and the article, they went back to regular nominal ADTs.

I do think think that was the right call at that time.

Re: Why don't more languages offer flow typing?

#22
post #15

This is called Type Narrowing by the way. Control flow analysis is only one way this works in TypeScript. Most languages don’t have the type system necessary to make this work in a sensible way.

In Kotlin this is called smart casts. It works great.

Doing a null check means that after that you can treat the variable as non nullable. Doing a type check, narrows down the type to what you just checked. Or going down a switch statement branch on the type actually implicitly does the cast as well.

It's both strongly typed and convenient.

Kotlkin even goes a step further and introduced contracts few versions ago that you can bind to functions. For example calling isNullOrBlank() on a nullable string changes to the type to nullable if the answer is false. You can write your own contracts for your own functions even. This is what the source code for this function looks like:

  @kotlin.internal.InlineOnly
  public inline fun CharSequence?.isNullOrEmpty(): Boolean {
    contract {
        returns(false) implies (this@isNullOrEmpty != null)
    }

    return this == null || this.length == 0
  }
I guess typescript does something similarish.

Re: Why don't more languages offer flow typing?

#23
post #4

This 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…

Way before 1.0 (IIRC), Rust in fact used to have structural records, and wanted to try to only have them. But due to the challenges described by you and the article, they went back to regular nominal ADTs. I do think think that was the right call at that time.

In the end, I feel like structural typing is one of those attractive nuisances. It sounds good on the outset, and it does provide a limited amount of convenience, but then it turns out not to be that convenient, and it removes a lot of safety and flexibility by taking intent out of the thing.

Re: Why don't more languages offer flow typing?

#24

The answer is simple: because other languages don't need it - they have different features to deal with it. The author even mentions it: pattern matching. Just that he picks a language that doesn't support union-types. But that doesn't mean that flow typing would be necessary here - it means that the language(s) should support union-types and extend their pattern matching accordingly. In fact, I would say that flow t…

> In fact, I would say that flow typing is almost like a workaround for missing pattern matching.

If flow typing is 'type narrowing' (the article kinda dragged on pulling in unrelated concepts so it lost me), then if anything, it is at least as good as pattern-matching/switch-blocks. At least in Typescript. That is because it works in switch statements and non-switch statements and provides the same guarantees. I don't get this argument.

Consider something like:

    data: {pages: number} | null = f()
    if (!data){
       return 0
    }
    return data.pages + 5

Sure you can switch on the 'data' as well (exhaustive type-switches are a thing in Typescript too), but thats a syntactic preference. It surely is nice to get the same guarantees without _needing_ a switch statement.

Re: Why don't more languages offer flow typing?

#25
post #17
post #4

This 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…

Another very common case in TypeScript (and other languages) is union types. Other languages have constructs for checking the underlying type of a union type directly, but typescript is smart enough to figure it out based off of the properties of the constituent types and what you've checked so far in your code.

e.g.

    interface A { type: 'a' }
    interface B { type: 'b' }
    type C = A | B;

    const c: C = ...;
    if (c.type === 'a') { /* c is of type A here */ }

Re: Why don't more languages offer flow typing?

#26
Nobody's mentioned dependent typing yet, but languages like Idris and Agda will "narrow types based on control flow" (in fact, since they're pure-functional, control-flow is the same as data-flow). For example, zipping two vectors of the same length:

    zip: Vec n t1 -> Vec n t2 -> Vec n (t1, t2)
    zip Nil Nil = Nil
    zip (Cons x xs) (Cons y ys) = Cons (x, y) (zip xs ys)
Here the 'Vec' type has two constructors, Nil and Cons. Since their types specify the same length, the type-checker knows we can ignore the 'zip Nil Cons' or 'zip Cons Nil' cases.

Re: Why don't more languages offer flow typing?

#27
post #16
post #8

fyi Java 16 did add pattern matching for instance of so you can avoid the cast.

I was going to point this out, too. I thought it also supported cases like: if (foo instanceof Bar) { // foo is typed as Bar in this block } Is that true, or did I get that wrong?

  if (o instanceof String s) {
    var foo = s.indexOf("bar");
  }
Java 17 introduced a preview of the same feature for switch statements

  return switch (o) {
     case Integer i -> String.format("int %d", i);
     case Long l    -> String.format("long %d", l);
     case Double d  -> String.format("double %f", d);
     case String s  -> String.format("String %s", s);
     default        -> o.toString();
  };
Future versions will have improved support: http://openjdk.java.net/jeps/420)

Re: Why don't more languages offer flow typing?

#28

Earlier quoted context omitted.

Way before 1.0 (IIRC), Rust in fact used to have structural records, and wanted to try to only have them. But due to the challenges described by you and the article, they went back to regular nominal ADTs. I do think think that was the right call at that time.

In the end, I feel like structural typing is one of those attractive nuisances. It sounds good on the outset, and it does provide a limited amount of convenience, but then it turns out not to be that convenient, and it removes a lot of safety and flexibility by taking intent out of the thing.

I think the majority of the convenience of structural typing can be had with explicit casting between structurally equivalent types. It eases pressure on the type checker (checking that two types are structurally equivalent is pretty quick, checking that any expression satisfies something structurally is a little harder) and gets you ultimately what a programmer wants - convenience.

Re: Why don't more languages offer flow typing?

#29
So 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) could use that information at runtime to optimize method dispatch and other operations?

Likewise with the flow pieces mentioned here, I gotta think that the VM could optimize out certain runtime checks on dispatch or conditional branching if it knows that the compiler has already checked for these?

Re: Why don't more languages offer flow typing?

#30
Contrary to what many comments here state, I don’t think this is only useful in a dynamic runtime kind-of environment. I often think I could use some form of this in Rust and/or Haskell. In a very specific way:

I often want a single constructor/branch of an enum (sum type) to a be a type as well, specifically a sub-type of the full enum. So once I learn about what branch a value is, I can treat it like that and even first class pass it around – without unpacking.

I think this should be implementable in Rust without any runtime overhead as pure syntactic sugar. You get the same effect by creating a new struct for every set of values per branch and using those wrappers instead.

Post reply on HN