Ruby is and will always be a favorite language of mine. It was also my first, and when I was learning on my own before Hacker School, I was constantly plagued with RuntimeError messages in my terminal - usually the bug was me mixing up types - expecting one thing to be an Array and instead having it become a Fixnum.

Since then, I’ve been lucky enough to have been exposed to “typed” programming languages. Rust was the first language I tried experimenting with and it was incredibly difficult to adhere to the compiler rules on setting types correctly - I simply was too used to the permissiveness of Ruby.

So, what if it were possible to enforce types/type signatures in ruby class methods? I initially tried to write it out like this by treating the “types” as default arguments, and then find a way to change how Ruby normally handles methods.

def multiply(x: Fixnum, y: Fixnum, output: Fixnum)
  x * y
end

Unfortunately, this way can’t work. There is a Method class in Ruby’s core libraries, but there’s little one can do with a method implicitly. Instead, I’ll have to emulate the “type signatures” that are often found in languages like Haskell.

Code Climate wrote up something called ‘gradual typing’ and in it, type signatures are used as a class method. How they were used, I could not say. So I went about making a rudimentary version of it - signatures go below the methods, types are set as default keyword arguments. The method gets called, copies the method it’s related to, binds it to the class and THEN redefines it with typechecking inserted. If it passes, the original method gets called. If not, an error is raised.

class TypeSet
  def self.typesafe(klass, meth, args={})
    original_method = klass.instance_method(meth).bind(klass.new)
    klass.send(:define_method, meth, ->(**passed_args) {
      if args.all? { |k,v| passed_args[k].class == v }
        output = original_method.call(passed_args)
      else
        exceptions = args.map do |k,v|
          passed_args[k].class != v ? "Expected #{v}, got #{passed_args[k].class}. " : ""
        end.join(" ")
        raise TypeError, exceptions
      end
    })
  end
end

class SomeClass < TypeSet
  def create_array_of_letters(arg1:, arg2:)
    (arg1 * arg2).split("")
  end
  typesafe(self, :create_array_of_letters, arg1: String, arg2: Fixnum)


  def capitalize(names:)
    names.map { |name| name.capitalize }
  end
  typesafe(self, :capitalize, names: Array)
end

Of course, there are a lot of problems with this approach. Re-binding the method is tricky, and so there can be a lot of limitations towards using this. Still, as much as I love ruby and the flexibility it offers, I do find myself becoming more interested in strict-typing. It helps to keep track what my methods are doing.