Skip to content

Instantly share code, notes, and snippets.

@Whitespace
Created April 13, 2015 13:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Whitespace/5d3150847540ef3ea779 to your computer and use it in GitHub Desktop.
Save Whitespace/5d3150847540ef3ea779 to your computer and use it in GitHub Desktop.
Gradual Type Checking for Ruby
# inspired by http://gogotanaka.github.io/rubype.github.io/
module Types
def self.included(base)
class << base
def typesig method, signature_hash
arg_types, ret_type = signature_hash.to_a[0]
self.send(:alias_method, "__#{method}".to_sym, method)
define_method method do |*args|
failures = args.map.with_index do |arg, i|
case arg_types[i]
when Any
when Symbol
if !arg.respond_to?(arg_types[i])
"Expected argument #{i} of #{self.class}\##{method} to have method \##{arg_types[i]} but got \"#{args[i]}\" instead"
end
when Class
if !arg.is_a?(arg_types[i])
"Expected argument #{i} of #{self.class}\##{method} to be #{arg_types[i]} but got \"#{args[i].class}\" instead"
end
end
end.compact
unless failures.empty?
raise ArgumentTypeError, failures.join("\n")
else
ret_val = send("__#{method}", *args)
case ret_type
when Any || ret_type
ret_val
else
raise ReturnTypeError, "Expected #{self.class}\##{method} to return #{ret_type} but got \"#{ret_val.class}\" instead"
end
end
end
end
end
end
class Any
def self.===(klass)
true
end
end
class ArgumentTypeError < TypeError; end
class ReturnTypeError < TypeError; end
end
# ex1: Assert class of args and return
class MyClass
include Types
def sum(x, y)
x + y
end
typesig :sum, [Numeric, Numeric] => Numeric
def wrong_sum(x, y)
'string'
end
typesig :wrong_sum, [Numeric, Numeric] => Numeric
end
puts MyClass.new.sum(1, 2)
# puts MyClass.new.sum(1, 'string')
# puts MyClass.new.wrong_sum(1, 2)
# ex2: Assert object has specified method
class MyClass
include Types
def sum(x, y)
x.to_i + y
end
typesig :sum, [:to_i, Numeric] => Numeric
end
puts MyClass.new.sum('1', 2)
# puts MyClass.new.sum(:has_no_to_i, 2)
# ex3: You can use Any class, if you want
class People
include Types
def marry(people)
"I now thee wed"
end
typesig :marry, [People] => Any
end
puts People.new.marry(People.new)
class MyClass
include Types
def foo(any_obj)
1
end
typesig :foo, [Any] => Numeric
def sum(x, y)
x.to_i + y
end
typesig :sum, [:to_i, Numeric] => Numeric
end
# It's totally OK!!
puts MyClass.new.foo(1)
# It's totally OK!!
puts MyClass.new.foo(:sym)
# It's totally OK!!
puts MyClass.new.sum(1, 2)
# It's totally OK!!
puts MyClass.new.sum('1', 2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment