

Gradual type checking for Ruby - eduardordm
https://github.com/gogotanaka/Rubype

======
curryhoward
When most practitioners think of "typechecking", they typically think about
proving properties about programs statically. This project seems equivalent to
adding a runtime check at each call site to ensure the arguments and return
values are the correct type.

This is certainly useful sometimes, as it gives programs the desirable "fail
fast" property. But it isn't "typechecking" as most engineers understand it.
Or at least, it should be clarified that this is run-time typechecking. As
such, it negatively impacts runtime performance, unlike compile-time
typechecking.

This project also seems to miss the primary opportunity of run-time type
checking: checking properties that are difficult to prove statically! For
example, checking that a number is even, that a string is lowercase, that an
index is within the bounds of an array, etc. These exotic types require a
dependent type system to be checked statically, but in a dynamic environment
they are trivial to verify.

Two suggestions for improvement: 1) add "sum" types (i.e., discriminated
unions), and 2) let the user define their own types via lambdas, such as
PrimeNumber.

~~~
seanmcdirmid
This is kind of pedantic. What programmers think of and find useful as types
and type systems is quite different from what type theorists think.

~~~
nahiluhmot
I would definitely find what curryhoward described as useful, but I don't
think Ruby is that language in which it should) be implemented.

That being said, sum types would be a nice addition to this library.

~~~
seanmcdirmid
I was only referring to the starting quote:

> When most practitioners think of "typechecking", they typically think about
> proving properties about programs statically.

This is what the type theory community (e.g. Bob Harper) thinks...not anyone
else from what I can tell (definitely not practicing programmers).

Typing it out on mobile didn't make it easy to do the right quote context.
Here is a good essay about it all:

[http://www.cl.cam.ac.uk/~srk31/research/papers/kell14in-
auth...](http://www.cl.cam.ac.uk/~srk31/research/papers/kell14in-author-
version.pdf)

vs. say

[https://existentialtype.wordpress.com/2011/03/19/dynamic-
lan...](https://existentialtype.wordpress.com/2011/03/19/dynamic-languages-
are-static-languages/)

There are other ways to do typing that might make more sense for a dynamic
language than going down the expressiveness rabbit hole; e.g.

[https://www.youtube.com/watch?v=__28QzBdyBU](https://www.youtube.com/watch?v=__28QzBdyBU)

~~~
curryhoward
Perhaps it's true that many programmers don't realize the fundamental
connection between typechecking and theorem proving (i.e., that they are the
same thing). But that wasn't meant to be the point. The point is that when
most programmers hear the word "typechecking", they think of compile-time
typechecking. This word is usually used in a static context.

I'm not saying that types or typechecking are inherently static concepts. I'm
also not saying that this library _should_ do static typechecking—that would
be an absurd demand. I only meant that the wording is misleading.

Even more misleading is author's use of the term "gradual type checking",
which has a well-understood meaning: the ability to add static checks to an
otherwise dynamically-typed program.

~~~
seanmcdirmid
> The point is that when most programmers hear the word "typechecking", they
> think of compile-time typechecking. This word is usually used in a static
> context.

Most programmers don't think "dynamic type checking" is a misnomer, while it
is true that "type checking" itself leans towards a static connotation.

I've seen gradual type checking used both ways in the literature, actually.
Wiki has the term defined for dual phase:

[http://en.wikipedia.org/wiki/Gradual_typing](http://en.wikipedia.org/wiki/Gradual_typing)

> Gradual typing is a type system in which variables may be typed either at
> compile-time (which is static typing) or at run-time (which is dynamic
> typing), allowing software developers to choose either type paradigm as
> appropriate, from within a single language.

I'm sure this is just poor writing (as Siek defines it, it should be from
dynamic to static), but there is enough confusion here where you might bother
using the term for adding stronger dynamic type checks to an otherwise less
dynamically typed language (if you admit dynamic types as types, of course).
Also, a dynamically checked type signature is the first step to a statically
typed one (as long as your types remain weak enough that static typing is
achievable in the future). So in that sense, it is "gradual" typing, just from
a meta perspective :)

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

I'm fairly sure it changes the performance characteristics of code it is
applied on. I'd recommend adding benchmarks to the README so prospective users
might be aware of this beforehand.

