Crystal 0.31.1 has been released!

// in math expressions, looks like a regex remnant that got lost in the wild and infiltrated its way into your code :laughing:

I like the way Ruby deals with using / for both integers and floats, so a user’s code looks like normal (expected) math expressions.

For integer results: 7 / 2 => 3 or a.to_i / b.to_i

For float results, make at least one value a float: 7 / 2.0 = 3.5 or a / b.to_f

Should be easy for compiler because source code explicitly sets numeric types.

If I remember right, the math symbols are implemented as a method call on the type of the left argument in crystal.

Being a lazy bad programmer that hates unit tests, I often cast var_int / var2.to_f in Ruby to return a float, rather than call result.to_f. But Crystal is using the left-side type. var.to_f / var_int would dynamically return a float, but the other way around would round down to zero, confusing users.

With the rest of the types to dynamically manage I think the core devs decided that consistent output was a better compromise than a massive switch statement and weeks of QA. So they’ve erred on the side of flexibility to soften the edges of the type system. I see the logic in the trade-off considering where most of the user audience will be drawn from - one of the top questions to type system newbies is why division doesn’t return a float - they expect 3/4 = 0.75, not 0.

I don’t know what this phenomenon is called, but I believe when a repo reaches a certain point in time, there is a disconnect that can happen every once in a while. This disconnect is always between the core developers (working on the internals of the language), and the average user. I’ve seen it happen with Godot’s repository (been there for over half a decade), and starting to slightly see it in Crystal’s repo.

An example in Crystal: (Time.now -> Time.local)? Reason? “Because it makes more sense”

Maybe to the contributor / core developers who implemented the PR, but to me, Time.now is far more explicit and less ambiguous than “local” or “utc”. Time.local? Local to what? We already know it’s a local time because we looked at the API! We want the TIME! (.now). This is equivalent to the English language changing how a word is spelled, after 50 years, just because someone thought it sounded funny.

Similarly, in Godot, 5/2 = int, and 5/2.0 = float. Same in the C language. Same for Ruby!
Crystal utilizes several C functions. And after reading this, a developer will inherently assume… this operation would work the same.

In Godot, the author (reduz), created GDScript to be easy to use, have high readability, dynamically typed, dev friendly syntax, lightweight, etc. Over time, a new “warning system” was added which bombarded the developer with any kind of error. Then, static typing was introduced, which now it’s not even simple GDScript anymore. Luckily, it’s optionable, and reduz is minimalist and very conservative about what PRs to merge, etc. That is the only thing protecting Godot from becoming a heap. Which is why I still use Godot, because I have faith in reduz and know Godot is going to be okay. I mean, they even had to create a separate repo (godot-proposals) because of the massive influx of ideas/PRs.

My point is it’s not about a feature creep of new additions to the language (Crystal removing markdown was a good thing). It’s about the use case a core developer might think is okay, compared to what an average user might do. I understand contributors are super important because of their knowledge of the inner-workings of the language. This is vital, however, this doesn’t necessarily mean PRs are objectively correct. PRs needs to make MORE SENSE to the community, not just several contributors.

For example, my issue reporting an arithmetic overflow. The arithmetic overflow error is a GREAT new exception because it makes the compiler stronger and more versatile detecting bugs. These kinds of changes will make Crystal prosper, not / -> // or Time.now -> Time.local.

“But but, girng is a newb, has not contributed at all to the language, why listen to him?”. Because I’m your average user!!

