Hacker News new | ask | show | jobs
by cocochanel 2623 days ago
Why is everything moving to types?
9 comments

Generally no one uses dynamic typing for the abilities it gives you. Do you declare string variables to later assign them to numbers? Do you dynamically add new functions and properties to objects? Do you ever really need the flexibility that dynamic typing is giving you?

If not then why are you using a dynamically typed language? If you're not using it's abilities then it doesn't sound like the right tool for the job.

It's like a cost/benefit analysis where none of the benefits you're using, and the cost is the total inability to validate, refactor, and navigate your code base.

> Do you declare string variables to later assign them to numbers? Do you dynamically add new functions and properties to objects? Do you ever really need the flexibility that dynamic typing is giving you?

Yes, yes, and no. I do most of my work in languages that prevent the first two, but when I do have access to this kind of runtime trickery I do use it when useful.

> Do you declare string variables to later assign them to numbers? Do you dynamically add new functions and properties to objects? Do you ever really need the flexibility that dynamic typing is giving you?

The issue is not "Do you purposefully do those things?", but rather "Do you have a call stack where you can't guarantee it won't happen by accident?" Type checking is not relevant when you know what will happen and want the dynamic/ducktyping behaviour.

Another issue is: I'd use a different framework which doesn't use Ruby, but this was the most productive framework at the time the codebase was started, and nobody will port that many lines of code to a non-dynamic language now. So the best course of action is to validate the current code is not overly-dynamic.

I think most rubyists do benefit from dynamic types. How easy would it be to build rspec and Rails in java? The whole dependency injection thing in Spring is in part a by product of types making it way harder to test things. That's just one example.
Can you give a concrete code example you are talking about? What is your problem with DI with spring? Why do you feel it is a problem with static type checker?
DI for testability adds complexity to code and reduces readability. In Ruby it's almost always unncessessary to use DI because in Ruby you can stub at runtime.

In other statically typed langues like Rust the type system itself eliminates the need for a lot of these tests but at the cost of mental overhead of expressing your logic in a way which will satisy the type system.

What jashmatthews said mostly. It adds bloat and isn't very readable. It's another "ceremony" that together with types, interfaces, generics etc increases lines of code. I can see the benefits for huge projects, but I don't like this way by default out of the box.
I believe it depends on the use case. If you notice, Stripe and Coinbase are the first few companies that use the type system. They are both dealing with financial systems and numbers in general where having types would help a lot in catching errors and bugs earlier. I've worked on financial systems before in a dynamic language, JavaScript, and from my experience there would be cases where a number would be passed from a place where it's a string (in a textfield) that then needs to be passed around as an integer at times. Type systems would help catch bugs here or in similar situations.
Because it's easier to deal with your CI telling you that you made a mistake before a deployment, than with Rollbar telling you that you're losing money due to a stupid bug in a case you forgot to test for.
It does seem like there is a fad around moving to types, mostly because of typescript's current popularity.

Will be interesting to see how ruby handles types vs duck typing etc 10 years from now, when the new best practices have been figured out.

It's a sad state of affairs. Every programming language is just copying the 'next cool' feature from another language. Duck typing, deconstruction, functional stream-like constructs you name it. I guess this ends when every language features is copied and we get X omni languages with Y omni SDK's all having same features with different syntax. The thing is that I only really need 1 omni language, not a dozen of them, so I feel that all the feature stealing in the end will be detrimental to all but the best supported omni language.

I guess some companies started fast with Ruby/Python and similar and instead of rewriting to static/typed languages they pushed forward features that would allow them to just continue where they left off at the expense of having a more concise problem oriented programming language that's good for solving specific problems.

Not really, what many seem to keep missing is that programming languages are products like anything else.

One buys into eco-systems, not language features bullet point list.

And there isn't something like an universal eco-system for any kind of business case, hence multiple languages.

