Rtype: ruby with type
You can do the type checking in Ruby with this gem!
require 'rtype'
rtype :sum, [:to_i, Numeric] => Numeric
def sum(a, b)
a.to_i + b
end
sum(123, "asd")
# (Rtype::ArgumentTypeError) for 2nd argument:
# Expected "asd" to be a Numeric
class Test
rtype_self :invert, {state: Boolean} => Boolean
def self.invert(state:)
!state
end
end
Test::invert(state: 0)
# (Rtype::ArgumentTypeError) for 'state' argument:
# Expected 0 to be a Boolean
Requirements
- Ruby >= 2.1
- MRI or RBX if native extension is used. Rtype itself without it is pure-ruby gem, therefore you can use Rtype in JRuby, etc.
Features
- Provide type checking for argument and return
- Support type checking for keyword argument
- Type checking for array elements
- Duck typing
- Custom type behavior
Installation
Run gem install rtype or add gem 'rtype' to your Gemfile
And add to your .rb source file:
require 'rtype'
Native extension
Rtype itself is pure-ruby gem. but you can make it more faster by native extension.
Just run gem install rtype-native or add gem 'rtype-native' to your Gemfile, then Rtype use it.
(Do not require 'rtype-native')
It is only for MRI or RBX. JRuby extension not supported yet.
Usage
Supported Type Behaviors
Module- Value must be an instance of this module/class or one of its superclasses
Any: An alias forBasicObject(means Any Object)Boolean:trueorfalse
Symbol- Value must have(respond to) a method with this name
Regexp- Value must match this regexp pattern
Range- Value must be included in this range
Array(tuple)- Value must be an array
- Each of value's elements must be valid
- Value's length must be equal to the array's length
- Of course, nested array works
- Example: Array
- This can be used as a tuple
Proc- Value must return a truthy value for this proc
true- Value must be truthy
false- Value must be falsy
nil- Only available for return type. void return type in other languages
- Special Behaviors
Rtype::and(*types): Ensure value is valid for all the types- It also can be used as
Rtype::Behavior::And[*types]orinclude Rtype::Behavior; And[...] Rtype::or(*types): Ensure value is valid for at least one of the types- It also can be used as
Rtype::Behavior::Or[*types]orinclude Rtype::Behavior; Or[...] Rtype::xor(*types): Ensure value is valid for only one of the types- It also can be used as
Rtype::Behavior::Xor[*types]orinclude Rtype::Behavior; Xor[...] Rtype::not(*types): Ensure value is not valid for all the types- It also can be used as
Rtype::Behavior::Not[*types]orinclude Rtype::Behavior; Not[...] Rtype::nilable(type): Ensure value can be nil- It also can be used as
Rtype::Behavior::Nilable[type]orinclude Rtype::Behavior; Nilable[...] - You can create custom behavior by extending
Rtype::Behavior::Base
Examples
Basic
require 'rtype'
class Example
rtype :test, [Integer] => nil
def test(i)
end
rtype :any_type_arg, [Any] => nil
def any_type_arg(arg)
end
rtype :return_type_test, [] => 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
Keyword argument
require 'rtype'
class Example
rtype :say_your_name, {name: String} => Any
def say_your_name(name:)
puts "My name is #{name}"
end
# Mixing positional arguments and keyword arguments
rtype :name_and_age, [String, {age: Integer}] => Any
def name_and_age(name, age:)
puts "Name: #{name}, Age: #{age}"
end
end
Example.new.say_your_name(name: "Babo") # My name is Babo
Example.new.name_and_age("Bamboo", age: 100) # Name: Bamboo, Age: 100
Example.new.say_your_name(name: 12345)
# (Rtype::ArgumentTypeError) for 'name' argument:
# Expected 12345 to be a String
Duck typing
require 'rtype'
class Duck
rtype :says, [:to_i] => Any
def says(i)
puts "duck:" + " quack"*i.to_i
end
end
Duck.new.says("2") # duck: quack quack
Array
This can be used as a tuple.
rtype :func, [[Numeric, Numeric]] => Any
def func(arr)
puts "Your location is (#{arr[0]}, #{arr[1]}). I will look for you. I will find you"
end
func [1, "str"]
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected [1, "str"] to be an array with 2 elements:
# - [0] index : Expected 1 to be a Numeric
# - [1] index : Expected "str" to be a Numeric
func [1, 2, 3]
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected [1, 2, 3] to be an array with 2 elements:
# - [0] index : Expected 1 to be a Numeric
# - [1] index : Expected 2 to be a Numeric
func [1]
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected [1] to be an array with 2 elements:
# - [0] index : Expected 1 to be a Numeric
# - [1] index : Expected nil to be a Numeric
func [1, 2] # Your location is (1, 2). I will look for you. I will find you
rtype with attr_accessor
rtype_accessor
You can use rtype_accessor_self for static accessor.
require 'rtype'
class Example
rtype_accessor :value, String
attr_accessor :value
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
Combined type
### TEST 1 ###
require 'rtype'
class Example
rtype :and_test, [Rtype::and(String, :func)] => Any
def and_test(arg)
end
end
Example.new.and_test("A string")
# (Rtype::ArgumentTypeError) for 1st argument:
# Expected "A string" to be a String
# AND Expected "A string" to respond to :func
### TEST 2 ###
# ... require rtype and define Example the same as above ...
class String
def func; end
end
Example.new.and_test("A string") # Works!
Combined duck type
Application of duck typing and combined type
require 'rtype'
module Game
ENEMY = [
:name,
:level
]
class Player < Entity
include Rtype::Behavior
rtype :attack, [And[*ENEMY]] => Any
def attacks(enemy)
"Player attacks '#{enemy.name}' (level #{enemy.level})!"
end
end
class Slime < Entity
def name
"Powerful Slime"
end
def level
123
end
end
end
Game::Player.new.attacks Game::Slime.new
# Player attacks 'Powerful Slime' (level 123)!
Position of rtype && (symbol || string)
require 'rtype'
class Example
# Works. Recommended
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
end
Outside of module (root)
Yes, it works
rtype :say, [String] => Any
def say()
puts
end
say "Hello" # Hello
Static method
Use rtype_self
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
Check type information
This is just the 'information'
Any change of this doesn't affect type checking
require 'rtype'
class Example
rtype :test, [: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
Documentation
Benchmarks
Result of rake benchmark (source)
MRI
Ruby version: 2.1.7
Ruby engine: ruby
Ruby description: ruby 2.1.7p400 (2015-08-18 revision 51632) [x64-mingw32]
Rtype version: 0.2.0
Rubype version: 0.3.1
Sig version: 1.0.1
Contracts version: 0.13.0
Typecheck version: 0.1.2
Warming up --------------------------------------
pure 85.600k i/100ms
rtype 25.735k i/100ms
rubype 20.922k i/100ms
sig 8.960k i/100ms
contracts 4.659k i/100ms
typecheck 1.102k i/100ms
Calculating -------------------------------------
pure 3.273M (
JRuby
Without Rubype that doesn't support JRuby
Ruby version: 2.2.3
Ruby engine: jruby
Ruby description: jruby 9.0.5.0 (2.2.3) 2016-01-26 7bee00d Java HotSpot(TM) 64-Bit Server VM 25.60-b23 on 1.8.0_60-b27 +jit [Windows 10-amd64]
Rtype version: 0.2.0
Sig version: 1.0.1
Contracts version: 0.13.0
Typecheck version: 0.1.2
Warming up --------------------------------------
pure 29.127k i/100ms
rtype 4.566k i/100ms
sig 4.162k i/100ms
contracts 776.000 i/100ms
typecheck 981.000 i/100ms
Calculating -------------------------------------
pure 6.705M (
Rubype, Sig
Rtype is influenced by Rubype and Sig.
If you don't like Rtype, You can use other type checking gem such as Contracts, Rubype, Rtc, Typecheck, Sig.
Author
Sputnik Gugja ([email protected])
License
MIT license (@ Sputnik Gugja)
See LICENSE file.