Rtype Legacy: ruby with type (rtype for ruby 1.9+)
require 'rtype/legacy'
class Test
rtype [:to_i, Numeric] => Numeric
def sum(a, b)
a.to_i + b
end
rtype [{state: Boolean}. {}] => Boolean
def self.invert(opts)
!opts[:state]
end
end
Test.new.sum(123, "asd")
# (Rtype::ArgumentTypeError) for 2nd argument:
# Expected "asd" to be a Numeric
Test::invert(state: 0)
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected {:state=>0} to be a hash with 1 elements:
# - state : Expected 0 to be a Boolean
Requirements
- Ruby >= 1.9
- If you are using ruby 2.1+, see rtype
- MRI
- If C native extension is used. otherwise it is not required
- JRuby (JRuby 1.7+)
- If Java extension is used. otherwise it is not required
Difference between rtype and rtype-legacy
- The two are separate gem
- Rtype requires ruby 2.1+. Rtype Legacy requires ruby 1.9+
- Rtype supports 'type checking for keyword argument'. Rtype Legacy doesn't
- Rtype uses
Module#prepend
. Rtype Legacy redefines method - Rtype can be used outside of module (with specifying method name). Rtype Legacy can't be used outside of module
Features
- Provides type checking for arguments and return
- Type checking for hash elements
- Duck Typing
- Typed Array
- Numeric check. e.g.
Int >= 0
- Custom type behavior
- ...
Installation
Run gem install rtype-legacy
or add gem 'rtype-legacy'
to your Gemfile
And add to your .rb
source file:
require 'rtype/legacy'
Native extension
Rtype itself is pure-ruby gem. but you can make it more faster by using native extension.
Native extension for MRI
Run
gem install rtype-legacy-native
or add to your Gemfile
:
gem 'rtype-legacy-native'
then, Rtype Legacy uses it. (Do not require 'rtype-legacy-native'
)
Java extension for JRuby
Run
gem install rtype-legacy-java
or add to your Gemfile
:
gem 'rtype-legacy-java'
then, Rtype Legacy uses it. (Do not require 'rtype-legacy-java'
)
Usage
Supported Type Behaviors
Module
: Value must be of this module (is_a?
)Any
: Alias forBasicObject
(means Any Object)Boolean
:true
orfalse
Symbol
: Value must respond to a method with this nameRegexp
: Value must match this regexp patternRange
: Value must be included in this rangeArray
: Value can be any type in this arrayProc
: Value must return a truthy value for this proctrue
: Value must be truthyfalse
: Value must be falsynil
: Value must be nilHash
- Value must be a hash
- Each of elements must be valid
- Keys of the value must be equal to keys of this hash
- String key is different from symbol key
- vs. Keyword arguments (e.g.)
[{}]
is not hash argument. it is keyword argument, because its position is last[{}, {}]
is hash argument (first) and keyword argument (second)[{}, {}, {}]
is two hash argument (first, second) and keyword argument (last){}
is keyword argument. non-keyword arguments must be in array.- Of course, nested hash works
- Example: Hash
-
TypedArray
,Num, Int, Flo
,And
,Xor
,Not
,Nilable
Examples
Basic
require 'rtype'
class Example
rtype [Integer] => nil
def test(i)
end
rtype [Any] => nil
def any_type_arg(arg)
end
rtype [] => Integer
def return_type_test
"not integer"
end
end
e = Example.new
e.test("not integer")
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected "not integer" to be a Integer
e.any_type_arg("Any argument!") # Works
e.return_type_test
# (Rtype::ReturnTypeError) for return:
# Expected "not integer" to be a Integer
Duck typing
require 'rtype'
class Duck
rtype [:to_i] => Any
def says(i)
puts "duck:" + " quack"*i.to_i
end
end
Duck.new.says("2") # duck: quack quack
Array
rtype :ruby!, [[String, Integer]] => Any
def ruby!(arg)
puts "ruby!"
end
func("str") # ruby!
func(123) # ruby!
func(nil)
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected nil to be a String
# OR Expected nil to be a Integer
Hash
# last hash element is keyword arguments
rtype :func, [{msg: String}, {}] => Any
def func(hash)
puts hash[:msg]
end
func({})
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected {} to be a hash with 1 elements:
# - msg : Expected nil to be a String
func({msg: 123})
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected {:msg=>123} to be a hash with 1 elements:
# - msg : Expected 123 to be a String
func({msg: "hello", key: 'value'})
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected {:msg=>"hello", :key=>"value"} to be a hash with 1 elements:
# - msg : Expected "hello" to be a String
func({"msg" => "hello hash"})
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected {"msg"=>"hello hash"} to be a hash with 1 elements:
# - msg : Expected nil to be a String
func({msg: "hello hash"}) # hello hash
rtype with attr_accessor
rtype_accessor
: calls attr_accessor
if the accessor method(getter/setter) is not defined. and makes it typed
You can use rtype_accessor_self
for static accessor.
require 'rtype'
class Example
rtype_accessor :value, String
def initialize
@value = 456
end
end
Example.new.value = 123
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected 123 to be a String
Example.new.value
# (Rtype::ReturnTypeError) for return:
# Expected 456 to be a String
Typed Array
### TEST 1 ###
class Test
rtype [Array.of(Integer)] => Any
def sum(args)
num = 0
args.each { |e| num += e }
end
end
sum([1, 2, 3]) # => 6
sum([1.0, 2, 3])
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected [1.0, 2, 3] to be an array with type Integer"
### TEST 2 ###
class Test
rtype [ Array.of([Integer, Float]) ] => Any
def sum(args)
num = 0
args.each { |e| num += e }
end
end
sum([1, 2, 3]) # => 6
sum([1.0, 2, 3]) # => 6.0
rtype
require 'rtype'
class Example
# Recommended. With annotation mode (no method name required)
rtype [Integer, String] => String
def hello_world(i, str)
puts "Hello? #{i} #{st
end
# Works (with specifying method name)
rtype :hello_world, [Integer, String] => String
def hello_world(i, str)
puts "Hello? #{i} #{st
end
# Works
def hello_world_two(i, str)
puts "Hello? #{i} #{str}"
end
rtype :hello_world_two, [Integer, String] => String
# Also works (String will be converted to Symbol)
rtype 'hello_world_three', [Integer, String] => String
def hello_world_three(i, str)
puts "Hello? #{i} #{str}"
end
# Doesn't work. annotation mode works for following (next) method
def hello_world_four(i, str)
puts "Hello? #{i} #{str}"
end
rtype [Integer, String] => String
end
Class method
Annotation mode works for both instance method and class method
require 'rtype'
class Example
rtype [:to_i] => Any
def self.say_ya(i)
puts "say" + " ya"*i.to_i
end
end
Example::say_ya(3) #say ya ya ya
if you specify method name, however, you must use rtype_self
instead of rtype
require 'rtype'
class Example
rtype_self :say_ya, [:to_i] => Any
def self.say_ya(i)
puts "say" + " ya"*i.to_i
end
end
Example::say_ya(3) #say ya ya ya
Type information
This is just 'information'
Any change of this doesn't affect type checking
require 'rtype'
class Example
rtype [:to_i] => Any
def test(i)
end
end
Example.new.method(:test).type_info
# => [:to_i] => Any
Example.new.method(:test).argument_type
# => [:to_i]
Example.new.method(:test).return_type
# => Any
Special Behaviors
TypedArray
: Ensures value is an array with the type (type signature)Array::of(type)
(recommended)- or
Rtype::Behavior::TypedArray[type]
- Example: TypedArray
Num, Int, Flo
: Numeric checkNum/Int/Flo >/>=/</<=/== x
- e.g.
Num >= 2
means value must be aNumeric
and >= 2 - e.g.
Int >= 2
means value must be aInteger
and >= 2 - e.g.
Flo >= 2
means value must be aFloat
and >= 2
And
: Ensures value is valid for all given typesRtype::and(*types)
,Rtype::Behavior::And[*types]
- or
Array#comb
,Object#and(*others)
Xor
: Ensures value is valid for only one of given typesRtype::xor(*types)
,Rtype::Behavior::Xor[*types]
- or
Object#xor(*others)
Not
: Ensures value is not valid for all given typesRtype::not(*types)
,Rtype::Behavior::Not[*types]
- or
Object#not
Nilable
: Value can be nilRtype::nilable(type)
,Rtype::Behavior::Nilable[type]
- or
Object#nilable
,Object#or_nil
You can create custom behaviors by extending
Rtype::Behavior::Base
Documentation
Benchmarks
Result of rake benchmark
(source)
Rubype and Sig don't support 1.9 ruby. Typecheck raise an error in my environment
MRI
Ruby version: 1.9.3
Ruby engine: ruby
Ruby description: ruby 1.9.3p551 (2014-11-13 revision 48407) [i686-linux]
Rtype Legacy version: 0.0.1
Contracts version: 0.14.0
Rtype Legacy with native extension
Warming up --------------------------------------
pure 49.620k i/100ms
rtype-legacy 13.038k i/100ms
contracts 2.765k i/100ms
Calculating -------------------------------------
pure 2.037M (± 1.9%) i/s - 10.222M
rtype-legacy 179.155k (± 2.3%) i/s - 899.622k
contracts 30.576k (± 0.9%) i/s - 154.840k
Comparison:
pure: 2036909.8 i/s
rtype-legacy: 179155.3 i/s - 11.37x slower
contracts: 30575.8 i/s - 66.62x slower
JRuby
Ruby version: 1.9.3
Ruby engine: jruby
Ruby description: jruby 1.7.23 (1.9.3p551) 2015-11-24 f496dd5 on Java HotSpot(TM) Server VM 1.8.0_91-b14 +jit [linux-i386]
Rtype Legacy version: 0.0.1
Contracts version: 0.14.0
Rtype Legacy with java extension
Warming up --------------------------------------
pure 76.140k i/100ms
rtype-legacy 5.123k i/100ms
contracts 1.422k i/100ms
Calculating -------------------------------------
pure 6.330M (± 9.7%) i/s - 30.913M
rtype-legacy 293.793k (± 4.4%) i/s - 1.465M
contracts 33.924k (± 2.3%) i/s - 170.640k
Comparison:
pure: 6329735.2 i/s
rtype-legacy: 293793.2 i/s - 21.54x slower
contracts: 33924.0 i/s - 186.59x slower
Author
Sputnik Gugja ([email protected])
License
MIT license (@ Sputnik Gugja)
See LICENSE
file.