I love first impressions that include “where the hell are loops? Oh, 10.times do { |i| ... }, sure, why not.” Personally, I find it hilarious.
That said, there’s a second impression that I wish more people got, where Enumerable/Iterator clicks. Because “everything is an object” you can dot chain laziness into the middle of something like the above.
In Haskell you write [1..] instead of (1..Float::INFINITY).lazy or (1..10_000_000).each.
You write filter even instead of select(&.even?).
A short lambda expression like {|x| x * 3} in Crystal can be abbreviated as an operator section in Haskell: (* 3).
You write take 3 instead of `filter(3).
No need to write .to_a.
Unfortunately, the “pipe right” operator isn’t in the Haskell core, you need to load a package. More modern functional languages have it built in (F#, Ocaml, Elixir, etc). The usual ASCII encoding of “pipe right” is |> but I prefer ⊳.
So the nice functional way of writing that Crystal expression is
Give them time. (To see the wisdom in functional languages.)
That said, anything list-based, per your example, is where Haskell’s simplicity shines. Not so much necessarily outside that use-case. See: module management, records, error handling, foldl and unexpectedly explosive memory consumption, Data.Text.IO (and dealing with character encoding in general), the lack of thoughtful namespacing, the need to abstract everything into graduate level mathematics as much as possible regardless of necessity (functors, monads, monoids, applicatives, etc.), updating fields in immutable structs (compare Elixir’s %{person | age: 30} with using the lens library in Haskell, etc.)
I’ve been looking at roc-lang and idris lately, which are both inspired by haskell (roc more by way of Elm first), but (hopefully) without the warts.
Yes, let’s use proper typography to make code look better on the screen when viewing and editing it. Including proportional fonts. (Note, I prefer × to · for multiplication.)
[1‥] ⊳ filter even ⊳ map (× 3) ⊳ take 3
Unfortunately, there aren’t many FOSS code editors that support proportional fonts. Emacs has a steep learning curve and intimidating reputation, so I’m experimenting with Kate right now (it has a vi mode).
While I’d encountered virtual sequences in Factor (like ranges and <evens>), I hadn’t yet touched lazy lists (or lists at all, apparently). So I used this example to give it a try:
It splits apart new to allocate memory and initialize to initialize it like Objective C does, which is always nice to see. Usually unnecessary, but nice to see.
This is also done by ruby which is where I expect Crystal got it from; there is a common ancestor in Smalltalk for ruby and Objective-C which also does the allocate/initialize split, at least in some versions. It’s possible ruby lifted it from Objective-C, allocate is the name Obj-C uses.
Splat assignment probably evolved out of Python’s * function argument swizzling operators
Again probably by way of ruby not python, ruby has called * the splat operator for a long time (forever?).
Go has a more … monastic? … approach to system library dependencies, and for Linux targets it is trivially easy to build Go binaries without libc dependencies by setting CGO_ENABLED=0.
The advantage of the Go approach is that it makes cross-compilation work from anything to anything. I’ve only played with Crystal a little bit so this might be a problem more experienced users don’t hit, but if I try to cross-compile even a basic “Hello world” on my macOS laptop to a Linux target then the output is a huuuge error message:
% crystal build hello.cr --target x86_64-linux-musl
sh: pkg-config: command not found
ld: multiple errors: unknown file type in '/Users/john/.cache/crystal/Users-john-temp-crystal-hello-hello.cr/P-ointer40U-I-nt841.o0.o'; unknown
[...]
unknown file type in '/Users/john/.cache/crystal/Users-john-temp-crystal-hello-hello.cr/F-loat32.o0.o'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Error: execution of command failed with exit status 1: cc "${@}" -o /Users/john/temp/crystal-hello/hello -rdynamic -L/Users/john/.opt/crystal-1.13.3-1/embedded/lib -lpcre2-8 -lgc -levent
I love first impressions that include “where the hell are loops? Oh,
10.times do { |i| ... }
, sure, why not.” Personally, I find it hilarious.That said, there’s a second impression that I wish more people got, where
Enumerable
/Iterator
clicks. Because “everything is an object” you can dot chain laziness into the middle of something like the above.An example in Ruby would be
An example in Crystal would be
Kind of neat.
The author probably didn’t talk about this because we do this in Rust as well, so they’re already used to that being a thing.
This looks a noisy parody of Haskell.
[1..]
instead of(1..Float::INFINITY).lazy
or(1..10_000_000).each
.filter even
instead ofselect(&.even?)
.{|x| x * 3}
in Crystal can be abbreviated as an operator section in Haskell:(* 3)
.take 3
instead of `filter(3)..to_a
.Unfortunately, the “pipe right” operator isn’t in the Haskell core, you need to load a package. More modern functional languages have it built in (F#, Ocaml, Elixir, etc). The usual ASCII encoding of “pipe right” is
|>
but I prefer⊳
.So the nice functional way of writing that Crystal expression is
It’s in
Data.Function
as(&)
: https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-Function.html#v:-38-Cool, thanks!
Give them time. (To see the wisdom in functional languages.)
That said, anything list-based, per your example, is where Haskell’s simplicity shines. Not so much necessarily outside that use-case. See: module management, records, error handling,
foldl
and unexpectedly explosive memory consumption,Data.Text.IO
(and dealing with character encoding in general), the lack of thoughtful namespacing, the need to abstract everything into graduate level mathematics as much as possible regardless of necessity (functors, monads, monoids, applicatives, etc.), updating fields in immutable structs (compare Elixir’s%{person | age: 30}
with using thelens
library in Haskell, etc.)I’ve been looking at
roc-lang
andidris
lately, which are both inspired by haskell (roc more by way of Elm first), but (hopefully) without the warts.There are endless and beginnless ranges in Ruby and Crystal, i.e.
(..10)
and(1..)
works.In Ruby
#filter
is the same as#select
.The Crystal example could be written as
You mean just like everything else?
If you’re going Unicode, why not go Unicode all the way?
Yes, let’s use proper typography to make code look better on the screen when viewing and editing it. Including proportional fonts. (Note, I prefer × to · for multiplication.)
[1‥] ⊳ filter even ⊳ map (× 3) ⊳ take 3
Unfortunately, there aren’t many FOSS code editors that support proportional fonts. Emacs has a steep learning curve and intimidating reputation, so I’m experimenting with Kate right now (it has a vi mode).
Very nice!
While I’d encountered virtual sequences in Factor (like ranges and
<evens>
), I hadn’t yet touched lazy lists (or lists at all, apparently). So I used this example to give it a try:The lack of loops (well, ruby actually has loops but you will be cencured if you use them) solves so many problems and I love it so much
This is also done by ruby which is where I expect Crystal got it from; there is a common ancestor in Smalltalk for ruby and Objective-C which also does the allocate/initialize split, at least in some versions. It’s possible ruby lifted it from Objective-C, allocate is the name Obj-C uses.
Again probably by way of ruby not python, ruby has called
*
the splat operator for a long time (forever?).Oh I didn’t know Ruby and Smalltalk did it. Sounds like the lineage of it goes further back than I thought, thanks.
I like how this guy is happy about stdlib.
As for “No repl that I’ve found” there is
crystal i
nowadays.As a Rubyist, Crystal is very delightful. I wish it generated binaries like Go. Amazing project, nonetheless.
What are the differences between Crystal and Go’s binaries?
Crystal links with the system libraries, including libc, so building a self-contained static binary requires a specially-configured environment: https://crystal-lang.org/reference/1.13/guides/static_linking.html
Go has a more … monastic? … approach to system library dependencies, and for Linux targets it is trivially easy to build Go binaries without libc dependencies by setting
CGO_ENABLED=0
.The advantage of the Go approach is that it makes cross-compilation work from anything to anything. I’ve only played with Crystal a little bit so this might be a problem more experienced users don’t hit, but if I try to cross-compile even a basic “Hello world” on my macOS laptop to a Linux target then the output is a huuuge error message:
FWIW lots of ecosystems have been using
zig cc
to ease cross-compilation woes. I would assume it would work for crystal as well.