I suggest reading similar motivations for Python (which also has / and //) here: PEP 238 – Changing the Division Operator | peps.python.org

However, this paragraph is worth noting:

The problem is unique to dynamically typed languages: in a statically typed language like C, the inputs, typically function arguments, would be declared as double or float, and when a call passes an integer argument, it is converted to double or float at the time of the call. Python doesn’t have argument type declarations, so integer arguments can easily find their way into an expression.

What this means is that if you have this:

def average(numbers)
  numbers.sum / numbers.size
end

and you test it with float numbers, it will work fine and you’ll believe it works fine. But then you try it with integer numbers and it won’t work fine because / will truncate the result (if / truncated the result).

So it’s exclusive to dynamic language but also to languages where the input types might not be specified, as is the case of Crystal.

However, in Crystal the expression will have a type, and if you try, for example, to assign the result to an instance variable expecting a Float you will get a compile-time error. So in a way Crystal will prevent / to be used incorrectly in some cases.

A part of me doesn’t agree with this change, and a part of me thinks it might be good. So far, Python says / and // are good to have for dynamic languages (Crystal is not) and / and // exist in Elm to be able to infer the type of an expression to either Int or Float, but Crystal doesn’t need that. Go works perfectly fine with / truncating integers… so I don’t know whether this was a good thing to do in Crystal, but we have time to experiment.

Edit: fixed text, cat stepped on keyboard and deleted some text.

1 Like

LOL

1 Like

I’m usually more into chaos, seance, and self-gratification than picking over semantics … but I thought Python was the only language to do this with // as a floored operator. Elm isn’t a general purpose language so, to me anyway, they’re not hurting anyone but themselves.

Of the other LLVM backed languages, kotlin, swift, pony, rust, and of course clang all round-down to the type. If going to all this trouble to have a typed language, it seems like adding features that make us work against compiler magic is a little weird. I don’t say “wrong” because there is no wrong. And I like weird so I can live with it. :smile:

edit: for example, consider this snippet of code for bijectives

while i > 0
  s << B64_ALPHABET[i.modulo(base)]
  i /= base
end

this basic bit of code in thousands of libraries is now broken in crystal … so the authors will have to go through a learning curve / bug hunt before realizing that i is cast to a float

edit #2. in case anyone is wondering, crystal’s b64 implementation calls down to LLVM’s Intrinsics.swap32 to do the real work here. no division bug possible like my slow-code snippet above.

For reference, here is the infamous float division issue:

Indeed this does look like a Python thing. I’d forgotten about Nim so I went over to have a look. Lo and behold: / returns a float64. Div must be used for round-down integer division.

https://nim-lang.org/docs/system.html#div%2Cint%2Cint

Another strange thing is that Nim also has a truncate function, trunc, for both 32 and 64 bit floats that just removes everything after the decimal, while .floor returns an integer not larger than x.00 passed as its argument. I don’t quite understand the nuance there (and they include a ceiling function), but I suppose it has something to do with edge cases bumping up against type overflow.

https://nim-lang.org/docs/math.html#trunc%2Cfloat64

There comes a time in just about every open source project (if it becomes popular enough) where its intrepid founder(s) need to realize the project is no longer just theirs. That people other than them are using it, and expect (and want) certain things from it. At that point (as with children) the project’s parents must relinquish the need to maintain absolute control and recognize the user community needs now are (should be) the highest priority.

The breaking changes in users code introduced in 0.31.0 are, IMO, examples of not putting the consideration of users first before introducing them.

I am not aware of any clamoring from the user community of a need (desire) to change the operation of /. It worked perfectly well before 0.31.0, is consistent with its use in Ruby (whose users I thought you were trying to attract), and added another operator users now need to know about, and how/when to use. And it was done with no public discussion with the user base (that I’m aware of) to give us warning of ts coming.

The change of Time.now to Time.local is even more puzzling. I can’t think of an instance where language devs replaced a short clear method name with a longer one for the same operation. Again, I am not aware of any surveying of the user community to establish the need/merits/viability for doing this.

And this is not the first time major changes that affected user expectations/desires occurred. Remember the (completely arbitrary) changes to output display that made numerical output include their type! Thank goodness @asterite got that rolled back, at the threat of leaving the project.

One of (if not THE ) most endearing qualities of Ruby is that Matz puts user happiness first. In fact, I’ve gotten a method added to it (in 2.5). It has a vibrant community, which he and the core team actually listens to. He’s incorporated stuff from Rails because people wanted it in Ruby too. Out of all the languages I use and follow, Ruby by far (via Matz) is the most open to tangible change from user and community input.

I know the Crystal dev team is much smaller, more overworked, hardly paid, under resourced, and maybe don’t feel appreciated enough (you ARE appreciated by me). Under these conditions, I think it would serve you even more/better to seek user advice and acceptance first.

Nim finally went 1.0 on September 24, 2019, after years of languishing. IMO it’s really not as stable as a user would expect (things changing old code still coming) but I think a major reason it pushed out a 1.0 release was its (small) community was pushing for it (for various reasons).

Crystal has sooooo much potential and usefulness, please stop nitpicking about cosmetic stuff. Please revert back the behavior of /, give us back Time.now and focus on the really important new technical stuff.

And ask the user community to weigh in more about impending ideas and changes before you thrust them upon us. The more Crystal becomes an our language versus a your language, the more it will be used and the faster it will be adopted by others.

1 Like

I’m torn on the // issue (though I do think that it makes the behavior more explicit for a developer reading the code), but I completely support the change from Time.now to Time.local. The point (as was discussed when the change happened) is that Time always needs a context. “Now” is (presumably) the same time in various time zones, but it is not the same Time. So we see that we have a relationship in which “now” maps to multiple potential instances of Time. It is indeed clearer that Time.local refers to the current time in the time zone where the code is being executed, while Time.utc clearly gives the timezone context as well.

I don’t think that the original creators and developers of Crystal are always right about what should and shouldn’t be in the language; I know @asterite has mentioned before that sometimes the experience of using Crystal has been hidden behind the experience of working in the Crystal compiler. However, I think the arguments that are being made don’t apply in this situation. Crystal isn’t even 1.0 yet, and we all use the language knowing that breaking changes are liable to happen. These criticisms seem to me to be motivated not by a real sense that the language changes impede usability (and I’d disagree, if that were the motivation) but rather because the changes in this pre-1.0 language broke people’s code. That’s just something we need to live with for the moment, and it’s the whole point of the crystal line in your shard.yml.

Incidentally, the idea of relying on division returning an integer is really weird to me. That’s never how it’s worked in mathematics, and I think it’s reasonable to require a specific integer division operator for it. However, maybe I’m biased by having learned Python early on in my programming education.

1 Like

The ship has already sailed for me.

My takeaway from all this: I can either conform to the new changes and continue developing my gameserver and be a happy camper, or loathe in self-pity complaining about petty things. I will choose the former, because the latter makes me an emotional wreck. I also feel like it’s a giant waste of time! I could have spent all that time on GitHub working on my game!

Plus, Crystal is not even 1.0 yet, so changes are bound to happen. This is normal in an open source project I think. A good developer is a flexible developer. I am going to try to stay positive and continue having fun working on my gameserver.

3 Likes

@jzakiya Great reply!

As an answer, I think the main reason we do these changes is because we want to see Crystal be as useable as possible without having to introduce breaking changes after 1.0.

/ vs. //

The reason for / vs. // is more of a purity thing: / for integers does floor division, while for floats it does a float division. The semantic of the operator changes for different types. With the current approach (after Crystal 0.31.0) this is now consistent: / always means float division, and // always means floor division.

This has been discussed a lot in the past (in the GitHub issue and also in other communities or even in Python’s PEP) but as a summary:

  • 10 / 4 giving 2 is not intuitive. If you do math, if you put that in a calculator, if you ask Google, everyone will tell you the answer is 2.5. So expecting 2 is familiar if you come from C, Java, Ruby, Go, etc., but it’s not simple (I recommend Rich Hichkey’s Simple Made Easy talk).
  • When programming in Ruby many times I have to remember to add .to_f to the first or second argument of a math operation just to make sure I do the math right. If you are familiar with C, Ruby, Java, etc., this is something you eventually get used to, but it’s so much simpler just to be able to write x / y and know you get a correct result. Ruby also has fdiv because converting to a float doesn’t work nicely with complex numbers.

You can also try to do the exercise I commented here:

Write a function that computes the average of two numbers. The signature is this:

def average(x, y)
  # fill in the body
end

The function should work with every number type: integers, floats and also complex numbers.

Here are some examples:

require "complex"

average(10.5, 2.3) # => 6.4
average(10, 1) # => 5.5
average(Complex.new(1, 2), Complex.new(4, 5)) # => (2.5 + 3.5i)

Do it with a previous Crystal version, for example 0.29.0, where / does a floor division for integers. Then do it for 0.31.0 and see which one is simpler and easier to read/write. You can use https://carc.in/ to use older Crystal versions.

It’s also interesting to know that in Python, Haskell, Elm and Nim, / is floor division, like in Crystal. And Python is now geared towards math and science so it might make sense for / to always do floor division (the PEP explains this too).

Time.now vs. Time.local

This is also the case of something familiar vs. something simple. Time.now is familiar: it’s in practically every programming language. However, I remember many times having to do Time.now.utc or the other way around in Ruby after finding bugs. This is because when writing Time.now it’s not clear where that “now” is: is it here where the code is running, is it UTC, or what?

With Time.local and Time.utc there’s no problem anymore: it’s clear from the method name.

I think this change is mainly annoying because a lot of code that uses Time.now already exists. But thinking it for the future, Time.local might be more intuitive to use.

I suggest we put back Time.now that only gives a compile-time error suggesting to use Time.local or Time.utc, instead of just saying “undefined method”.

Why these changes are annoying?

At some point we introduced deprecation warnings. The idea was that you would get warnings when using deprecated methods, or even / for integers, so that your code would still compile and you could slowly migrate the code so when we eventually remove or change things your code will still compile.

I suggested to make warnings opt-out: you will get warnings unless you passed a flag. My reasoning was that people will never find or use warnings if it was opt-in because it’s something you have to do, and we humans are lazy by nature (we try to do what’s simpler for us). But it was finally decided to make it opt-in to avoid annoying users with walls of warnings.

What happened? Apparently users never knew about these warnings, and eventually when we broke things people got really annoyed (like it can be seen here).

So I think part of the annoyance was not making warnings opt-out.

Luckily, starting from Crystal 0.31.0 warnings are opt-out :slight_smile:

What else?

In Crystal we are not afraid of making “breaking changes” compared to Ruby or other languages if we think carrying the baggage over is worse than trying to do something new and better. We think of Crystal as a new language, now a language that you use by copying code from another language and expect it to work with minor changes.

For example we have Object#to_s(io) instead of the classical toString() or to_s to avoid creating intermediate objects when converting things to strings.

Another example is detecting nil at compile-time: we could simply define method_missing for nil that just keeps returning nil (like in Objective-C) so that programming becomes easier (no more fighting with the compiler!) but the programs become less reliable or bug-prone.

5 Likes

Thanks @asterite for the detailed explanations of the changes.

I’m a GAM (grown ass man) so I was mostly (highly) just annoyed about the / change because I was totally unaware it was coming, not so much its technicalities.

Nim is even more anal :smile: because not only does it do div for // but also mod for % and (xor, and, or) for (^, &, |). The key thing is, coming to Nim you learn from the beginning that’s how they do it. You get annoyed about it (if you must) and then just use it without thinking about it further.

Here we had a significant change (you mean there really is no Santa :fearful:) and you didn’t provide us resistant to change humans the reasons to learn a new trick.

One of the main reasons Nim users pushed its devs to 1.0, was they seemed so enamored in creating the perfect language users couldn’t do things for the long term because their code broke every x months (still will). They (literally) forced its quantum wave function to collapse to some (any) reality they knew wouldn’t change (too much).

Just be cognizant of human psychology.

It’s easier to get (Johnny, Maria, Kwame) to go along with something if you first said to them, hey guys, I’m thinking about doing this, that, and the other, for these (very good to you) reasons, what do you think?..instead of hey you guys, I just decided to change things (again) to do x, y, z, (so you have to change your life to deal with it no matter what you think about it). Now you don’t need to verbalize the last part for the ‘‘guys’’ to think that. Humans, remember.

So my (tangible) suggestion/request is to just TAKE THE TIME to make more frequent blog posts, create more forum discussions, i.e. communicate more with us users about what’s pending/going on.

We will marvel at your insight, revel in your acumen, and hail your brilliance, while we change X loc in our (fully working and tested, doesn’t need to be broke again) code, to joyfully upgrade it to achieve new heights of performance and efficiency heretofore unimaginable.

May I live to experience the nirvana of Crystal 1.0! :blush:

2 Likes

@asterite WDYT about publishing your reply Crystal 0.31.1 has been released! as a blog post?

1 Like

[quote=“asterite, post:30, topic:1185”
We think of Crystal as a new language, now a language that you use by copying code from another language and expect it to work with minor changes.
[/quote]

I agree with your outlook after adjusting more to the type system and getting speed-drunk while running benchmarks for fun.

As a marketing message I think it’s less than fabulous. A lot of users are attracted to Crystal because they want a fast Ruby. Serdar’s excellent site CrystalForRubyists helps re-inforce the link - keep resources like that at the forefront of the message to the community and I think y’all will do just fine.

One useful tip that’s used in soulless consumer-facing dotcom corporate land from which I originate is that, when addressing the community, the message must leave the door open for education, not debate.

On a personal note I like the Time.UTC/LOCAL change. When the earth’s crust was still cooling I once worked as a DBA and getting programmers to persist timestamps in UTC was an endless battle. This change makes them think about it, waking up those rails devs that normally ignore it altogether and assume that ActiveRecord will do the right thing.

1 Like

Python is definitely way out in the lead for first-timer programming languages. This is the sole reason that it’s used for “data science” - a lot of the folks coming into those roles are from academic backgrounds, not computer or software engineering. Like Asterite pointed out they expect simple division to work like a calculator.

Maybe, though I don’t have much time right now. And people can read it here too, so…

On a separate note, we have 5.days.ago and 7.days.from_now. It’s not clear whether these reutrn a local or UTC time. What do we do?

Well, that one is obvious! :laughing:

5.local.days.ago
7.utc.days.from_now
1 Like

Using forum threads will not provide an effective means to inform people/users of important changes/issues/plans with Crystal because 1) you have to know about the thread in order to see it 2) discussions fall off the top of the page, which prevents 1), and 3) a user may have to read thru many posts, that make incremental points, and create no single comprehensive, and easily understood, synopsis/decision on what the result from the thread was about.

OTOH, a blog, by definition, is a place where specific things are posted that are implicitly deemed to be (to anyone/user) important, and are easy to find and kept a record of. Isn’t that why you already have a blog on the site?

This is why you have to TAKE THE TIME to do this on a regular basis.
You have to see this as important as writing code.

This is a ubiquitous problem that faces most tech projects/people.

I would urge you to have the dev team get into the habit of making at least one blog post a month. It doesn’t have to be the same person, split it up among you, it can be collaborative.

You will actually find the more you have to regularly explain stuff to others, the more you will be forced to understand whatever it is. And if it becomes too hard to explain in a plain and simple manner, maybe it’s too complex and/or the details need to be reconsidered.

1 Like