Harder to make bugs. Better documentation. Typed code is easier to optimize - just look at Crystal performance. Better IDE/Text editor tools. Simpler deploy / distribution - just copy a binary file. I really wish there will be optional type to prevent nil errors at compile time.
Because TypeScript has proven that a type system can be helpful without being clunky and annoying.
I thought the ML family of languages showed that long ago? I guess TypeScript popularized the notion.
ML family has "type inference", which means the compiler figured out the type even if not explicitly written into the code. However, the language spec is still statically typed - an int will not turn into a string and vice versa (ex: "1").

Javascript and ruby, the underlying types can change depending on where the code is in execution - a variable holding a 1 can turn into a "1" and back (implicit type conversion - try 3 * "3"). This leads to a whole class of bugs not possible in a statically typed codebase where explicit conversion needs to happen - I have no hard data, but I remember debugging this type of stuff far too often and far too many times when I could've spend my time better elsewhere. (but I actually like ruby a lot!)

Type checking is not the same as being statically vs. dynamically typed!

> a variable holding a 1 can turn into a "1" and back

This is true of Javascript, but not of Ruby.

  irb(main):001:)> 3 * "3"
  TypeError: String can't be coerced into Fixnum
People commonly conflate dynamic typing with weak typing, Ruby has the former, but not the latter (with some explicit exceptions, e.g. to_ary and friends).

That's not to say you can't still end up with some interesting problems though -- if we just slightly change your example:

  a = 3
  b = 3
  # later...
  a = "oops"
  product = a * b
  # product is now "oopsoopsoops"
But this isn't due to automatic "weak types" style coercion -- just that Ruby lets you build a repeated string by multiplying a string by a number.
The alternative view is that those so-called 'dynamically typed' or 'untyped' languages should really be called 'monotyped languages' since all variables and expressions have the same type: a giant union of all possibilities.

See https://news.ycombinator.com/item?id=8206562

I don’t know but I hate it. Not sure if I’m in the minority but it sure feels like it. In an ideal world. I feel that types are something that should be dealt with at the IDE level. In fact, there so many things that can be done at that level, but no one has really been brave enough to do so I suppose.
So, what, everyone standardizes around an IDE then? You really think that's gonna unite the vim and emacs camps?

I'm personally tired of staring at variables trying to figure out what they're supposed to be, then having to dive into source to see how its used. C/C++/C# solved that problem, why are we still dealing with it?

C had some typing, but I'm not going to call it "solved" until "numberOfHats = distanceInPixels + weightInKg" is considered a compile-time error due to the three "int" values being incompatible; but "numberOfHats = aliceHatCount + bobHatCount" is acceptable.

How does nobody(?) support this yet?

Python supports some parts: you can subclass int, and you get all of the int methods like addition and subtraction for free, but "distanceInKm + distanceInKm" gives you an int instead of a distanceInKm; and "distanceInKm + distanceInMiles" gives you an int instead of an error.

Rust also has partial support but from the other end: distanceInMiles and distanceInKm can be two distinct subclasses of int, and adding them together is a compile time error. But also adding distanceInMiles with distanceInMiles is a compiler error, because these are basically "completely new classes" rather than "subclasses of int", and so you have to implement add / subtract / stringify / etc for yourself for every type D: (I'm fairly new to Rust so if there is a shortcut there that I'm missing please do point it out)

Every language that has generics supports this via phantom type variables that can encode extra information only in the type system alongside some other type, or with a specific newtype keyword that effectively does the same:

    newtype Pixel = Pixel Int
    newtype Em = Em Int

    pixelWidthToEm :: Pixel -> Em
    pixelWidthToEm (Pixel px) = Em px
You can try to call `pixelWidthToEm` with anything other than pixels and it won't work.

More dynamically, with an open type variable that only exists in the type system:

    data User a =
      User { name :: String, socialSecurityNumber :: String }

    data LogSafe
    data LogUnsafe

    logUser :: User LogSafe -> IO ()
    logUser = undefined

    makeUserLogSafe :: User LogUnsafe -> User LogSafe
    makeUserLogSafe = undefined
