Statically typed Ruby? Don’t call me a heretic

I’m wish I could hear people’s reactions to reading that title. I’m sure there would be a wide variety of opinions, from “don’t you understand what duck typing is?!?” to “why don’t use just use .NET”? Well, I do understand duck typing and I also use .NET, so hopefully this turns into a logical discussion.

I do a lot of .NET development. I practically grew up on .NET and statically typed languages, so my world consists of things like interfaces, dependency injection, and waiting for my code to compile.

If you use dynamic languages, you might thumb your nose at those other things, and brag how you don’t have to use those things in a dynamic language. This is all true, and every time I sit and wait 30 seconds for compile my mind drifts off to a land where I can write code and just run my tests without having to wait 30 seconds to all of my code to compile so that I can test 1% of it.

I love Ruby because I don’t have to have all of the ceremony that I have to deal with in .NET. I love being to able to design objects the way I want without having to structure it in a way that makes it testable. And of course not having to wait for code to compile completely changes the way you write code.

It’s not all rainbows and unicorns

Software development is a series of trade-offs. One thing I really love about Ruby is that you have a lot of freedom but it’s not as dynamic as something like JavaScript (there’s a difference between 1 and ‘1’ and true), but I don’t have all of the constraints of .NET. The trade-off is that I don’t have that check that makes sure that I’m passing objects of the right type into a method or makes sure that an object returns an object a given type. This is where people yell “DUCK TYPING!!!!!”, which is true. In .NET, a class implements an interface if it’s specified in the class definition, which means that it has to implement all of the methods of the interface. In Ruby, there is no explicit interface, and the interface is simply defined by the methods that an object responds to. That’s all well and good.

Do I want compile time checks in Ruby? Well, maybe. The argument against that is that I should have tests that test everything, and in most cases I will. Then you refactor something. You run your tests and they fail, but the failure message is due to something that happened much earlier in the test, and the failure is just exposing that a problem exists somewhere. Now you’re left digging to try and find the place where something is returning the wrong type, and sometimes this takes awhile.

As much as compiling in .NET can suck, the static typing really makes refactoring easier, in part because the refactoring tools are better as a result. I’ll just change stuff and compile and let the build errors tell me exactly where to go. This is about the only time I like compiling. But when you’re used to that check and then it’s not there and you realize that refactoring is a little harder, it makes you think.

I did something about it

If I can’t have compile time checks in Ruby, I can have runtime checks. Then when I run my tests, at least it shows me exactly where things are failing. But after you write raise "Expected Hash but got #{input.class}" unless input.is_a? Hash over and over, you start to feel like there could be a better way. But this is a little better than it was before.

People have thought about this

There has been some discussion about adding static types to Ruby 3, but it doesn’t look like it is going to happen. I suppose I understand, Ruby is a beautifully simplistic, yet powerful language, so I get it if some people aren’t on board with such a radical shift. There also gems like rdl that give you a way to implement runtime type checking.

Other languages have done this

TypeScript is basically JavaScript with static typing and compile time checks. Some people don’t like having TypeScript’s restrictions imposed on them, but most people that have used TypeScript seem to like it. Either way, TypeScript gives a nice blueprint of a way that statically typed Ruby could work (I’m sure there are nuances that make it different, but you get the idea).

Discussion is good

I’m sure that people will continue to debate ideas like this, and if anything it provides a good education on differing philosophies in programming language design and the pros and cons of them all. It’s also a good reminder that there is no perfect programming language, that every benefit comes with a trade-off, and there is more than one right way to get the job done.