While that sounds interesting for a toy language or research language, I’m imagining dealing with it when reading code someone else has written and I’m already exhausted.
This has been a good discussion, and thanks for all the historical|technical references people have provided.
I’d just like to suggest that looking toward the future (Crystal 2.0) we can provide a more “intuitive” and “ergonomic” coding environment. Even though these are subjective terms, we can try to define some objective use cases.
It seems the current operator precedence is (mostly) a legacy from C. But I think we can define operator precedence, and language use, that is more natural and reflective for current times.
In the past, programmers had to act as sort of their compilers, because there weren’t the tools available to catch common errors, let alone human based ergonomic rules to allow the programming|compiling process to assess intent. But languages (human and programming) change, and have come long ways since 1940-80.
The bit operators, x = & | ^
have a specific structure and use cases, requiring two integer operands a x b
, and produce one integer result c
. So in languages where spacing doesn’t matter, you can write: axb
, a xb
, ax b
, or a x b
.
So if someone were to write this snippet: m + p + n | 1 + j + k
, assuming the variables are integers, it would currently parse as: (m + p - n) | (1 + j - k)
But I think most would understand it to mean: m + p - (n | 1) + j - k
An easy rule to force this meaning could be: m + p - n|1 + j - k
What I’m trying to highlight is that current bit operator precedence is legacy technical debt from the beginning of modern programming. We don’t have to live with it.
As we advance, we can make programming tools advance with us.
Python essentially went through a 20 year “war” to conceive, propose, create, then migrate from Python 2 to 3. Why? Because enough people realized there was just too much technical debt embedded in Py2 to overcome with tweaks, and something new needed to be created, with the understanding old code wasn’t going to work in it. A similar process occurred with Perl, to the point that a whole new language, Raku, was created because of it.
I am proposing we should look at improving a programmer’s experience using Crystal with an eye to the future, and not one tied to the past. What made sense then may not make as much sense going forward.
I would hope we can have an open and constructive conversation on these topics.
I absolutely agree that something being the way it is isn’t a good reason to avoid changing it in the face of a better alternative.
My disagreement is around the content and attitude behind this statement:
I think there’s no reason to believe that there’s a “default” way for a person to understand the precedence in that expression. It’s clear that you would understand it that way, but I know that I would see a bitwise operator and think that I need to be careful in my interpretation (rather than understanding the precedence in a particular way off-the-bat).
Since I don’t agree that there’s evidence that most people would interpret it that way, I don’t see sufficient motivation to change the existing operator precedence. “This is the normal way of doing X” isn’t a good reason, but it is a reason, so for me it outweighs what I see as no reason. That said, if I saw compelling evidence that changing the operator precedence is somehow psychologically, practically, or formally better then I probably wouldn’t object (for Crystal 2.0).
Looking at the expression objectively, the only information that provides for a specific bit operation is the 1
, so n | 1
is clearly setting the lsb of n
to 1
. Everything else is pure speculation, as the current parsing provides no specific info on the operands to either side of |
.
This is a contrived example to show, 1) this kind of code would never occur in a real program that required known outcomes, and 2) but logic says to make any sense of it, assume the portion that makes the most sense the most reasonable thing to assume.
No.
Forking Crystal and editing src/compiler/crystal/syntax/parser.cr to make it behave the way you want might be the quickest solution. That way, you don’t need to convince the core team—you just get what you want.
I’ve done something similar myself: to make Crystal more convenient for running one-liners, I made a small tweak to eval
. I created a branch called oneliner and added a single file, eval2.cr, which is automatically loaded via require "./command/*"
. When a new Crystal version is released, I rebase and force-push. It’s a bit of a hassle, but still much easier than trying to convince the 97% of people who don’t care about one-liners. Besides, my code usually ends up having bugs anyway, but at least I don’t have to worry about causing trouble for anyone else. It makes things feel a lot more relaxed.
After making your changes, you could run the test suite to see which parts of the standard library depend on the current operator precedence. Fixing those spots one by one might actually be fun as a personal project. If you write up your findings in a blog post, it might be useful to others in the community.
When I have some time, I will consider this.
There’re others things I might also modify, so a customized
version might be fun to create.
This is a good suggestion! I haven’t maintained a parallel version myself, so I’m not sure if this would save any effort, but another approach would be to maintain only a set of patch files. I’ve seen people do this to patch open-source phone operating system kernels for specific devices so that they don’t need to worry about keeping up with updating for new kernel versions until there’s a conflict with one of the patches.
I believe you can accomplish this with diff
and patch
or with git diff --patch
and git apply
.
It’s worth mentioning that there is a massive downside to this, especially if you care about others being able to build your code.
Normally, it would just result in a failure to compile; in this case, the issue is even worse because the code may compile but not work correctly.
I know of a tool that looks like JavaScript at first glance but is actually designed to run on a custom runtime.
Some people prefer this style when they want to focus on algorithms and computation without relying on external libraries.
But honestly, I find it rather inconvenient to use.
With Crystal, being able to distribute a binary might make the situation a little better — at least a tiny bit easier for others to try out.
(The author once tried Crystal, but it seems he’s given up on it recently.)

there is a massive downside to this
(emphasis mine)
To clarify, “this” is “maintaining a custom compiler codebase”, right? (Mostly asking for the sake of future forum readers.)