There’s been some excitement lately around type checking in Ruby 3, and it got me thinking about how something that functional programming languages like Haskell, Elm, etc., have been using for years might help with writing better Ruby programs.
Specifically, I’m talking about Type Driven Design/Development.
It’s about designing and developing your code by thinking about the type first, in the implementation phase.
So it goes something like this:
- Describe the business need
- Describe the solution/feature
- Describe the implementation behavior
- Describe the types
- Start writing your implementation code
This way of thinking about your code…
… keeps you from overusing primitive types
… gives you a better way to represent invalid states
… trims down the number of if/else checks you need to make in your code
…so overall, it’s worth at least thinking about how you could create more meaningful types (objects) in your code before you start writing it.
To better illustrate this, let me give an example:
class Printer
def self.full_name(person)
"Your name is: #{person.fname} #{person.lname}"
end
end
class Person
attr_reader :fname, :lname
def initialize(fname, lname)
@fname = fname
@lname = lname
raise "Firstname too short" if fname.empty?
end
end
john = Person.new("", "Doe")
puts Printer.full_name(john)
# typed: true
class Printer
extend T::Sig
sig { params(person: Person).returns(String) }
def self.full_name(person); end
end
class Person
extend T::Sig
sig { params(fname: String, lname: String).void }
def initialize(fname, lname); end
end
Suppose the first name of the person is empty. In that case, your program can now raise an error instead of using primitive types (like String) for the arguments to the Printer.full_name method.
If you were to use String types and send them to Printer.full_name, you would have to do the checks inside the full_name method, and everywhere else, you make use of that information. But with a more concrete type (like Person) you can encapsulate that validation logic inside the class, and make sure that once you do have a Person object, it’s going to be valid.
Here’s a video where I go into a little bit more detail.
So with type-driven design/development, you are forced to think about the types your application needs, as opposed to using primitive types everywhere and adding thousands of checks all over your codebase.