Question

I know Ruby is dynamically and strongly typed, but AFAIK, current syntax doesn't allow checking the type of arguments at compile time due to lack of explicit type notation (or contract) for each argument.

If I want to perform compile-time type check, what (practically matured) options do I have?

Update

What I mean type-check is something like typical statically typed language. Such as C. For example, C function denotes type of each argument and compiler checks passing-in argument is correct or not.

void func1(struct AAA aaa)
{
    struct BBB bbb;
    func1(bbb);  // Wrong type. Compile time error.
}

As an another example, Objective-C does that by putting explicit type information.

- (id)method1:(AAA*)aaa
{
    BBB* bbb = [[AAA alloc] init];  // Though we actually use correctly typed object...
    [self method1:bbb];             // Compile time warning or error due to type contract mismatch.
}

I want something like that.

Update 2

Also, I mean compile-time = before running the script. I don't have better word to describe it…

Was it helpful?

Solution

There was a project for developing a type system, a type inferencer, a type checker and a syntax for type annotations for (a subset of) Ruby, called Diamondback Ruby. It was abandoned 4 years ago, you can find its source on GitHub.

But, basically, that language would no longer be Ruby. If static types are so important to you, you should probably just use a statically typed language such as Haskell, Scala, ML, Agda, Coq, ATS etc. That's what they're here for, after all.

OTHER TIPS

RDL is a library for static type checking of Ruby/Rails programs. It has type annotations included for the standard library and (I think) for Rails. It lets you add types to methods/variables/etc. like so:

file.rb:

require 'rdl'

type '(Fixnum) -> Fixnum', typecheck: :now
def id(x)
  "forty-two"
end

And then running file.rb will perform static type checking:

$ ruby file.rb
.../lib/rdl/typecheck.rb:32:in `error':  (RDL::Typecheck::StaticTypeError)
.../file.rb:5:5: error: got type `String' where return type `Fixnum' expected
.../file.rb:5:     "forty-two"
.../file.rb:5:     ^~~~~~~~~~~

It seems to be pretty well documented!

While you can't check this in a static time-sense, you can use conditionals in your methods to run only after checking the object.
Here the #is_a? and #kind_of? come in handy...

def method(variable)
    if variable.is_a? String
        ...
    else
        ...
    end
end

You would have the choice of returning specified error values or raise an exception. Hopefully this is close to what you are looking for.

You are asking for a "compile-time" type check, but in Ruby, there is no "compile" phase. Static analysis of Ruby code is almost impossible, since any method, even from the built-in classes, can be redefined at runtime. Classes can also be dynamically created and instantiated at runtime. How would you do type-checking for a class which doesn't even exist when the program starts?

Surely, your real goal is not just to "type-check your code". Your goal is to "write code that works", right? Type-checking is just a tool which can help you "write code that works". However, while type-checking is helpful, it has its limits. It can catch some simple bugs, but not most bugs, and not the most difficult bugs.

When you choose to use Ruby, you are giving up the benefits of type-checking. However, Ruby may allow you to get things done with much less code than other languages you are used to. Writing programs using less code, means that generally there are less bugs for you to fix. If you use Ruby skillfully, I believe the tradeoff is worth it.

Although you can't type-check your code in Ruby, there is great value in using assertions which check method arguments. In some cases, those assertions might check the type of an argument. More frequently, they will check other properties of the arguments. Then you need some tests which exercise the code. You will find that with a relatively small number of tests, you will catch more bugs than your C/C++ compiler can do.

It seems you want static types. There is not an effective way to do this in Ruby due to the language's dynamic nature.

A naive approach I can think of is to make a "contract" like this:

def up(name)
  # name(string)

  name.upcase
end

So the first line of each method will be a comment declaring what type each argument must have.

Then implement a tool that will statically scan & analyze the source and catch such errors by scanning the call sites of the above method and check the type of the passed argument whenever possible.

For example this would be easy to check:

x = "George"
up(x)

but how would you check this one:

x = rand(2).zero? "George" : 5
up(x)

In other words, most of the time the types are impossible to be deduced before runtime.

However if you do not care about the "type checking" happening statically, you could also do:

def up(name)
  raise "TypeError etc." unless name.is_a? String
  # ...
end

In any way, I don't think you will benefit from the above. I would recommend to make use of duck typing instead.

You might be interested in the idea of a "Pluggable type system". It means adding a static type system to a dynamic language, but the programmer decides what should be typed and what is left untyped. The typechecker stands aside the core language and it is usually implemented as a library. It can either do static checking or check types at runtime in a special "checked" mode that should be used during development and to execute tests.

The type checker for Ruby I found is called Rtc (Ruby Type Checker). Github, academic paper. The motivation is to make the requirements on a type of a parameter of a function or method explicit, move the requirements out of the tests into type annotations and turn the type annotation into an "executable documentation". Source.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top