We can never log the user unless the user is deemed LogSafe and we make functions that produce log safe users that you have to call before hand, in order to make sure that sensitive data isn't printed to logs.

These are things that have been around for a long time in almost every type system, but people's general lack of interest in using type systems to help them conspires to keep them in the dark.

Here's how you can create a number type distinct from other number types in TypeScript:

    type DistanceInPixels = number & { readonly __newtype__: "DistanceInPixels" }
And a type alias that allows you to create them:

    export type Newtype<T, Tag extends string> = T & { readonly __newtype__: Tag }
    type Pixels = Newtype<number, "Pixels">
I get similar concerns with functions that take multiple strings - how do I make sure I didn't swap the bucket with the key? I've seen enums used here, as well as "wrapper" classes.

In any case, to answer the question of "How does nobody(?) support this yet?", have you heard of https://frinklang.org/ ? It's not a useful tool for most codebases I work on but it's an interesting idea.

You could do this in C++ by storing the unit of measure with the measurement value and then performing unit conversion in overloaded math operators.
(They won’t be subclasses in Rust, Rust doesn’t have classes nor inheritance. They’d be “newtypes”, a struct with one member.)
Do they, the language is called F#.
It rarely happens to me that I stare at a variable and have to wonder what type it is. And yes, an IDE like Rubymine is becoming crazy good at autocomplete and method lookup. I think the experience of developing on Rubymine isn't that far behind from Intellij nowadays. Not everyone have to use the same IDE, the vim or emacs guys will have to find equivalent tools.
Algol solved the problem.
What do you mean by types being dealt with at the IDE level?

Depending on your type system, a well-typed program can eg run faster, because the compiler / interpreter can elide certain runtime safety checks that would be necessary in untyped code.

If your type system is crazy enough, you can even track the runtime complexity of your program at the type level, including whether your program runs in finite time. See eg Dhall (https://dhall-lang.org/) whose type systems only allows programs running in finite time.

I think what GP means is that the IDE for a theoretical programming language could automatically infer types and have you not type any code for that explicitly. It might even not show you types as code at all and by default and overlay/add this info only on request.

Generally, there is this huge disconnect between how code is expressed as text and how it is handled in as a graph structure inside the tooling. It is soon time to move beyond simple text files for code, I believe.

ML family languages (like Haskell) had proper type inference for decades now. And yes, integrating that with your editor/IDE is a good idea.

Depending on what you want to do, you might also want to start with the types and have the computer figure out the implementation.

> It might even not show you types as code at all and by default and overlay/add this info only on request.

Types annotations are often are great documentation, and there's a lot of practical knowledge in the ML communities about what types annotations to show in the source for helping with understand and debugging and which ones to leave out as clutter.

I wrote 'proper type inference' above, because it's much more powerful than the watered down version Go and C++ give you. See eg https://news.ycombinator.com/item?id=8447280 for a Rust example.

In an ideal world, humans write bug-free code ;)

But honestly, if you're asking your IDE to do it, that means you're asking your IDE to do static analysis of your code - and type-checking in a lot of ways is just another static analysis technique. And for a lot of us (myself included) we prefer to catch as many of these bugs as possible using static analysis, instead of waiting for someone to get paged when it causes an outage.

Yes, there's a trade-off, and types can be obnoxious (Java imports being probably one of the worst offenders, C++11 introduced `auto` for a reason), but that's the cost we pay.

Ruby being so dynamic means that without type annotations you cannot infer the type (or types) within a variable statically, so you basically have to eval the program, which in Ruby means basically running the whole codebase.

So, please, be brave and evaluate a 100kloc codebase+deps that may contain a top level `rm -rf ~/`

