Programming Crystal Book Club

Thanks ODL - that’s quite a bit faster!

$ time shards build --release
Dependencies are satisfied
Building: crystal_fib
shards build --release  0.83s user 0.29s system 119% cpu 0.936 total

$ time ./bin/crystal_fib 
701408732
./bin/crystal_fib  2.62s user 0.01s system 92% cpu 2.840 total

Your code from here is actually a bit slower :confused:

$ time shards build --release
Dependencies are satisfied
Building: crystal_fib
shards build --release  9.01s user 0.39s system 102% cpu 9.199 total

$ time ./bin/crystal_fib     
701408732
./bin/crystal_fib  2.68s user 0.02s system 93% cpu 2.901 total

That makes no sense, it remove the exception check, so it’s less code, especially removing a branch, which is costly… o.O

Seems to imply to me something else is a bit off…

1 Like

Maybe we need to put up instructions on how to get as close to your set-up as possible?

I installed Crystal using ASDF then ran:

crystal init app crystal_fib
cd crystal_fib

Then put:

  def fib(n)
      return n if n <= 1
      fib(n - 1) + fib(n - 2)
  end

  sum = 0

  (1..42).each do |i|
      sum += fib(i)
  end

  puts sum

At the bottom of the src/crystal_fib.cr file, then ran:

time shards build --release
time ./bin/crystal_fib 

Then swapped that code for yours:

def fib(n)
    return n if n <= 1
    fib(n &- 1) &+ fib(n &- 2)
end

sum = 0

(1..42).each do |i|
    sum &+= fib(i)
end

puts sum

Then built and run again - does that seem right to you?


Shall we split these posts into another thread @mafinar? I’ll leave it up to you :smiley:

1 Like

Either’s fine. This thread has been more of a learning experience for me than I had anticipated <3

Regarding the code, how I did this was just copied the content into fib.cr and ran crystal --release fib.cr and then./fib.

For me compilation times for both were more or less similar.

2 Likes


If you are splitting it, do it from the point where @mafinar showed his first benchmark.

3 Likes

One side-effect from this book club was, lots of motivation to learn more on compilation performance and benchmark.

3 Likes

I got the DS&A book in my basket since a while, so you said it worth the time to read it ?
It’s very cool to see that Crystal get traction.

We’re planning to make a POC at work, replacing Go by Crystal, since main of our services are written in Ruby. I’m gonna follow this thread very closely. Thanks.

3 Likes

I would say so. I mean, it’s a PragProg style algorithm book that has more pragmatism and less theory (and doesn’t cost 500 dollars). I’ve only skimmed through the chapters on Trie and Graphs, and found them nicely written. I will probably skip the first 5 chapters (Big-Os and market pitches) because I’ve been through those numerous other times.

There’s this other book from Manning, Advanced Algorithms and Data Structure, that treats more advanced type of algorithms like bloom filters, treaps, some ML ones, if you’re looking for less common algorithms and data structures. But as I am learning Crystal right now and would like to implement some algorithms with it, the “A Common Sense Guide” would cost less time for me. If I like Crystal enough to stick to it for a few more months, I’ll start the other book or maybe one of Manning’s “Classic Computer Science Problems in X” where X is Python, Java, Swift.

It’ll be great to know of your experience. As a language I certainly enjoyed Crystal more than Go, it’s been only 5 days and still I’m sold as far as syntax is concerned. Go has more libraries available, it will be interesting to hear about your thoughts there. I had similar concerns four years ago when trying Elixir/Phoenix but that never bothered me (too much) in practice.

Thank YOU! I look forward to hear about your journey with Crystal.

3 Likes

Seems about what I did yep, that --release on the shards build call is the thing that makes an optimized build, ditto with cargo build --release. :slight_smile:

Convenient! So the crystal compiler binary also takes --release as a shortcut, very nice!

Every book club I’ve been in gets very ‘offtopic’ in that in moves from topic to topic as things are learned and you can build on that, lol.

That’s cool, what I’ve learned is try to use type annotations in your code to help it compile faster. ^.^

It would be really cool to post a blog (or a post here or whatever) of how the conversion results go and all. :slight_smile:

2 Likes

Thanks for the advice! So the compilateur will avoid to make type inference based on the annotations I guess.

Sure, I’ll try to keep notes from the transition and if the POC was conclusive or not.

2 Likes

Love reading about both successes and failures, and the ‘why’ for each. :slight_smile:

3 Likes

Chapter 3 - Typing Variables and Controlling the Flow

