Crystal 0.18.0 has been released!
This is a huge release with many new language and standard library features, and a few breaking changes as well.
Let’s review them.
Union as a first class citizen
Union types exist in the language since day one. However, they were types without a name. Sure,
you could write
Int32 | String in type expressions, but you couldn’t write
the same way you can write
Tuple(Int32, String) to denote a tuple of two types.
Now you can. And you can also add class methods to the
Union type. This seemingly small change
adds a lot of power to the language.
For example, we can define a method to parse a JSON string into a
Union. To do this we try
to parse the string for each type in the union, as can be seen here
(the code could be just these 9 lines, but
it’s more complex because it has fast paths for primitive types)
With that, we can now write:
We can also use unions in mappings, even unions of complex objects:
In short, more expressive power and type safety.
Hash, Enumerable and block auto-unpacking
We won’t deny it, Crystal has a lot of inspiration in Ruby, be it in some of its syntax and a huge part of its standard library.
Ruby’s Hash, a mapping from keys to values,
Enumerable. But there’s something a bit magical happening in
Hash. Take a look:
So, we can iterate a
Hash and get its keys and values, and we can also use
map on it,
and transform the keys and values. But how does it work?
One would think that
each like this:
Then maybe Enumerable’s
map is implemented like this:
However, that doesn’t seem to be the case, because if we define our own
method that doesn’t use a splat, it works as expected:
What’s going on?
The answer is that if a method yields an array, and the block specifies more than one argument, the array is unpacked. For example:
Ruby’s solution is very convenient and powerful: it lets us iterate a hash as if it were a sequence of keys and values, without us having to care if it’s internally implemented as such; and when we want to add methods to Enumerable we don’t need to use splats to “get it right”, we can just treat each yielded element as a single object.
In Crystal we decided to do the same, although for tuples, because their size
is known at compile-time. This means that the first Hash snippet now works
exactly the same as in Ruby, and
Enumerable’s code remained the same, and
extensions to it will continue to work well.
Splats in yield and block arguments
Splats now work in yield and block arguments. This makes it trivial to forward block arguments to another method:
Named tuples and arguments can be created with string literals
Named tuples were introduced in the previous release but only allowed identifiers as keys.
Starting from this release, we can use a string literal too. This makes it possible to have named tuples with spaces and other symbols:
This is a breaking change, as that syntax used to mean a Hash with string keys. Now, only
=> means Hash, and
: always means a named-something.
Why is this useful? Consider a library like html_builder, that provides an efficient DSL for generating HTML:
We say it’s efficient because
HTML.builds creates a string builder, and methods
append to it. For example the
a method appends
"<a ...></a>", and so on. And,
in this case, the argument to
a is a named argument (
href), which on the method
side gets captured as a named tuple, iterated and appended to the string builder,
so no memory allocations other than that for the string builder exist.
The problem is, if we wanted to have a
"data-foo" attribute we couldn’t do it:
we’d had to use a
Hash, which is much slower. Well, now we can:
This is just one use case, but one can imagine many more uses cases. For example, generating JSON objects with keys that have spaces:
Class variables are now inherited
Class variables now work more like Ruby’s class instance variables: they are available in subclasses, with the same type, but each subclass has a different value for it.
OpenSSL and TLS improvements
@jhass and @ysbaddaden took the lead on improving and stabilizing OpenSSL and TLS-related functionality in the standard library. Be sure to read the changelog to see all the additions and changes. Huge thanks to them!
For Functional Languages fans…
User defined classes can now be generics with a variable number of type arguments. The built-in
Tuple, Union and Proc use this. For example,
Proc(*T, R), with
T being the arguments
R the return type.
With that, and because
R can be queried at compile time, we added a
One could even define a
curry method if we wanted too. But we leave that as an exercise
to the reader (hint: use a different struct to represent a curried method).
There are more little features, like being able to use macros in more places, better error
message when an
as cast fails, and several enhancement to the standard library.
Thank you everyone who contributed to this release! <3