What is Type-Driven Design/Development in Ruby

Feb 5, 2021 - 2 min read
What is Type-Driven Design/Development in Ruby

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:

  1. Describe the business need
  2. Describe the solution/feature
  3. Describe the implementation behavior
  4. Describe the types
  5. 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}"

class Person
  attr_reader :fname, :lname

  def initialize(fname, lname)
    @fname = fname
    @lname = lname
    raise "Firstname too short" if fname.empty?

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

class Person
  extend T::Sig

  sig { params(fname: String, lname: String).void }
  def initialize(fname, lname); 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.

12 Project Ideas
Cezar Halmagean
Software development consultant with over a decade of experience in helping growing companies scale large Ruby on Rails applications. Has written about the process of building Ruby on Rails applications in RubyWeekly, SemaphoreCI, and Foundr.