Hello again! Now that I am off the post vaccine unpleasantries, I will put in my thoughts on Chapter 3, I skimmed it once, will get down with the code after brunch. It mostly deals with types, I got to meet a new type of method name, to_i*, to_f, to_a etc. The way to encourage types on number is similar, 25_i8, also, we have ? suffix to nullity management.

In the middle there’s a discussion on exception handling. So two things I usually check for when talking about exceptions: 1. How do you do an “else if” and 2. What are the exception handling syntax. So for 1. It’s elsif (Not elif or else if) and for 2. It’s begin..rescue...else...ensure and raise to throw.

An interesting discovery:

x = 10

ten_1 = if x == 10 "It's TEN" else false end # Works fine => Bool | String
ten_2 = if x == 10 1 else false end # Works fine => Bool | Int32
ten_3 = if x == 10 true else false end # ERROR: Unexpected token: `true`
ten_ternary = x == 10 ? true : false # I know it's redundant, but the syntax works as expected

Maybe I am missing something here. Another thing, I really really miss a REPL. Maybe it’s just me.

Another thing, if x == 10 10 else false end works, putting the condition and expression in the same line, but in case of case, case x when 10 10 else false does not work, it should be case x ; when 10 ; 10 ; false end. I just wish ; being relevant for if too could have been more consistent?

Also, the book was written in a time when there was case...when in Crystal. We now have case..in that is exhaustive. Also, the case does some than the C/Java ones do, for instance, you can destruct tuples, and call bool methods (i.e. when { .odd?, .even? } => ) and does type check. Not sure I mentioned this in an earlier post because I recall the book introducing case at an earlier section too.

Here’s a nice example from the book:

(1..100).each do |i|
  case {i % 3, i % 5}
  when {0, 0}
    puts "FizzBuzz"
  when {0, _}
    puts "Fizz"
  when {_, 0}
    puts "Buzz"
  else
    puts i
  end
end

One minor note, I found the name responds_to? method really cute, quite in line with how the original OOP used to be about messages and passing them around.

Middle of the chapter contained an example that glued all the knowledge together, and after that there was an introduction to composite data types. There’s Hash key => value and named tuple name: value, that was nice. There’s a type for Set, unlike Go.

So once you know the control flow, assignments and typings of a language, your knowledge of that language is somewhat Turing complete, you can do anything with it (though it’d not be pleasant). This chapter gets you there about Crystal, preparing you for the next step: organization. This really expands upon the first 50% of the previous chapter.

As usual, there was a Company’s story through an interview at the end. This time it is Dev Demand which mostly praised the performance and intuitiveness. There was tad comparison with PHP, Python and Go. To be honest, I think Crystal is intuitive for Ruby devs, just as Go is intuitive for people only habituated with C-Family members and Elixir for anyone who got habituated with patterns. For me, Crystal isn’t intuitive, but I’m sure Ruby will become once I learn it AFTER (if ever) Crystal

1 Like

Here’s an interesting snippet, something I found when solving Advent of Code year 2016, Day 1 problem. I created a dumb case to explain here.

def maybe_a_tuple(val : Int)
  if val.odd?
    {0, 0}
  else
    nil
  end
end

tuple = maybe_a_tuple(11)

# This throws an error, sure, maybe_a_tuple CAN return a Nil, so it's okay
# pp tuple[0], tuple[1]

# This works, because `if` ensures it ain't `nil`
#if tuple
#  pp tuple[0], tuple[1]
# end

case tuple
when {0, 0}
	pp tuple[0], tuple[1] # Compile Time Error
        # However, this is the clause that wins though.
when Nil
	pp "NIL"
else
	pp "ELSE"
end

So, if works to cast the Tuple | Nil into Tuple, but case didn’t, despite the clause {0, 0} winning which guarantees, there’d be a [] method somewhere. This may make sense once I dig in, but it certainly defied Principle of Least Surprise to me.

2 Likes

Woot!

I’m curious why they went with a _ separator, normally that’s a digit separater in programming languages and the language usually just does something like 25i8 or so by default.

Oh the horror, lol.

Very interesting how it doesn’t have something to separate the conditional to the ‘then’ expression, I wonder how much lookahead its parser performs and how much it affects parsing performance…

It has no repl? That’s pretty common now even for hard compiled languages (like rust has excvr if I’m typing that right, lol), because the jupyter ecosystem uses them heavily, checking… found:

It doesn’t seem to work standalone but it does seem to work in jupyter itself (jupyter interface rather than CLI), so perhaps that?