~~~
vidarh
As someone working on a Ruby AOT compiler (whether it'll ever be finished is
another matter...), I'd love to get some spec'ed out extensions for things
like this with semantics that explicitly allows implementations to recognise
the extension and decide to ignore the default implementation and instead
provide it's own version that could e.g. do compile time checks instead of
runtime checks, or hoist checks, whenever possible. Or use it for
optimisations (e.g. by hoisting out and eliminating other checks and caching
method lookups)

Ruby is flexible enough to allow quite a bit of experimentation with stuff
like that. _Especially_ because classes can be re-opened, so if worried about
performance, it'd be easy enough to allow type annotations (or whatever other
extensions people come up with) to live in separate files if people want them
to, and conditionally include them only as needed/wanted.

------
JoelMcCracken
This is very similar to contracts.ruby, which is cool. I'd love for more
contract discussions.

[https://github.com/egonSchiele/contracts.ruby](https://github.com/egonSchiele/contracts.ruby)

~~~
MrDosu
Personally I don't get contracts when they aren't used for static
verification.

It's much easier to use defensive coding techniques at runtime without having
yet another framework in there that anyone who wants to read the code has to
learn. Typing a few lines less is not really a benefit when it basically
increases code complexity.

~~~
actsasbuffoon
The Contracts gem is very easy to use. I introduced it at work recently, and
everyone got the hang of it in about 20 minutes.

A lot of languages (Java, C#, Go, etc.) have very inexpressive type systems,
and that tends to make many dynamic language developers wary of them.
Contracts has a very expressive type system though. It understands duck
typing, adhoc union types, and a bunch of other stuff that makes it a great
fit for a dynamic language.

Plus, we've disabled it in production, so we get coverage in test/development
mode, but no performance penalty in prod. I look at it as an extended form of
testing. Type contracts are very quick and easy to add, and they help tests
catch additional errors. They're also useful in code reviews. Sometimes I'll
have to search for 5 or 10 minutes to figure out what kind of thing is getting
passed in, where the contract makes it perfectly clear.

The best part is that the contracts are optional. If some method takes wildly
differing arguments and it's going to be a pain to give it a contract, then
don't.

This is the best tool I've added to my Ruby arsenal in some time. I strongly
recommend taking it for a spin.

~~~
MrDosu
I think you misunderstood me.

What I meant was that contracts in a language like C# give me the ability to
statically verify the program at compile time. It is not a "nicer way to add
conditional testing logic" but a way to formally prove correctness in my
programs.

~~~
JoelMcCracken
IIUC, contracts are specifically for creating verifications at runtime.

In some cases, sanity checking can happen up front/at compile time. In other
cases, these must occur at runtime.

The Pragmatic Programmer has a whole section on contracts; it is pretty good.

------
anaolykarpov
I find that putting the method signature at the end of the method definition
can become unreadable pretty fast. Also, the fact that it doesn't offer any
performance improvement (most probably, this will actually degrade
performance) makes me see this as a cool trick, but not really recommended in
production.

I like the aproach Perl 6 took on gradual typing. You can read about it in
this article which computes fibonnaci's
number:[http://blogs.perl.org/users/ovid/2015/02/avoid-a-common-
soft...](http://blogs.perl.org/users/ovid/2015/02/avoid-a-common-software-bug-
by-using-perl-6.html)

The only reason I wait for the next winter to come is because Perl6 will be
production ready by then as Larry Wall announced at FOSDEM this year.

~~~
arnvald
Since Ruby 2.1 (IIRC), method definition returns symbol, therefore it's
possible to something like

typesig def meth(arg) arg end, [Numeric] => Numeric

Although it's still end of the method, it already suggests that signature will
be there

------
cookrn
I wonder if it would be possible to configure this gem with "environments" in
the same way that Rails applications have environments. Then, in
development/test/CI-like environments, very strict checking could be applied
based on the specified types. Otherwise, in production-like environments where
"performance" may be more important, the type-checking could be looser or
simply pass-through.

Disclosure: this may be a Bad Idea (TM)

------
moe
This already exists in a relatively mature form:
[https://github.com/egonSchiele/contracts.ruby](https://github.com/egonSchiele/contracts.ruby)

------
nahiluhmot
Really cool idea! I like that you can specify a `#respond_to?` constraint
instead of a class. Not sure if OP is the author, but here's some feedback:

* It would be better if this didn't pollute the global namespace by defining `#typesig` in `Module` [0] -- perhaps consider refactoring that method into a module which the user may extend. Doing so would also get you out of needing to define `Module#prepend` for older versions of Ruby.

* Perhaps allow the user to enable/disable type checking at a global/class level. For example, then users could only enable type checking during specs if they wanted.

* Instead of using class-level variables, try using class level instance variables. They have less odd behavior when dealing with subclasses [1].

[0]
[https://github.com/gogotanaka/Rubype/blob/develop/lib/rubype...](https://github.com/gogotanaka/Rubype/blob/develop/lib/rubype.rb#L18)

[1] [http://www.sitepoint.com/class-variables-a-ruby-
gotcha/](http://www.sitepoint.com/class-variables-a-ruby-gotcha/)

Edit: Whitespace

~~~
saraid216
> Not sure if OP is the author

The OP is eduardordm, which can be found right underneath the article link. If
you look on his HN profile, his GitHub name is also eduardordm.

The repo author is gogotanaka.

So, no. He's not. And the repo author is consequently unlikely to ever see
your comments.

Not only are these simple checks to make, but it's extremely bad practice to
scatter commentary about a project everywhere across the internet; people who
only know about this project through GitHub would not see your comments
either.

~~~
jrochkind1
What are you suggesting, that there never be any discussion of code that lives
in a github repo on HN, but instead all discussion take place... in the GH
issues?

~~~
saraid216
Yes.

------
jaggederest
Reminds me of
[https://github.com/lucky/pedant](https://github.com/lucky/pedant) from a few
years ago. I added some basic argument assertions to it as well:
[https://github.com/lucky/pedant/pull/1](https://github.com/lucky/pedant/pull/1)

------
overload119
I've been writing Ruby for a few years on a number of production applications.

Recently I've had to pickup Hack for work, and if there's one thing I really
like about it is the type hinting. The best part is that it helps you handle
nullable types (not sure if it's done here).

When I switch back to Ruby from Hack, I find it harder to reason about my
program.

------
firlefans
More interesting to my mind is this (Diamondback Ruby):

[http://www.cs.umd.edu/projects/PL/druby/](http://www.cs.umd.edu/projects/PL/druby/)

Some features:

\--------------

Type inference:

DRuby uses inference to model most of Ruby’s idioms as precisely as possible
without any need for programmer intervention.

Type annotations:

Methods may be given explicit type annotations with an easy to use syntax
inspired by RDoc.

Dynamic checking:

When necessary, methods can be type checked at runtime, using contracts to
isolate and properly blame any errant code, similar to gradual typing.

Metaprogramming support:

DRuby includes a combined static and dynamic analysis to precisely model
dynamic meta-programming constructs, such as eval and method_missing.

------
siscia
Any now and then another language get some sort of type check, why we don't
build an agnostic type checker ?

Then we interface with the AST of any language and we can stop re-iventing the
wheel every two week...

It is so crazy ?

Nobody tried it before ?

~~~
vinceguidry
Type checking is typically done statically, i.e. during compile time. By the
time you have AST it's too late.

Think about it, how would you handle type annotations in an agnostic type
checker?

~~~
siscia
It's definitely me being to naive, however I would type all the expression, in
either the source code (into comment) or in a separate file, and all the
statement.

Now I do have a table of symbols and their type.

At this point its just a matter of using the AST to being sure that everything
is correctly typed...

Am I wrong ?

------
stewbrew
IMHO the state of static type checking/code analysis in ruby is still
deplorable and this (well known, rather trivial) approach won't ameliorate the
situation. Even javascript has more to offer in this respect. Who would have
suspected that 5 years ago.

Since I still like ruby's syntax, my hopes are that crystal ([http://crystal-
lang.org/](http://crystal-lang.org/)) will one day become more mainstream (and
maybe be adapted to some extent in mainstream ruby).

------
transfire
Already done many years ago:
[https://github.com/rubyworks/platypus](https://github.com/rubyworks/platypus)

------
shitlord
Is there an error in the section named "Typed method can coexist with non-
typed method"? There's a line that has `typesig :sum` but sum is never
defined.

------
revskill
Why Ruby 2.0 ?

~~~
damncabbage
In case the question is actually "Why not 1.9.3?", it's likely because 1.9.3
is End Of Life, and doesn't even get security upgrades now:

[https://www.ruby-
lang.org/en/news/2014/01/10/ruby-1-9-3-will...](https://www.ruby-
lang.org/en/news/2014/01/10/ruby-1-9-3-will-end-on-2015/)

[https://www.ruby-lang.org/en/news/2015/02/23/support-for-
rub...](https://www.ruby-lang.org/en/news/2015/02/23/support-for-
ruby-1-9-3-has-ended/)

