Git Product home page Git Product logo

rubype's Introduction

Ruby + Type = Rubype

Gem Version Build Status Dependency Status Code Climate

210414.png

require 'rubype'
class MyClass
  # Assert first arg has method #to_i, second arg and return value are instance of Numeric.
  def sum(x, y)
    x.to_i + y
  end
  typesig :sum, [:to_i, Numeric] => Numeric
end

MyClass.new.sum(:has_no_to_i, 2)
#=> Rubype::ArgumentTypeError: for MyClass#sum's 1st argument
#   Expected: respond to :to_i,
#   Actual:   :has_no_to_i
#   ...(stack trace)

This gem brings you advantage of type without changing existing code's behavior.

Good point:

  • Meaningful error
  • Executable documentation
  • Don't need to check type of method's arguments and return.
  • Type info itself is object, you can check it and even change it during run time.

Bad point:

  • Checking type run every time method call... it might be overhead, but it's not big deal.
  • There is no static analysis.

Feature

Super clean!!!

I know it's terrible to run your important code with such a hacked gem. But this contract is implemented by less than 80 lines source code! (It is not too little, works well enough) https://github.com/gogotanaka/Rubype/blob/develop/lib/rubype.rb#L2-L79

You can read this over easily and even you can implement by yourself !(Don't need to use this gem, just take idea)

Advantage of type

  • Meaningful error
  • Executable documentation
  • Don't need to check type of method's arguments and return .
require 'rubype'

# ex1: Assert class of args and return
class MyClass
  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

MyClass.new.sum(1, 2)
#=> 3

MyClass.new.sum(1, 'string')
#=> Rubype::ArgumentTypeError: for MyClass#sum's 2nd argument
#   Expected: Numeric,
#   Actual:   "string"
#   ...(stack trace)

MyClass.new.wrong_sum(1, 2)
#=> Rubype::ReturnTypeError: for MyClass#wrong_sum's return
#   Expected: Numeric,
#   Actual:   "string"
#   ...(stack trace)


# ex2: Assert object has specified method
class MyClass
  def sum(x, y)
    x.to_i + y
  end
  typesig :sum, [:to_i, Numeric] => Numeric
end

MyClass.new.sum('1', 2)
#=> 3

MyClass.new.sum(:has_no_to_i, 2)
#=> Rubype::ArgumentTypeError: for MyClass#sum's 1st argument
#   Expected: respond to :to_i,
#   Actual:   :has_no_to_i
#   ...(stack trace)


# ex3: You can use Any class, if you want
class People
  def marry(people)
    # Your Ruby code as usual
  end
  typesig :marry, [People] => Any
end

People.new.marry(People.new)
#=> no error

People.new.marry('non people')
#=> Rubype::ArgumentTypeError: for People#marry's 1st argument
#   Expected: People,
#   Actual:   "non people"
#   ...(stack trace)

Typed method can coexist with non-typed method

# It's totally OK!!
class MyClass
  def method_with_type(x, y)
    x + y
  end
  typesig :method_with_type, [Numeric, Numeric] => Numeric

  def method_without_type(x, y)
    'string'
  end
end

Duck typing

You can use Any class.

class MyClass
  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!!
MyClass.new.foo(1)
# It's totally OK!!
MyClass.new.foo(:sym)


# It's totally OK!!
MyClass.new.sum(1, 2)
# It's totally OK!!
MyClass.new.sum('1', 2)

Check type info everywhere!

class MyClass
  def sum(x, y)
    x.to_i + y
  end
  typesig :sum, [:to_i, Numeric] => Numeric
end

MyClass.new.method(:sum).type_info
# => [:to_i, Numeric] => Numeric

MyClass.new.method(:sum).arg_types
# => [:to_i, Numeric]

MyClass.new.method(:sum).return_type
# => Numeric

Benchmarks

result of bundle exec rake benchmark

Ruby 2.2.1, Macbook Pro 2.7Ghz Intel Core i5, 8GB RAM

ruby version: 2.2.1
rubype version: 0.3.0
Calculating -------------------------------------
           Pure Ruby   101.070k i/100ms
              Rubype    63.973k i/100ms
-------------------------------------------------
           Pure Ruby      7.115M (± 6.1%) i/s -     35.476M
              Rubype      1.537M (± 2.5%) i/s -      7.677M

Comparison:
           Pure Ruby:  7114786.0 i/s
              Rubype:  1536611.5 i/s - 4.63x slower

Installation

gem install rubype or add gem 'rubype' to your Gemfile.

And require 'rubype', enjoy typed Ruby.

This gem requires Ruby 2.0.0+.

Contributing

  • I really wanna make Rubype elegant source code.

  • Any feature or comments are welcome.

How to develop

Now Rubype is written with 100% Ruby. In terms of performance, only core module(https://github.com/gogotanaka/Rubype/blob/develop/lib/rubype.rb#L4-L80) will be translate to C.

Only two API will be translate to C, it means you don't need to know what C dose!

  1. Fork it ( https://github.com/gogotanaka/Rubype/fork )

  2. Create your feature branch (git checkout -b my-new-feature)

    $ bundle install --path vendor/bundle

  3. Commit your changes (git commit -am 'Add some feature')

  4. Run tests

    $ bundle exec rake test

    ......

  5. Run benchmerk(optional)

    $ bundle exec rake bm

    ......

  6. Push to the branch (git push origin my-new-feature)

  7. Create a new Pull Request to develop branch

Credits

@chancancode and This article first brought this to my attention. I've stolen some idea from them.

License

MIT license (© 2015 Kazuki Tanaka)

rubype's People

Contributors

gogotanaka avatar stefkin avatar janlelis avatar styrmis avatar garbles avatar nicoder avatar tricknotes avatar tjarratt avatar

Stargazers

Kyle Behrens avatar Yohei Yasukawa avatar Yo avatar Margret Riegert avatar kousei yamashita avatar shigeek avatar Rustam Ibragimov avatar KATO Kei avatar Márk Török avatar Gerald Bauer avatar Makoto Tajitsu avatar Nami W avatar Coolfix avatar Pankaj Doharey avatar wint avatar Adam avatar  avatar Rigo avatar Maythee Anegboonlap avatar Billy.Zheng avatar William Wolf avatar  avatar Jared Clifton-Lee avatar  avatar Nick Soto avatar Max Surkov avatar Peter Shih avatar Marcin Nowicki avatar  avatar yoheisenju avatar Vaughan Kelly Guy avatar yjono avatar HIRAKI Satoru avatar  avatar Alex Moore-Niemi avatar  avatar Ryutaro Yamada avatar  avatar  avatar Igor Victor avatar nikushi avatar Guilherme Dutra avatar Julien J. Rosa avatar Jonah George avatar  avatar Ben Caldwell avatar Joseph Weissman avatar Chris Olstrom avatar Satoshi Namai avatar Dan Barrett avatar  avatar Michael Williams avatar Angus H. avatar Tom Link avatar Santi Bivacqua avatar Oleg Kovalenko avatar Maksim Morozov avatar Yuta Totz avatar Masataka Pocke Kuwabara avatar liupeng avatar zgl avatar Wiljo Doeleman avatar Max Pleaner avatar bscd avatar Girish Duvuru avatar David Feinberg avatar Robin Liao avatar Maksym Litvynov avatar Michal Cichra avatar Chayoung You avatar Nao Minami avatar  avatar Benjamin Fleischer avatar Amit Thawait avatar Karol Bucek avatar Sean Huber avatar Senthil Nayagam avatar Albert avatar Mirosław Boruta avatar Kris Leech avatar Lucas Caton avatar Jeremy W. Rowe avatar  avatar Antonin Deniau avatar Vjatseslav Gedrovits avatar Alexander Kirillov avatar Gosha Spark avatar Sergey Nartimov avatar Giovanni Kock Bonetti avatar Christos Zisopoulos avatar Kazuma Furuhashi avatar Robert Audi avatar Cheng-Yu Hsu avatar Víctor Martínez avatar zernie avatar Jakub Arnold avatar  avatar Arthur Corenzan avatar Dante Tsang avatar Jakub Cieślar avatar

Watchers

 avatar Rakhmad Azhari avatar 404 avatar Alan Zimmerman avatar mayulu avatar James Cloos avatar Masaki Takehara avatar Paul Götze avatar Andre Dickson avatar  avatar Eliott Appleford avatar  avatar Koichi Shiraishi avatar Andrey O avatar masa kunikata avatar  avatar

rubype's Issues

Another similar library

Hi there,
I'm the author of contracts.ruby, which is very similar to your library! Its neat to see another library for contracts. But I think we are both unnecessarily doing duplicate work. For example, we recently fixed a bug where putting a contract on a private method would make it public. Rubype has the same issue:

require 'rubype'

# ex1: Assert class of args and return
class MyClass
  private
  def private_sum(x, y)
    x + y
  end
  typesig :private_sum, [Numeric, Numeric] => Numeric
end

MyClass.new.private_sum(1, 2) # => should error with private method `private_sum'

Instead of fixing the same issues twice, maybe we should join forces? You already have knowledge on implementing gradual type checking in Ruby, so your input would be valuable.

Variable arguments

Like that.

def sum(char, *ary)
  ary.inject(:+)
end
typesig :sum, [String, [Numeric]] => Numeric

Add benchmarks to the readme

Even though the idea of typechecking in Ruby is great, I think it is worth to mention that type checking comes with performance.

I'm attaching simple pull request that adds benchmarks to README.

Anything but a certain type

like this:

  def inspect_non_nil(object)
    object.inspect
  end
  typesig :inspect_non_nil, ~Nilclass => String

or

like this:

  def inspect_non_nil(object)
    object.inspect
  end
  typesig :inspect_non_nil, Any.but(NilClass) => String

Optional

like that?

typesig :method, [Integer] => (String | NilClass)

Performance drop in 0.2.6

First of all, thank you a lot for this great gem. Being inspired, I build a very similar one, with some differences in syntax and features. While doing so, I noticed a huge performance drop in the latest version of rubype.

Here is a benchmark using rubype 0.2.5:

ruby version: 2.2.1
sig version: 1.0.0
rubype version: 0.2.5
contracts version: 0.8
Calculating -------------------------------------
                pure    57.194k i/100ms
                 sig     7.816k i/100ms
              rubype     8.756k i/100ms
           contracts     5.771k i/100ms
-------------------------------------------------
                pure      2.855M (± 1.1%) i/s -     14.299M
                 sig    102.990k (± 2.5%) i/s -    515.856k
              rubype    116.062k (± 1.0%) i/s -    586.652k
           contracts     70.575k (± 1.7%) i/s -    357.802k

Comparison:
                pure:  2854866.4 i/s
              rubype:   116062.3 i/s - 24.60x slower
                 sig:   102990.1 i/s - 27.72x slower
           contracts:    70575.3 i/s - 40.45x slower

And here one using rubype 0.2.6:

ruby version: 2.2.1
sig version: 1.0.0
rubype version: 0.2.6
contracts version: 0.8
Calculating -------------------------------------
                pure    53.905k i/100ms
                 sig     7.808k i/100ms
              rubype   417.000  i/100ms
           contracts     5.699k i/100ms
-------------------------------------------------
                pure      2.956M (± 2.4%) i/s -     14.770M
                 sig    105.957k (± 0.5%) i/s -    530.944k
              rubype      4.313k (± 5.3%) i/s -     21.684k
           contracts     69.450k (± 2.3%) i/s -    347.639k

Comparison:
                pure:  2956225.5 i/s
                 sig:   105956.8 i/s - 27.90x slower
           contracts:    69450.1 i/s - 42.57x slower
              rubype:     4312.8 i/s - 685.45x slower

The code used for the benchmark: https://github.com/janlelis/sig/blob/v1.0.0/Rakefile#L32-L131

Keyword arguments

like this:

  def create_user(database_name, login:, password:, active:)
  end
  typesig :create_user, [String] => NilClass, login: String, password: String, active: TrueClass | FalseClass

(example requires #4)

Document inline syntax for Ruby 2.1 and newer

The Readme should document an alternative syntax usable in Ruby 2.1 and newer.

class MyClass
  typesig def by_four(i)
    i * 4
  end , [Integer] => Integer
end

If Rubype would allow trailing method name, it could look like this:

class MyClass
  typesig {[Integer] => Integer},
  def by_four(i)
    i * 4
  end
end

Don’t say Rubype offers “gradual type checking”

From this discussion on Lobsters, and from the paper that one commenter links to, it sounds like “gradual typing” means something different from what this gem does.

Gradual typing means mixing dynamic (run-time) type-checking and static (compile-time) type-checking. This gem does only run-time type-checking.

The following might be better terms for what Rubype implements:

  • contracts
  • run-time type assertions
  • run-time type checking

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.