Hacker News new | ask | show | jobs
by bcherny 2438 days ago
> I wish there was a language that let you move gradually from one end to the other, exactly when you need to.

This is precisely what gradually typed languages — like TypeScript, Flow, and typed Pythons — solve!

I talked about this on Software Engineering Radio last week: https://www.se-radio.net/2019/10/episode-384-boris-cherny-on....

4 comments

On that note, I'd include Erlang. It's not gradually typed, per se, but you can have a fully dynamic language (no type specs, no Dialyzer), a completely optimistic static analyzer for inferring types and warning where it's inconsistent (Dialyzer runs), and then you can add specs where needed to tighten up and improve what Dialyzer can catch, to basically be a fully static language.
It's relatively easy, but not free, to do this. I find that the erlang (and elixir) guides seem to be a bit scant on best practices to achieve this level of discipline, for example, wrapping all gen_sever calls in module functions and presenting a well-defined API for the genserver module (and possibly, even linting for no naked genserver calls) is not really explained in this light. Similarly guidance is not provided for wrapping enum module calls (since that similarly destroys typing information)
Yeah; it requires some rigor to do. My point was simply that it _can_ be done, and while the effort is high, it does allow you to move from pure dynamic language, to highly defined type checking.
Dialazer is pretty bad and not even close to a static language.
If you fully spec out your code, it's actually quite close. In a project we did that in, the only type errors we encountered were ones that a static system would not have caught either (due to their being caused by incoming data that did not conform to our type expectations; for instance, deserializing JSON to a specific type).

Without specs, it will assume every type is 'any()', unless it has information to infer something more stringent. For instance, if it sees you add 5 to it somewhere, it will instead assume it is a number. Etc. Even if in practice it actually is a list of some kind (and so that addition of 5 will fail). Which, yes, ain't great. Hence why I said it was a gradual transition; it will catch provable errors (i.e., if you call append on that same variable as above, it will note that there is no type that allows both append, and + an integer, and error), but leave plenty of things uncaught that could have been caught had it known the type in question (via a type spec).

That critique is a bit unspecific but I tend to agree.

I have heard good a stuff about "gradulizer" though. It uses a gradual type system instead of dialyzers success typing.

https://github.com/josefs/Gradualizer

TypeScript is perfectly this. (And other gradually typed solutions; TS is simply the most popular one.)

You have the madness of thousand of developers flinging code at the universe due to the easiness of browsers, JS, and npm.

This results in great speed, but not great quality.

When your project/company now wants quality, you keep your code but transition to types. (In OSS space, Angular and Yarn projects have both done JS => TS migrations of some form.)

Afaik, typescript is pretty bad in terms of catching some basic errors. Types are not enforced. A caller can change sync function to async, breaking the functionality downstream.
You can decide if they're enforced or not. That is part of how it is gradual typing.
Yes gradually typed languages are usually looser/more flexible about static typing.

> A caller can change sync function to async, breaking the functionality downstream.

I think you mean callee?

Are you taking about using a returned result? Because most languages permit ignoring return values.

If you want to check out promise use, check out https://tsetse.info/must-use-promises

Sorry, I did mean callee.

Consider

``` const myIntValue = f(); ```

This code will silently break when f changes from sync to async.

Right, but there will be an error where you consume myIntValue:

  const myIntValue = f()
  
  // Error TS2365: Operator '+' cannot be applied to types 'Promise<number>' and '2'
  const myResult = myIntValue + 2
  
  async function f() {
    return 42
  }
Typescript was exactly what I was going to mention in reply
It's not just about static typing, though. Macros, metaprogramming, being able to reach as deep as you want to, ugly code full of side effects, global state etc. All of those might actualy benefit you when your project is small, and they make development way faster (see Rails). Later, however, they're a definite impediment.