Язык программирования

Auto

01 Apr 2015 by asterite

Note: this was an April’s Fool post. However, with Crystal macros you could do this.

We Crystal developers belive compilers should be smart. You don’t need to add type annotations everywhere: only when needed, or when you want them. Can we make the compiler even smarter?

This past month we have been thinking that since Crystal is a relatively young language with a still incomplete standard library and ecosystem, there are lots of things to code yet. It’s unfortunate that many of these algorithms and data structures are already present in other languages. Not only that, but these other languages have been used for many years now, so their implementation is pretty robust and bug free. We will have to walk the same road in Crystal. Or will we?

Well, not anymore. The next release of Crystal will have a tiny but powerful addition: an auto keyword. To understand how it works, let’s see it in action:

class String
  auto def succ
  end
end

"hello".succ #=> "hellp"

The first thing you need to know is that String#succ is not in Crystal’s standard library. In the code above we define it with the auto keyword, leaving the body empty. We then invoke the method on some string and it gives the correct value. Awesome! Crystal not only deduced the return type of succ, it also deduced its behaviour!

How auto is implemented

When we said auto is a keyword, we lied: it’s a macro. Macros in Crystal receive AST nodes, that is, they receive syntax. auto then receives a method definition and processes it at compile-time to generate a method definition that implements the desired functionality:

macro auto(method)
  ...
end

Macros can inspect the arguments: they can ask the method’s name, arguments or where the method is defined (String in the above example). If you need to do more complex stuff, you can invoke run in a macro, like this:

macro auto(method)
  {{ run("auto/process", @type, method.name, *method.args) }}
end

This will invoke the program auto/process.cr passing the type name, method name and splatted method arguments to the program. The program then receives these arguments in the usual ARGV array, processes them and outputs a method definition that will then be embedded in our original program. Neat, right? We use a similar technique for ECR (similar to ERB): the ECR templates are processed at compile-time.

The auto/process.cr program does a few things: it searches the internet for relevant method definitions together with their source code and possibly associated tests/specs. Right now this is only done for Ruby code because of its similarity with Crystal, but support for other languages is coming soon. Then it processes the code and generates Crystal code.

Now, this can be quite slow. In fact, it takes a few seconds (5 seconds on one of our machines). Luckily, the generated code is cached in the usual “.crystal” directory so the next time auto is used for the same method of a same type, it will reuse the cached version. But even with this penalty, think of the time you save by using auto: you don’t have to write the method, plus you reuse existing robust and well-tested code!

auto types

You can even use auto on a type:

auto class LinkedList(T)
end

list = LinkedList(Char).new
list.push 'a'
list.push 'b'
list.push 'c'
puts list.size #=> 3
puts list        #=> ['a', 'b', 'c']

So the auto macro actually checks whether the received AST node is a class or method. For the class case, auto/process.cr will search that class name on the internet and generate a definition for it together with every method it can find for it, reusing the previous logic.

Trying it

You can try all of this by checking out the auto branch in our GitHub repository, but you’ll need to compile a new compiler because we added some macro methods for this feature. Please understand that this is still very new so any bug you find, please report it!

comments powered by Disqus