Hacker News new | ask | show | jobs
by JohnBooty 1000 days ago
I like strong typing as well, and worked with a strongly typed language for years before Ruby.

Then I did Ruby+Rails fulltime for 9 years. Just recently moved on.

    With Ruby, I'd have to write hundreds of 
    edge-case unit-tests just to cover stuff that, 
    with Rust is enforced compile-time for me.
Never a problem for me.

It was one of my major concerns about Ruby, prior to starting out. But like... it just wasn't a problem.

It turns out that we just don't pass the wrong kind of thing to the other thing very often, or at least I and my teams did not. It certainly helps if you follow some sane programming practices. Using well-named keyword arguments and identifiers, for example.

    # bad. wtf is input?
    def grant_admin_privileges(input)        
    end

    # you would have to be a psychopath to pass this
    # anything but a User object
    def grant_admin_privileges(user:)
    end
   
Of course, this can be a major problem if you're dealing with unfamiliar or poorly written code. In which case, yeah, that sucks. I know that many will scoff at the old-timey practice of "use good names" in lieu of actual language-level typing enforcement, and that "just use a little discipline!" has long been the excuse of people defending bad languages and tools. But a little discipline in Ruby goes such a long way, moreso than in any language I have ever used.

    With Ruby, I'd have to write hundreds of edge-case unit-tests 
    just to cover stuff that, with Rust is enforced compile-time for me.
Well, you do need test coverage with Ruby. But you do anyway in any language for "real" work, soooooo.

I strongly dispute that you need extra tests for "edge cases" because of dynamic typing. Something is deeply wrong if we are coding defensive methods that handle lots of different types of inputs and do lots of duck typing checks or etc. to defend themselves against type-related edge cases.

     (yes, I know guard, fancy runners with pattern matching etc).
Yeaaaaaah. Rails tests hit the database by default, which is good and bad, but it is inarguably slowwww. I don't find pure Ruby code to be slow to test.

     The last reason, for me, is editor/IDE integration
Yes. I still miss feeling like some kind of god-level being with C#, Visual Studio, and Resharper. I liked the Ruby REPL which offset that largely in terms of coding productivity but was certainly not a direct replacement.

    But stepping through a stack in a rails project is a nightmare
Yeah. I always wanted a version of the pry 'next' method that was basically like, "step to the next line of code but skip all framework and Ruby core code"
2 comments

I dare you to have a look at your rollbar, sentry or other exception logging of a rails project. And I'll put money on it, that the top 5 exceptions has several 'undefined method x' (probably on nil) errors.

Those warrant unit tests. Those will regress. Those would never exist in a strongly typed language (though Java still has null...ugh)

Yeah that's the usual argument and I don't agree.

It's true that 99.9% of production log errors are NoMethodError exceptions.

annnnnd 99.9% of those NoMethodErrors are just code not handling nils/nulls correctly

annnnnd 99.9% of those unhandled runtime nils/nulls are from external data (user inputs, database data, etc)

So strong typing doesn't help you there at runtime, it just blows up differently.

> So strong typing doesn't help you there at runtime, it just blows up differently.

It really does, though. Not with the Java-type of strong typing (still allows null) but with the Rust type of strong typing. Simply because it moves all this to the edge. At the point where you read the CSV/database/HTTP-response/user-input.

Everything inside of this edge (a strong boundary) doesn't need to to deal with "can this be nil" because it can't. Your `the_outlier(items: Vec<Measurement>)` will simply not compile if the type-checker sees that `items` can be nil, `items` can contain a nil, or, internal to that function an items[].measured_at might be nil, or maybe items[].measured_at is a Date instead of a DateTime.

You don't need a bazillion tests to deal with this situation around `the_outlier()`. That doesn't mean that the part that reads a Vec<Measurement> from a CSV (or json, or database or whatever) is covered by this typechecker. But it means this layer, the edge, the boundary, is where you put the protection. Validation, whatnot.

> # you would have to be a psychopath to pass this > # anything but a User object > def grant_admin_privileges(user:)

I had an app once where we used user objects, and later switched to ids to save db calls. Now you have some functions that can accept both, and some that accept one of them, and without type hints (that was long time ago) you can easily make a mistake.

That sounds like some malpractice.

Ruby has had keyword arguments since Ruby 2.0 from 2008 and earlier code could certainly use option hashes.

So I don't see a reason for any confusion there.

    # probably malpractice in a system where many methods
    # take IDs and some take Users
    def do_something_with_user(user)
    end

    # how hard is this? trivially easy and unambiguous.
    def do_something_with_user(user_id:)
    end
In the second example, you would really have to be asleep at the wheel to make a mistake like:

    # this is obviously wrong
    do_something_with_user(user_id: User.first)
I don't see the problem. It would be nice if a compiler/IDE could catch that, but on the other hand, it just looks blatantly wrong as you type it and will certainly blow up the first time you call it.