By IDE level, you mean compile level. Types are at compile level.
No, types can be at compile level. They can also be checked without compiling any time you like. Or automatically. By an IDE, for example.
Almost all good IDEs are essentially interactive compilers. The lines have blurred during the last two decades. For example, QtCreator and XCode use clang to provide code annotations in the editor. Eclipse is built around ECJ, its own Java compiler, which exists mostly to provide information back to the editor and refactoring tools (the editor maintains a complete bidirectional mapping between code as text and code as AST at all times). Code generation is almost only a byproduct there.
I come from Assembly and C, now working in Javascript. One of the reasons I made the move to JS is dynamic typing, getting rid of that administrative pain and now being able to create stuff much faster. Even in large JS apps I hardly ever have type related bugs at all, and when I have one I fix it mostly within minutes, don't need an entirely different language and ecosystem for that.

Now the JS fanboys discovered and moved (from Coffeescript to Babel ESxx) to Typescript they apparently think that they can write beautiful and bugfree software just because of static type checking! Let them please move to C++ or whatever statically typed language and shoot themselves in the foot by making all those mistakes that have nothing to do with static type checking at all! Oh, and of course hitting the wall because they are missing their precious 'any' keyword!

I totally agree that type checking for dynamic languages should be done in the IDE, tooling. But static typing in the dynamic language world is a hype at the moment, so we'll have to go through a wave static type checking frenzy. For my work I look at horrible code bases, perfectly typed and strictly formatted by tslint..

I started using TypeScript back when it was 0.8, before it even had generics. Does that make me a fanboy? I have a project with about 45k SLOC of TypeScript (using Knockout.js for presentation). There is really no way I would maintain that same project without types.

> For my work I look at horrible code bases, perfectly typed and strictly formatted by tslint.

There is no language that can stop people from producing horrible code.

> There is really no way I would maintain that same project without types.

That's bold. Do you think no developer would be able to manage it without TS? In that case you must be a fanboy!

And honestly, are you not using 'any'? And do you think your app cannot crash because of a type error at runtime? And do you trust all the third party libraries you are using that they always provide you with consistent types, also during runtime? I ask this because most TS proponents live in some kind of dream.

I'm sure some developers would manage such a project without TS. Good for them. I wouldn't maintain it that way because:

1. I don't have the mental capacity to keep every single function's argument/return shape in my head, or to manually check it every time I make a change. Unit tests can't deliver 100% code coverage in practice.

2. Nor do I want to perform refactorings with stone age tools like s/setFoo/setBar/g. Setting up type information lets my IDE understand which calls to .push() deal with a native Array and which ones deal with my own class, so it can rename the latter ones when I ask. I can also use tools like "Find References" and avoid false positives.

3. I'm not a one-man-band. My coworkers need to deal with this project too, and new developers need to be introduced to it from time to time, and types serve as documentation and guard rails for them much better than jsdoc or regular comments. (This also serves as a significant barrier against using anything more esoteric like Elm, because nobody around would be familiar with it. TypeScript adds just enough syntax on top of regular JS to keep JS users comfortable.)

---

I do use `any` (and `unknown`), I have no doubts that an edge case can crash my app because I didn't validate something, and I never expect third-party code to work flawlessly whether it has types or not. Rejecting TS completely because "but run time loopholes" is throwing the baby out with the bathwater (or to put it in a more hyperbolic way, being an anti-vaxxer because vaccines are not 100% safe from side-effects). TS and types are an additional safety net/force multiplier†, not a silver bullet. (That said, what is a silver bullet? Because I'd sure like one.)

---

† Only applies to a project that has passed its initial rapid prototyping phase. During wild prototyping rides, types can indeed slow you down. But that's really the same debate as RDBMS vs NoSQL.

C/C++ doesn't really have much static typing to speak of. I don't think you are realizing the power of real statically type languages, such as Haskell, OCaml, or Scala.
Are you thinking of the way ints and chars (and floating-point types!) inter-convert? That is a weakness or convenience, but otherwise the typing is pretty strong.
Not much experience with C++ I suppose.
There is benefit to future proofing code with basic static analysis for type checking.

But it is always an incomplete solution because a) old code needs to be retrofitted, see TypeScript's way of defining type maps for vanilla JS or b) more commonly you keep the code around that's using unsafe types, effectively passing void*|Object|"choose your poison" around.