Ah, yeah that is a weird inconsistency…

That is also exceptionally odd! o.O

3 Likes

I am going to do a double chapter review tomorrow- chapters 4 and 5 aka methods and classes. It was nice read. I got to learn about yield and block aka that_thing_you_told_me_to_do_i_am_doing_with_these_parameters_or_not syntax. Loved it.

More in depth opinions on this coming up tomorrow!

2 Likes

Lol!

Looking forward!

2 Likes

Seems like this is a bug. None should compile and should have semicolon. There is an example of this in the book (I think Page 64), so I’d say not to follow this example. I had posted a question on Crystal forum last night on this.

2 Likes

Chapter 4 and 5

I did not just think that method and proc should be reviewed without reviewing classes with it. So I bundled the chapters for today’s discussion.

Chapter 4 - Methods and Procs

So this was a nice little chapter that discussed the various things surrounding defining actions (methods, procs, blocks) of Crystal. Most of them were not new to me because other languages, save procs and blocks (the way they handle syntax).

  • Method definitions are pretty much like Python’s. You can = default args, * and ** for splats.
  • : to show return types, not -> or not <space>, and types come late. (This thing will join the list where elsif and exception handling belongs, the list where languages differ to make a polyglots life interesting).
  • This matches more with Dart. When using named labels during call, we use : instead of = like Python. So, Point.set(x: 10, y: 20) it is.
  • Here’s another thing, you can “label” a parameter, i.e. the caller uses one thing as a label, but the internal name is another. I think I saw something similar when looking at ReasonML, ~ or something, I could be wrong, I just looked into it once after seeing a presentation by @yawaramin and discussion afterwards. Maybe I should again!
  • There’s a * i.e. its_only_named_parameters_after_this mode.
  • You can overload functions, sort of like singledispatch module of Python, but cases can differ based on all parameter types. That’s nice. I always liked overloading (and this experience gets better if you have a well behaving language server behind the scene, which Crystal does not have in my opinion)

That’s pretty much it about everything except blocks. Which is new to me, at least in this way. I had always seen Ruby programmers do a do and do all sorts of things inside that probably injected itself into points within the body and yet looked fluent. So, it’s really some “points” inside the function body that “wants” a “callable”, and whatever you pass it, it calls that callable by passing those values in it. Function really, but the way the callsite syntax is designed is what makes the outcome beautiful. So. if I am a Python programmer and map(lambda x: x + 1, lst) happens, it is really lst.map do |x| x + 1and when defining themap, I just put yield` in place of where function should be.

I am actually making thins as a “Note to Self” kind of a way so thinking out loud and hence the cryptic paragraph above.

So, after reading this chapter, the reader would be equipped to create and use things that “does something” (i.e. functions, closures, procs etc). And the chapter is very well written, with a mini project and exercises. This was certainly the fastest and also the most educational chapter for me. This and the next one…

Chapter 5 - Classes and Struct

This is about classes and struct, the first one is call by reference and the second one is call by value, and the syntax differs by the first token (i.e. class Point vs struct Point).

It starts with having you define a Ruby class and slowly transforming it into Crystal one, describing the rationale behind “diff”-s. I bet any Ruby programmer will immediately grasp the concept. For me, I freed two birds with opening one door. Also, this chapter seems to have an “excerpt” for free, so anyone can go and read parts of it.

Here are some interesting tidbits from this chapter:

  • You can create and assign instance variables in constructor like in Dart def Point(@x : Int32, @y : Int32); end.
  • There’s Enum!
  • There’s abstract classes.
  • Inheritance is single (phew)
  • crystal tool hierarchy is a cool tool.
  • You can alias types.

If you know how to make Ruby classes, then 75% of this chapter should be a breeze.

This chapter ended with a success story, this time from LI-COR Biosciences. Being able to compile to binary and less greed for system resources dominated this discussion.

I missed the story of Chapter 4 when I was discussing this and since I hate scrolling up, I will do so here. Chapter 4 conversation was Crystal story of Duo Design. A CMS!!! There was brief discussion on Node vs Crystal (for their use case) here which I thoroughly enjoyed.

That’s all for today. Will come back with Chapter 6 tomorrow. And a small one on Chapter 7 too (Psst, I read them both actually).

2 Likes

You don’t like diamonds? :wink:

Lol.

2 Likes

I guess I’d prefer Ruby :wink: … also til there’s a programming language called Sapphire and another called Emerald … none of them seems to like Diamonds either.

2 Likes