
Crystal 0.26.0 released - parvenu74
https://crystal-lang.org/2018/08/09/crystal-0.26.0-released.html
======
interfixus
Some grumpiness in quite a few comments. Pity. This is an awfully nice
language to work with, and the toolchain is starting to grow up - the compiler
appears much speedier these days than it used to. Also: A real grassroots
effort unencumbered by megacorporation agendas (Yes, I know. Manas is a
comparatively small Argentinian company, and kudos to them for having got the
ball rolling). Give these guys a hand like they deserve. If Crystal keeps on
the way it promises, parallellism _will_ be there, and will be a joy to
handle.

------
payne92
Pro tip: when posting to HN, include a summary of "what it is"! It's often
hard for readers to figure it out from a changelog.

From the docs:

"Crystal is a programming language with the following goals:

Have a syntax similar to Ruby (but compatibility with it is not a goal).

Be statically type-checked, but without having to specify the type of
variables or method arguments.

Be able to call C code by writing bindings to it in Crystal.

Have compile-time evaluation and generation of code, to avoid boilerplate
code.

Compile to efficient native code."

See: [https://crystal-lang.org/docs/](https://crystal-lang.org/docs/)

~~~
xellisx
I keep thinking it's Crystal Reports every time it shows up on HN.

~~~
estomagordo
Me too. Or whenever I see it mentioned in this Slack I'm in. It was very
confusing last December, when one guy kept solving Advent of Code in Crystal,
and doing so with speed.

------
patagonia
No note of parallelism on the roadmap?

[https://github.com/crystal-
lang/crystal/wiki/Roadmap](https://github.com/crystal-
lang/crystal/wiki/Roadmap)

~~~
deedubaya
iirc, it was removed/postponed to make reaching 1.0 more obtainable

~~~
patagonia
That just seems nuts in a world where AMD is putting out affordable 32 core
chips.

~~~
weberc2
I'm a big fan of shared memory parallelism from a performance standpoint, but
I don't see any reason why v1.0 of a language _must_ support this. Stable APIs
enable a lot more than shared memory parallelism, which is, for the most part,
a performance optimization. If you can delay the parallelism story by a few
months to move the stability story up a year, why not?

~~~
kjeetgill
I would call it much more than just a performance optimization.

A good story on parallelism is fairly high on anyone's list looking to move
away from a GIL language like Python and I believe Ruby.

As I'm sure you're aware, parallelism isn't always easy to bolt on afterwards.
Once a glut of your code is written violating your (unwritten) memory model
you're never quite sure if you've gotten rid of that last race condition.

~~~
sriram_malhar
Crystal's programming model is about concurrent actors that don't share
memory. Nothing should break when they move to multiple threads/event loops;
it just needs the scheduler to schedule multiple fibers onto different
threads. It's a solved problem.

------
weberc2
> In 0.25.0 we tried to improve the inferred type of unions of sibling types.
> Although it worked well for the compiler itself, some codebases out there
> exhibited some combinatorial explosion of unions due to this change.

What is a union of sibling types? Why would improving type inference require
unions to be _generated_? I skimmed the PR referenced in the changelog, but it
assumes domain knowledge I don't have (I'm only nominally familiar with
Crystal).

~~~
ezrast
I'm not a Crystal developer but I think this is accurate:

The way Crystal's type inference works, if you have a variable that sometimes
can be assigned a Foo and sometimes can be assigned a Bar, then that variable
will have the inferred ("generated") union type (Foo | Bar). This usually
works great, but the compiler runs into performance issues if these unions
become very large (more accurately, it happens when the _number of distinct
such unions that gets passed to a particular method_ is large, for which a
large possible union space is a precondition).

In practice, this tends to happen in class hierarchies with many descendants
from a single root class. As a somewhat kludgey workaround, the compiler will
simply forget about the union in these cases and use the parent class instead.
For example, a method that looks like it should take a (Bat | Cat | Dolphin |
Dog | KoalaBear | Hog) will actually just take a Mammal. This can be bad if
Mammal doesn't implement all the common capabilities of those specific
subtypes. For example, adding an Echidna subclass that doesn't implement the
birth_live_young method can cause calls to `cat_or_dog.birth_live_young` to
fail even in places that type inference "should" be able to determine that
`cat_or_dog` will never hold an Echidna. However, it's deemed a necessary evil
unless/until the compiler can be reworked to handle such cases without
exploding compile times.

~~~
weberc2
That makes some sense, but I don't understand why the explosion would be
combinatorial and not linear.

~~~
ezrast
If a method can handle _n_ different types at runtime, then there are _2n - 1_
possible union types that can be passed to the method: A, B, C becomes A, B,
C, (A | B), (A | C), (B | C), (A | B | C), and that's without taking into
account generics and nested type hierarchies. Not all of these unions will
necessarily come up organically in code, but the class hierarchy of AST nodes
used within the compiler was large enough that it was causing issues in real
life.

~~~
weberc2
Correct me if I'm wrong, but you don't need to generate every permutation to
determine whether or not a given union type can be passed into a method,
right? If the parameter is of type `A | B` and the function takes `A | B | C`,
you don't need to generate the whole space to determine that `A | B` satisfies
`A | B | C`. Also, for v1.0 of the language, it doesn't seem like it would be
a big deal to put the onus on developers to recast their `A | B` to a `A | B |
C`.

~~~
ezrast
It's not just about validating types. If B and C are implemented sufficiently
differently, the compiler output for a function that takes (A|B) will be
different than for the same function that takes (A|C), and both of those
outputs can end up coexisting in the final executable. Unfortunately I don't
know enough about the compiler internals to go into further detail.

As for manual recasting, sure, it's possible, but it's not a great tradeoff.
Either you do type inference correctly and have developers crashing the
compiler unless they recast their variables in seemingly arbitrary ways, or
you cheat on inference and have developers getting "undefined method" errors
on classes they didn't think they were using until they learn the rules.
Ultimately it was decided that the latter was less common and easier to deal
with in practice (just add a PlacentalMammal subclass to your class hierarchy
that includes Cat and Dog but not Echidna). It's not a decision anyone is
thrilled with but I think it's reasonable.

~~~
weberc2
I guess "larger binaries at v1 but correct" and "slightly less ergonomic at v1
but correct" both seem like pretty good tradeoffs to my mind, but at this
point we seem to be pretty deep into speculation.

------
emmelaich
People interested in Crystal might also be interested in _Inko_.

It's Ruby-ish but less compatible, [https://gitlab.com/inko-
lang/inko](https://gitlab.com/inko-lang/inko)

> _" Safe and concurrent object-oriented programming, without the headaches.
> Inko is a gradually-typed, safe, object-oriented programming language for
> writing concurrent programs. By using lightweight isolated processes, data
> race conditions can not occur. The syntax is easy to learn and remember, and
> thanks to its error handling model you will never have to worry about
> unexpected runtime errors."_

