module Types InvalidTypeError = Class.new(ArgumentError) def specify(spec, a_method) args_spec = [] return_spec = nil case spec.first when Hash args_spec = spec.first.keys.first return_spec = spec.first.values.first when Array args_spec = spec.first else args_spec = spec end original_method = :"_#{a_method}_without_type_checking" alias_method original_method, a_method define_method(a_method) do |*args| ensure_correct_types(args_spec, args) result = send(original_method, *args) ensure_correct_types(return_spec, [result], what: "return value") if return_spec result end a_method end module InstanceMethods def ensure_correct_types(spec, args, what: "argument") Array(spec).each.with_index do |klass, index| raise InvalidTypeError, ["Type for #{what}:", klass, "for" , args[index]&.inspect || "-missing #{index.succ} argument-", args[index].class].join(" ") if klass && !(klass === args[index]) end end end private_constant :InstanceMethods def self.extended(mod) mod.include InstanceMethods end end Object.extend Types class Foo specify [String], def foo(arg) 42 end specify [String => Numeric], def bar(arg) 42 end specify [[String, String]], def foo2(arg1, arg2) 42 end specify [[String, String] => Numeric], def bar2(arg1, arg2) 42 end end Foo.new.foo("42") Foo.new.bar2("42", ?x)