Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Created June 4, 2018 13:08
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/83c408348b5c597b73622a81b90c6e70 to your computer and use it in GitHub Desktop.
Save JoshCheek/83c408348b5c597b73622a81b90c6e70 to your computer and use it in GitHub Desktop.
A Ruby runtime type checker based off Stripe's "Sorbet" (https://tinyurl.com/y7tzjutf)
# An implementation to make this code example work: https://tinyurl.com/y7tzjutf
class Sig
def initialize(param_types)
@param_types = param_types
end
def returns(return_type)
@return_type = return_type
self
end
def named(name)
@name = name
self
end
def with_params(parameters)
@parameters = parameters
return self if @param_types.keys.sort == parameters.map(&:last).sort
raise "Declared type (#{@param_types.inspect}) does not match signature (#{parameters.inspect})"
end
def check_args!(args, block)
# this is only really valid for required (aka ordinal) args, but good enough for the example
@parameters.map(&:last).zip(args) do |name, val|
expected = @param_types[name]
actual = val.class
next if expected == actual
raise "Expected #{name.inspect} to have type #{expected.inspect}, but got #{actual.inspect}"
end
nil
end
def check_return!(returned)
return returned unless defined? @return_type
klass = returned.class
return returned if @return_type == klass
raise TypeError, "Return type mismatch! Expected #{@return_type.inspect}, got #{klass.inspect}"
end
end
class Module
def sig(**param_types)
@next_sig = Sig.new(param_types)
end
def method_added(name)
return unless @next_sig
original = instance_method name
sig = @next_sig.named(name).with_params(original.parameters)
@next_sig = nil
define_method name do |*args, &block|
sig.check_args! args, block
returned = original.bind(self).call(*args, &block)
sig.check_return! returned
end
end
end
# # # # # # # # # # # # # # # #
class BoxedInt
sig val: Integer
def initialize(val)
@val = val
end
sig(num: Integer).returns(BoxedInt)
def + num
BoxedInt.new @val + num
end
sig(num: Integer).returns(BoxedInt)
def - num
BoxedInt.new @val - num
end
sig.returns(String)
def inspect
"BoxedInt.new(#{@val.inspect})"
end
end
n = BoxedInt.new 10
n + 2 # => BoxedInt.new(12)
n - 3 # => BoxedInt.new(7)
n + 5 - 3 # => BoxedInt.new(12)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment