Hacker News new | ask | show | jobs
by oftenwrong 2583 days ago
Rails is ideal for the "Get Shit Done" approach to development. However, Ruby's type system does not provide many assurances, making mature codebases harder to maintain, refactor, and extend (as compared to codebases in, say, well-written Java). I am surprised this was not listed as one of the criticisms. Sorbet, a type checker for Ruby, stands to make this criticism less valid: https://sorbet.org/
2 comments

I have to be honest, I don't understand why people care so much about typed languages.

I almost never face type related issues, and when they occur they are the easiest to catch.

I used to think that, as a C programmer used to passing void-stars around bristling against C++ in 2001 (which, to be fair, was not a good language. I continued to think it through a years of writing Python and Ruby. Then I picked up Go, which I thought I would hate due to typing, and: the opposite thing happened.

Particular example: every time you've accidentally pulled the wrong key out of a map (because that's how everyone does structs in Ruby and Python and Javascript) and gotten a null pointer exception: those were probably type related issues, and they're easily buried and discovered only in production, or through stupidly intensive unit testing, much (not all, but much) of which exists mostly to cover for (wait for it) lack of typing.

Nobody knows where The Citation came from. He passed many a Void Star on his way to Earth.
Passing a string where you expect a number is not the only kind of type errors modern type systems are about. They can catch a particular class of domain-level bugs, and more importantly, it acts as a pair programmer who tells us about all the logical edge cases that we forgot to think about, as we program.

This is done using what is called "sum types" - we can tell the compiler that say a user can be "Premium" or "Regular". This distinction will affect policies across the codebase - eg. Premium customers get a discount on their shipping costs. We'll have robust unit tests and integration test that cover all these policies.

But even with the tests, we run into trouble when the feature request to create a new type of User comes in - "Semi-Premium" who gets only their _domestic_ shipping free. We now have to hunt and peck across the codebase, and change all our policies so that it handles this possibility well. Our tests are of no help here - they are meant to verify existing facts about the system, and can't tell when this user fall through certain policies because we forgot to handle it there. The type system on the other hand knows exactly the places where we decide things based on the kind of user. It will then realize that a new type of user has come in, and our policies don't handle them. This turns what usually is a high-risk and difficult task into an almost mechanical one.

What's happening here is that we're telling the compiler more things about our domain. This means the compiler can then remind us as we go about our business writing code, to handle every case, to never pass a null, and to help model our data structures that prevents inconsistent states from ever happening. The more we can encode in types, the more powerful this becomes.

> ...We now have to hunt and peck across the codebase, and change all our policies so that it handles this possibility well.

A stellar case about Object design, not strong typing. I don't find type solves this issue, in fact, it even might be the opposite. Its way looser and more flexible to be able to say how things actually behave than to define the exact type they are to see how they behave.

The static analysis is definitely a huge plus, but it's a bit tangential: if a static analysis tool were able to point to the same issues, the reason would evaporate. (It's like saying a language is better because it has a better community, which is practical but not intrinsic).

Objects don't solve this. Untyped programming is more flexible, but are able to express less things about the domain explicitly than typed languages (not Java/C#, but rather OCaml/Haskell etc.).

> if a static analysis tool were able to point to the same issues, the reason would evaporate.

This static analysis tool is called the type checker. The experience of using a type checker is best when the underlying language has sound types. Sorbet, TypeScript, Typed Racket etc. bolt a type system on top of an existing language, and so don't have a choice but to be unsound, and so their qualitative experience isn't as nice as it could be. Even so, they're highly recommended for large codebases.

> It's like saying a language is better because it has a better community, which is practical but not intrinsic

Not at all. This is an intrinsic feature of programming languages and has a huge effect on how we think of programs.

It is however not a silver bullet and when building web applications, practicality - tooling, ecosystem etc. trumps everything else. However if you run into difficult problems which require deep attention to data modelling, and wish for a better tool to help frame the problem, then Typed Functional Programming might be a good try.

Static typing is not very important for new, small projects where the developer can keep the whole thing in their head. It can even be a drag during the design phase if the design is changing rapidly due to the added overhead of changing interfaces. Progress has been made reducing the overhead in recent years and proponents may disagree.

Fast forward to when the same project grows and matures. Now it is huge and no one knows every subsystem. The original developers skipped town years ago. There are -1x to 10x developers on the team, and contractors who must "hit the ground running." Static typing is now crucial to prevent the whole thing from collapsing like a house of cards.

I recommend the best of both worlds, such as prototyping in a productive language, then port when the design solidifies. Or, use a language that can be gradually typed later as your startup matures into an established company with customers that rely on it.

Type errors aren't the problem, understanding what the code is doing is the problem. I like to know what I'm looking at -- is it a primitive? A simple array? A complex, stateful object? This helps establish clearer relationships between systems.
I have a talk where I turn "you called a function from the wrong thread" into something caught by the type checker in C. Getting an error message at compile time pointing out the specific line with an error, rather than segfaulting at a later point in roughly 2/3 of executions, is a big improvement in developer experience.

Slides here: https://github.com/dlthomas/using-c-types-talk/tree/master/s...

The example is a toy but the technique was developed for (and very successfully used in) a production setting.

"where I turn" This wording really hilights something that I think a lot of dynamic typing proponents don't fully appreciate. Using a type system is a learned skill, not something you magically get for free. You need both a good type system and the experience/education to use it to its fullest. Encoding threading rules in the type system isn't something C ships with out of the box, and it's not something a lot of people would think of, but moving that into the type system rather than relying on unit/manual test is a huge win.
Absolutely. That's pretty much the point of the talk I mentioned (with some hope of helping the audience get better at it). The only thing I disagree with is needing a good type system - "need" is too strong; the C type system isn't good but is enough to be pretty useful if you use it right.
How many team members contribute to your codebase? I've personally found that I need it less when it's just me committing code, but need it at my large company when traversing unknown codebases.
Even when its just me, if I leave a project for >1 month and then try to come back and pick up where I left off, I have a much easier time getting back up to speed with a typed language vs un/dynamically typed.
I agree that avoiding type-related issues are not a particularly big selling feature. I, however, like typed languages because the tooling tends to be substantially better, as tooling is much easier to build when you have type information available. Tooling makes my job a lot easier, faster, and, in my opinion, results in better code long-term. We're getting much better at building tools for dynamically-typed languages, but there is still a gap.
Yeah this was one thing I missed going from Java to ruby 10 years ago.
Type-related issues are super common in rails apps with several contributors. Do you ever get "undefined method foo for nil:NilClass"? That's a type error.

There isn't really a universal agreement on how to handle nil in the ruby community, and as long as that remains true, type issues will continue to be a daily reality of using the language.

I don't find nil errors to be as cumbersome as the over-design required to type everything you do.
That's the beauty of gradually typed systems like sorbet. You only define types where you need to. Plus, it's really only one extra line of code per method. And that extra LOC gives you a lot.

So many of the production bugs I've encountered in my rails career (probably around 2/3rds) would immediately go away with a type system. Now, that's not to say that I won't encounter other issues, but type systems provide other benefits as well. Typescript annotations make JS seem pretty sane, and editor integrations like VSCode's support for TS is amazing. You get go-to-source functionality that isn't based on heuristics, compile-time error reporting as opposed to runtime error reporting.

Sure, it might not make sense in your situation. But every single job I've had in my career would've been much easier with a type system, and depending on how solid sorbet turns out to be, working in ruby might look quite a bit different in the next few years.

Ease of refactoring is the big one, especially with an IDE since you'll catch a lot of problems as you write.

However, any decent IDE will work with type hinting (putting types in the comments, basically), and as long as you're fastidious about using them, you can realize those same benefits.

I think the advantage of type systems isn't something trivial like ensuring a function receives an integer instead of a string. It's that (in powerful type systems) you can encode your own domain constructs into types, which reduces cognitive overhead.
Safe refactoring + fewer runtime exceptions.
The first point is definitely a positive point for typing.

The second I feel is by design. A runtime exception is better than undefined behavior, which was the other option when many of these more abstract languages started taking hold. We're just reaching a point where languages with undefined behavior are being competed against by projects like Rust which offer similar performance without the same risks. Runtime exceptions become less attractive when you can avoid them and avoid undefined behavior with more comprehensive solutions.

You never have issues with Ruby objects unexpectedly being nil?
Ruby is strongly typed, just not statically typed (by default). There's a lot less ambiguity in Ruby's types compared to, say, JS or PHP. But yes, optional type checking would be a nice thing indeed.
Stripe has written https://sorbet.org/