Crystal 1.0 – What to expect
crystal-lang.org
crystal-lang.org
- Elegance of Ruby
- Statically type checked + global type inference
- No Nulls
- Go-like concurrency
- Easy C ffi
- High performance
I really hope the Crystal succeeds and the language goes mainstream - this release is a huge step forward towards that. Congrats to the Crystal team for reaching the 1.0 milestone!
I just don't understand why other people seem to think Ruby isn't enough.
Does it not? It has 'nil', just like Ruby. Basically the same thing, the docs literally say it's 'similar to null in other languages'.
https://crystal-lang.org/reference/syntax_and_semantics/lite...
So a function might return an Int or a Nil, but if you then try to, say, invoke a function expecting an Int on a Nil, it predictably blows up. An Int cannot be Nil itself.
It's not entirely unlike how algebraic data types with a "None" case work in the ML family of languages.
In most languages without ML-like type systems, null is treated as a "bottom type", as if it were a subclass of all classes. This violates the Liskov substitution principle and results in an unsound type system.
When I was working on Google's indexing system, I was very excited to hear rumors that Ken Thompson and Rob Pike were working on a new language. When it was unveiled, I was pretty disappointed to learn that it repeated Tony Hoare's billion dollar mistake.
[0] https://crystal-lang.org/reference/syntax_and_semantics/type...
I don't have any use for it, however as language geek it is quite nice to see ecosystems blossom.
Also, Crystal was first released in 2014, when Kotlin was like 3 years old, and 100% JVM-bound.
This was not the case when Crystal development started.
Also there is no ABI so you cannot use anything like a native DLL.
Native compilation while possible is clearly second class on the JVM platform. For Crystal it is first class.
Pretty sure this isn't the case with Kotlin Native[1].
[1] https://kotlinlang.org/docs/native-dynamic-libraries.html
Take that with a grain of salt, though, since I have a deep-seated hatred of Java stemming from experiences in the early 00s.
The simplicity and predictability are key with Crystal; everything is an object, and the control structures are limited in number.
Crystal code is very concise and legible. You might say it's crystal clear.
def foo_then_bar(a, b)
a.foo
b.bar
end
and it will just work for any two types that implement `foo` and `bar`, respectively. This isn't something I do often - I like the clarity that type-annotated parameters give - but it's a really nice feature to have available in the 2% of cases where I want it.Then there's all the ecosystem stuff - Kotlin makes me do extra work to target native, and has little bits of weirdness inherited from Java all around the edges. None of it's deal-breaking, but it is friction.
Note that Kotlin has union types through this compiler plugin: https://github.com/arrow-kt/arrow-meta/issues/570 However it is experimental and shouldn't yet be used in production. Btw sealed classes take care of many uses of union types.
What kind of extra work is needed? I'd say that graal native is transparant for any library that doesn't do weird use of reflection but it's true that many libraries do abuse of reflection.
I did try Kotlin/native hoping it would make it easier to remain in blissful ignorance of the JVM ecosystem, but as of a year or two ago it had significant holes in the standard library (most notably no file abstractions other than thin bindings to libc) and pretty atrocious compile times (something like 10s for hello world, and 30+ for a couple thousand lines of code).
Try creating a small command line tool for a JVM language. You need to distribute a virtual machine with it. You got the overhead of firing up a VM or JIT just for a short lived session.
Also JVM gobbles memory. You would not want a system made up of lots of tiny command line tools all running on a JVM. That would be a lot of overhead.
swift support for windows is non-existent
I was prepared to suggest WSL, but apparently Swift support for Windows is existent[1].
See Babashka[0] for an example scripting toolkit written in Clojure.
That many didn't want to buy those compilers and settled with the free beer that could get hold of is another matter.
Sadly, people often confuse "prevailing mindset of the noisiest authorities of some programming language" with "the programming language itself" { not that this is the only confusion or maybe even the worst... :-) }
In the words of David Heinemeier Hansson:
> In fact, Ruby, to me, so much of the enjoyment in Ruby is these incredible subtleties, of how many different ways you can structure a conditional. Like, Ruby has, I don't know even the count, there's gotta be 60 different ways you can say `if something`, right? And it is in those 60 different ways that I find half the enjoyment of writing Ruby. Like, it was one of those things where I knew, very early on, that Python was not a language for me because it said, right in the manifesto, there should be preferably one and only one way to do things. Ruby has the exact opposite approach, there should be preferably ten thousand subtle different ways of doing things, that will allow you to write that particular conditional, with just the right emphasis, do you write it in the front, do you put it at the back, is it multi-line, is it single line? Like, there's so much variety and it's in that variety that I find poetry. And it is the poetry of writing Ruby code, of making those subtle distinctions where, at the end, you can like, "Ehh, should we move it around" like, where I just go like, giggles, right? Like, this where like we talked about that big smile, right? So much of that big smile comes from, not just like solving the problem, but solving it in a poetic way.
This doesn’t sound elegant (meaning “pleasingly ingenious and simple”) at all.
It also only seems readable in the most shallow sense - certainly not understandable.
I’d really love to see OCaml get true parallelism, green threads, and a decent HTTP server story. Then, I don’t think I’d look anywhere else.
And macros.
EDIT: also forgot to ask if Crystal has a "killer app" yet? Ruby had Rails, Python had scipy/pandas(several others), Rust had servo etc.
Everyone seems so preoccupied with compile speed for Crystal.
Also, you are comparing compiled languages to interpreted ones, but the GP talked about differences between compilation time (i.e. compiled languages). And others are also comparing it to e.g. go (compiled but quick to compile).
I don't know how opensolaris kernel builds are, but they're not nearly as simple to wrap your head around as the linux kernel. As a result the level of participation is quite low.
Of course this is more a build issue than it is a language issue in this case.
But yes as you said the compile speed on the linux kernel is partly because it's C and not C++
Energize C++ and VA C++ v4.0 showed the way of a Smalltalk like experience for C++, the tools just need to catch up with the past.
VC++ and C++ Builder are on the good path for it.
$thing might be `bash ./script.sh` (because my text editor's atomic rename doesn't understand execute bits >.>), `php script.php` or `gcc -O0 script.c && ./script`. (Also, as an aside I used to use `-e close_write $file` until I realized watching even giant directories is equivalently efficient to watching a file.)
Shell scripts (the small kind that run few subprocesses) are typically fast. Likewise, small C programs of <1000-2000 lines compile just about instantly on modern hardware; and where modern hardware isn't available and what I'm trying to do doesn't leverage too many libraries or whatnot, tcc has been able to swing the balance firmly in my favor in the past, which has been great.
But for better or worse, PHP is currently the language I use the most. Because it's faster than Python and Ruby.
A while back I wanted to do a bit of analysis on a dataset of information that was only published as a set of PDF documents... yayyy. But after timidly gunzipping the stream blocks and googling random bits of PDF's command language ("wat even is this"), I discovered to my complete surprise that it was trivial to interpret the text coordinate system and my first "haha let's see how bad this is" actually produced readable text on pretty much the first go. (To be pedantic, step #-1 was "draw little boxes where the text should be", step #0 was "how to x,y correctly" (with a side serving of "...those boxes look quite reasonably positioned..."), and step #1 was "replace boxes with texWHAT it worked?!")
With rendering basically... viable (in IIRC 300-500 LOC O.o), the next step was the boring stir-the-soup-for-144-hours bespoke state machine that cross-correlated text coordinates with field meanings ("okay, that's a heading, and the next text instruction draws the field value underneath. OK, assert that the heading is bold, the value is not, and they're both exactly the same (floating-point) Y position").
While that part took a while, it was mostly extremely easy, because I was pretty much linearly writing the script "from start to finish", ie just chipping away at the rock face of the task at hand until I processed an entire document, then the next document ("oh no"), then the next one ("ugh") and so forth ("wait, the edge cases are... decreasing? :D"). My workflow was pretty much founded entirely on the above-noted method where I would re-exec the script from scratch upon save.
Loading/gunzipping a given PDF and getting to the point where the little pipeline would crash would typically complete well before I had a chance to release the CTRL key after hitting CTRL+S. So while the process was objectively quite like stirring soup, it did not feel like that at all and I was able to kind of float a bit as my brain cohesively integrated the mental model of the architecture I was building without any distractions, pauses or forced context switches getting jammed in the mental encoding process like so many wrenches.
Soon 15 documents were handled correctly, then 20, then 30, then 100 ("oooh, if all the items on the page add up exactly right it pushes line 2 of the summary heading down to the second page! Hmmm... how on earth to special-case that without refactoring to look at more than 1 page at a time..."), and then I hit some sort of threshold and it suddenly just started ticking through PDFs like crazy without asserting. Which was both awesome and a Problem™: the thing ran at something like ~60 PDFs/sec, and while jumping to just after the last successfully-processed PDF on restart worked great when the code crashed constantly, now I was sitting spinning for tens of seconds, getting distracted as I anticipated the next crash. ADHD(R)(TM).
I wasn't surprised to learn from htop that the script was disk-bound; for some reason my ZFS mirror setup will happily read sequentially at 200MB/s, but thousands-of-tiny-files situations are... suffice to say apt unconditionally takes 60 seconds to install the smallest thing, unless the entire package db is in the FS cache. I'm not sure why. The PDFs were sharded sanely, but they were still in separate files. So I decided to pack them all into a giant blob, and since there weren't too many PDFs and they were numbered sequentially I used a simple offset-based index at the front of the blob where `fseek(data_start + (<PDF ID> * 4)); $o = fread(4); fseek($o);` would give me random seeking when I needed it, or I could just fseek() to data_start and start reading directly.
Reading the blob instead promptly pegged a single CPU core (yay!), and gave me IIRC ~150-200+ PDFs/sec. This was awesome. But I was still just a tiny bit curious, so after googling around for a profiler and having a small jawdrop moment about SPX (https://github.com/NoiseByNorthwest/php-spx), I had a tentative look at what was actually using the most CPU (via `SPX_ENABLED=1 php ./script.php`, which will automatically print a one-page profile trace to stdout at graceful exit or ^C).
Oh. The PDF stack machine interpreter is what's taking all the CPU time. That tiny 100 line function was the smallest in the whole script. lol
So, I moved that function to the preprocessor/packer, then (after some headscratching) serialized the array of tokenized commands/strings into the blob by prefixing commands with \xFF and strings with \xFF\xFE\xFF so I could explode() on \xFF and tell commands from strings by checking if the previous entry was \xFE (and just skip entries of '\xFE' when I found them) :D. Then I reran the preprocessor to regenerate the pack file.
$ php convert_dlcache.php
Scanning...
24/66060
67,927 entries [4.27 sec]
Sorting... 10013-343271 [1.61 sec]
data_start=1373094
* 67,920/67,927 99.99% 83/s 00:00 13:58 343259
Thankfully I've only needed to run the preprocessor rarely, like for example I only needed to run it twice today because it promptly truncated its pack file when I accidentally reran it after the first run (yay). Yes, it really does take ~14 minutes, and yes, there are just under 70,000 PDFs.Then I reran the pipeline script.
$ php conv2.php
67,927/67,927 890/s 01:16 00:00 343271
Complete
Oh. 900 PDFs/sec.Uh, what happens if I... set up a simple pcntl_fork() + stream_socket_pair() multi-process worker system...?
$ php conv2.php
Reading... done
4 workers, (16,982 x 3) + (16,981 x 1)
62,162/67,927 1,800/s (469/s 166319, 444/s 237743, 442/s 302338, 444/s 340327) 00:34 00:03
^C
"Oh. Okay."":D"
(The workers disappear from the output as they complete, so I ^C'd it just before they all exited, at 3 seconds left.)
So. 68,000 PDFs/sec on a not particularly amazing 3.3Ghz i3-3220 with 1600MHz RAM.
PHP 8's JIT is aweso--wait, is the JIT actually on?
Oh. It's off by default.
$ php -dopcache.enable_cli=1 -dopcache.jit_buffer_size=32M conv2.php
Reading... done
4 workers, (16,982 x 3) + (16,981 x 1)
58,206/67,927 2,537/s (669/s 163803, 623/s 230254, 619/s 300420, 627/s 338470) 00:22 00:03
*Blinks*(In small voice) "I am processing/unit-testing 68k documents in 22 seconds. At over two and a half thousand PDFs a second."
I also noticed that
PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
31511 i336 20 0 276M 19864 5736 R 100. 0.2 0:18.25 php conv2.php
31513 i336 20 0 276M 19856 5732 R 100. 0.2 0:18.35 php conv2.php
31512 i336 20 0 276M 19728 5600 R 100. 0.2 0:18.32 php conv2.php
31510 i336 20 0 276M 19764 5648 R 97.7 0.2 0:18.03 php conv2.php
31509 i336 20 0 275M 34072 20036 S 0.7 0.4 0:00.19 php conv2.php
the workers are only using 20MB RAM each. This is with object deferencing going on, after I kinda started going crosseyed and properly moved my JBOGF (Just a Bunch of Globals and Functions) into a proper class (albeit a static one, since I'm still learning, and construction/deconstruction would probably slow things down too). I also note that with the JIT off, VIRT is 115M, RES is 16,312K and SHR is 2,412K for all workers, with the master taking 29M RES and 15M SHR.--
Why'd I write all this?!
To make the point that a) live reexec on save is an awesome programming model when it can be applied, and b) PHP is my incredibly awkward gold standard reference, lol.
(In rerunning everything for this post I discovered the JIT and learned my script could go even faster!)
I've been keeping vague tabs on recent language developments from a bit of a distance for a while now, and am very interested to dig into Rust and Zig's incremental compilation capabilities at some point.
Based on what I'm hearing I don't know if I should just go dive in yet though - to be entirely honest, there are multiple realms of applications I simply cannot write in PHP - including things as simple as console CLIs, because the neccessary TTY I/O control (eg, turning TTY echo off and reading 1 char at a time) remain unavailable (see also: multiple years-old long-forgotten bugs/feature requests).
I don't really want to discover what's possible only to find myself between a rock and a hard place as my programs start to grow and I hit invisible quadratic brick walls that persist after I enable every "fastest possible" setting the language offers. That happened a few years ago when I tried to play around with FLTK in C++; once build times were around the 3-4 second mark (*with* -O0 and precompiled headers), regardless of the fact that I'd been working on the project for enough time I was invested in it, I just gave up.
Much more recently I installed .NET Core the other day to run and dissect a small F# program. Running `dotnet fsi` cold took 5 seconds to reach a REPL prompt (ouch), and while warmed-up reruns would only take 1 second, I found actually loading a ~300-line program would take a non-reducible ~4-5 seconds.
Everyone's different, and sometimes things people say can look crazy, or stupid, or "...how even...", but they can still be true for that person. For me, waiting 4 seconds for an interpreter/runtime/etc to reach a useful milestone such as "I can actually interact at all with it" is untenable. Yup. I go straight to "I've waited for this thing to do the thing 200 times today <cartoon punching fight cloud>" after the 3rd pause. I am utterly incompatible with the Old Guard "code's compiling!" way of doing things. It puts me straight into defensive not-in-my-comfort-zone mode.
Completely open-minded about Crystal, which I've heard repeatedly good things about. Yes, of course, I haven't tested it (yet), again because I don't want to go "wow this is awesome... except I don't have the patience for it :(".
The status quo noted above remains an actively unsolved problem; trying to figure out how many interesting things I can wedge into the "focusable space" defined by "how big I can make my program before the programming languge slows down" is getting boring.
Perhaps I'm just stuck here because I'm using older(ish) hardware, and once I finally figure out that problem I'll suddenly realize all of this deep analysis was unnecessary. heh
By the way, if you're looking for instant introspection, changes and carrying on, you should have a look at what you get with Common Lisp. You're able to redefine code on the fly, and continue from where things stopped. This blog post series goes into debugging, specifically with SBCL (one implementation), Emacs and an add-on called Slime.
Thank you
Once replaced a python script while it was running. Expected time, python: 12 hours. Time to get it running in Crystal: 20 min. Time to finish in Crystal: 12 minutes.
Can you please elaborate on this?
Agreed.
I don’t do enough python to judge them. But, then again, I had even less experience in Crystal back when I worked on this.
I think Elixir is one of crystal's top competitors, but only for servers. Probably you'd never leave Elixir to write a webserver in Crystal. That's just Erlang's specialty. You would leave golang, Ruby, python, PHP, etc. for crystal though, because of the type system, performance, threads, binary, etc..
I've actually started using crystal to write the kinds of scripts people would normally use python for, despite it not being a scripting language.
The biggest factor drawing me to experiment a little with Crystal is that it is one of very few languages providing what otherwise has been pretty unique to Go: Lightweight threads+Channels+M:N concurrency (automatically multiplexing the lightweight threads onto a smaller number of OS threads).
Also it does it with a very readable and clean syntax.
Wrote a little about it, with code comparisons, before:
https://rillabs.com/posts/crystal-concurrency-easier-syntax-...
Do you realize the human resources cost that a new language imply? A new programming language (which is not polyglot aka based on graalvm) imply an almost complete duplication of work for creating their library ecosystem instead of reusing the existings. We talk about so many human hours wasted that could have instead be used for making the world a better place. Justifying such a cost is already almost impossible with a language that has a clear value proposition but to not realize the absurdness of this cost for a language that has no clear value proposition really is something intellectually...
Take a look at yourself and reflect. If there's dozens or hundreds of interactions that go poorly, notice that YOU are the common denominator.
What you've written here is clearly delusional and makes me worry for your health.
Nobody is attacking you, I'm expressing concern for you. Want to talk privately?
Email in my bio.
For Crystal 1.0, the number of OS threads is always 1, isn’t it? So although this provides concurrency, it’s less powerful than Go where the same mechanism also provides parallelism.
(page1, page2) <- concurrently (getURL url1) (getURL url2)
https://hackage.haskell.org/package/async-2.2.3/docs/Control...edit: there seem to be some clues about FFI effects in this case of the PG connection library: https://forum.crystal-lang.org/t/issue-with-crashes-hangs-wi...
It's fantastic. I can think in it. Thank you to all the devs behind this wonderful language!
Migration seems like it might be tough. The ORM (Avram) is missing some things still. (Like I think - tables without primary keys, seems like 'has many through' needs some work, stuff like that.) My API is quite simple.
So a project that compiles to native code but uses Ruby-like syntax is very much "what I would build if I committed to building a language". But I've kept pushing off learning it on the notion that I could always justify learning other things that were "version 1" and "production ready (whatever that means)".
I'm thinking now is the time to jump in and try this out finally.
Any real world users care to explain some domains that either are or are not well suited for this at the moment? Any "gotchas" with writing C-bindings, should I need to? Or is the library ecosystem fairly strong?
Hopefully 1.0 would allow more companies to use it. I've been following the evolution of Crystal for more that 3 years now.
I came from Ruby. Got a JSON/nested structures first program in Crystal working without even reading the docs. Was impressed by Crystal being "a cleaner Ruby" (some of the Ruby quirks removed, some nice things added), compile time NULL checks, type inference and of course it's runtime speed.
Next I've rewritten a small web service from Ruby to Crystal and seen a huge speedup with API requests serving in microseconds. The pre-import CSV data process (took ~ 40s in Ruby) was replaced by just reading CSV on the fly during server boot (~ 2s). Not even tried to optimize anything, just straightforward "make it work".
I would encourage anyone who loves Rube to check Crystal.
I would also suggest not to look at Crystal as just faster (fancier) Ruby. Although on the surface it looks very similar it actually quite different.
I figured Crystal was for Ruby developers who want a good static type system.
The only exception to this is untrusted input, but for that an string is usually always fine.
Never written generic/template code? Making that much easier is one of the main benefits of dynamic typing.
I have no problem when a language allows the programmer to explicitly opt for a variable to be an undefined/dynamic type, whether as part of generics or as part of a defensive ingest of untrusted data. I admit this is previously unspecified nuance but in my mind this doesn't break my rule: "this variable is always going to be a dynamic type, never not a dynamic type." This means you know when to interact with it defensively.
The thing I'd like in a hybrid static-dynamic language is type assertion blocks which restore some benefits of static typing while dealing with dynamic typed variables. E.g.:
declare x as dynamic;
x = untrusted_source();
if (x IS_A string) {
// Inside here, x behaves as a statically typed string.
// The compiler knows it and checks types appropriately.
// Passing x elsewhere will send a static typed string.
function_that_demands_a_string(x);
}
else if (x CAN_SAFELY_BECOME_A uint) {
// True for any non-negative integer regardless of data type.
// Inside here, x is always a statically typed uint.
// If it was some other type, it was converted for me.
}
(This probably already exists in some language somewhere but my familiarity with languages is narrow.)https://tio.run/##ZVDBbsIwDL33KywmrRcaUXbbhHbeaYdpJ4RQaA1kCk...
Note how the language is pretty smart about flow-typing variables as you narrow down their types. `x` here is typed as a tagged union (String | Int32 | Array(Char) | Float64 | UInt32), but by the time the program gets to the final `else` the compiler has figured out that the String and Array cases have already been dealt with (even though we never mentioned Array by name, just asked if the object implements a method that only Array implements), so it lets us call `x.abs`, which only works for numeric types. Of course, `abs` has different implementations for Int32 and Float64, but the signatures are compatible so the compiler lets it fly, resorting to dynamic dispatch at runtime.
The missing part is that there's nothing like a CAN_SAFELY_BECOME_A operator so I had to implement that test myself (as `try_to_u32`) and call it early to get the flow typing to work.
The Wikipedia article for the concept is https://en.wikipedia.org/wiki/Flow-sensitive_typing
With a
<T>createMapWithStringKey(T arg) -> Map<string, T>
You can keep strong type info with AType a
var b = createMapWithStringKey<AType>(a)
// b is for certain a Map<string, AType>In any serious statically-typed language, the type system does real heavy lifting. In practice that means that libraries execute code at compile time that, in effect, generate the code you would otherwise need to write (again) yourself.
C, Pascal, and Go are examples of statically typed languages whose type system does only trivial work.
The fastest JavaScript engines and LuaJIT are closer in speed to Crystal than Crystal is to C.
Granted - all these languages are really fast - and unless you're doing something INSANELY performance dependent and you REALLY know what you're doing - I think familiarity trumps all. You're almost certainly going to write faster Crystal code if you're a Crystal expert than you would write C, C++, or Rust.
There's a crazy amount of engineering effort that has to counteract a missing "this has one parameter and it's either a string or a float" annotation.
In practice if you don't have a world class well funded team - yes, static typing is necessary for performance.
It uses a method JIT so it is deterministic and easy to analyze unlike JavaScript.
You can lookup ahead of time how each function will get compiled with given input types.
You can't analyse functions like this ahead of time:
function foo(x)
x + y
end
You've got an Any type interacting with an Any global. There's no better answer than you'd get from JS analysis.It's very explicit in almost anything the language designers wrote from the beginning. Julia has always had it's language semantics designed around a JIT. Keep in mind, julia's JIT is not at all like JS JITs, julia's style is often called 'just ahead of time'.
For the you quote, the only thing causing a problem is the global y. Using global variables is very frowned upon in julia for this reason. However, if you write
function g(y)
function f(x)
x + y
end
end
or something, then there is no `Any` involved at runtime. What will happen is that say you call g(1.0)(2), as soon as julia sees this, it will do static type inference on the function bodies, knowing that y :: Float64 and x :: Int, all the code can be inferred and compiled down. Julia specializes on all arguments of functions, whether or not you put a type assertion on them.If there's a point in the program where type inference fails to produce concrete types, then the compiler just generates code up to that point and then waits until the types are resolved at runtime, and then using the new runtime type information does static analysis going forward from that point until it either encounters another type instability, or the program is finished.
This is why writing code that isn't concretely inferrable can carry a heavy performance penalty in julia, but it's also why inferrable code can be so fast. The trick is just to keep type dynamism away from performance bottlenecks and you'll be fine.
Sometimes discipline must be enforced on the programmer.
Next generation climate models are built with Julia. You would not pick a slow language for such a high performance dependent task.
You're confusing dynamic typing and type inference.
> Julia is dynamically typed, feels like a scripting language, and has good support for interactive use.
Literally on the front page.
As an implementation detail, it has a statically typed intermediate representation that's used for very aggressive and impressive optimization.
But that's an implementation detail, and is not what most people mean when they talk about a language being dynamically or statically typed.
Absolutely yes. If you don't know the shape of your data at compile time then you're going to destroy your performance discovering and validating it at runtime.
That said, my current dayjob is around a Rails codebase, so I might not be representative of the normal systems programming crowd.
Crystal is interesting to me because its a systems language, using Ruby syntax and idioms.
I'm a Rails developer by trade but I've been pining to go back into desktop GUI development. Right now the only viable cross-platform options seem to be Swing and Electron. I'm not sure what the desktop story is with Crystal but if it there is a workable solution based on GTK or something similar I'd be very happy!
this issue tracks Windows progress, it hasn't been really updated in a while. I've been following Crystal Windows port for over a year, nothing changed yet.
Categorically, what I've seen of this falls closer to "lintable comments" than it does "actual static types". Static types are a foundational aspect of crystal, it's not like a sticker slapped on as an after thought.
- Ruby itself
- Crystal
- Elixir
I'm massively keen on Zig, too, but it still feels like early days for the language itself. The C-interop is utterly astounding but I don't want to write Zig as syntax-sugar around C libraries.
However, as far as I understand it there is also an argument to be made that multiple dispatch is at least as close to Alan Kay’s original intent for OO (e.g. [1]) than is the currently-ubiquitous class-based version of OO that leads to things like `RequestProcessorFactoryFactory` s with `RequestProcessorFactoryFactory.getRequestProcessorFactory(Class)` methods.
[1] https://medium.com/javascript-scene/the-forgotten-history-of...
It's a great language regardless.
Swift is the only one on that list that really feels tied to any particular platform.
C# has been extremely portable for years now thanks to .NET Core. Even SQL Server runs on Linux these days, if you really just enjoy spending money.
Java has always had a strong focus on portability, of course.
Nim... I don't know much about. Doesn't it compile to C? I don't think it intentionally has any particular platform affinity, although I wouldn't be surprised if it worked best on Linux.
Java is tied to JVM
Swift is tied to MacOS.
Of course, with the exception of JVM, they are all nominally "cross-platform" in the OS sense of the word. But I haven't seen any substantial Linux/Mac .Net codebases yet, same with Swift on Windows
Edit: I just saw the person that you replied to said the same thing, so I'm not sure why you're pushing that it has a complicated cross-platform story.
To be fair I haven't written .net code in like 10 years, and the docs explaining how to navigate the transition from framework to core were extremely confusing. Has that soup of different technologies solidified any?
TL;DR rewrite it - there is no migration path!
No, which doesn't really matter since we're talking about .NET, not .NET Framework. Poor naming choice by Microsoft I guess.
It left a bad taste, but maybe that was because I wasn't using a distro like Ubuntu.
My experience with C# and .NET Core on ARM Linux is that it comes with a lot of crashes and segfaults. On x86-64 Linux it works well, though.
If your environment is anywhere similar, and you OK with .NET 2.1.18, you can try my package: https://github.com/Const-me/Vrmac/releases/tag/1.0 Sources: https://github.com/Const-me/Vrmac/tree/master/net-core
Naturally it requires understanding the whole stack, how to improve performance in general, picking the right data structures and algorithms as well.
Of course, there are scenarios where that might not be enough, and that is where a C or C++ native library might come into play.
No need to be siloed into a single language.
Nim, Crystal and Swift all compile to native code and is designed for that.
Swift is not a GC language in the normal sense since it uses automatic reference counting. That is fully deterministic and with low latency. There is no stop the world to collect garbage like with Java and C#.
Swift is akin to C++ code with smart pointers.
Also, you can compile both Java and Kotlin to native images with Graal.
VM vs native is one way, but you can also go by the level of abstraction they provide, which very much puts them in the same category.
Also, this debate comes up every time, but ARC is widely classified as a form of garbage collection, just not a tracing one. It is transparent and automatic to the user. (Well, apart from cycles)
FWIW Chris Lattner, the language creator, put Swift into a non-GC camp.
Widely acknowledged CS books see it differently,
LLVM, GIMPLE, TIMI, TenDRA,...
Java and C# also targeet native code,
.NET Native, CoreRT, IL2CPP, Mono AOT, SubstrateVM, GCJ, ExcelsiorJET, OpenJ9, PTC, Aicas,...
Languages and implementations are orthogonal.
An Introduction to Crystal - https://news.ycombinator.com/item?id=26217013 - Feb 2021 (39 comments)
Switch from Ruby to Crystal - https://news.ycombinator.com/item?id=25005780 - Nov 2020 (35 comments)
Go vs. Crystal Performance - https://news.ycombinator.com/item?id=23615303 - June 2020 (160 comments)
Ruby vs. Crystal Performance - https://news.ycombinator.com/item?id=23431941 - June 2020 (148 comments)
Crystal 0.34 - https://news.ycombinator.com/item?id=22800139 - April 2020 (31 comments)
Towards Crystal 1.0 - https://news.ycombinator.com/item?id=22725829 - March 2020 (32 comments)
Crystal 0.33 - https://news.ycombinator.com/item?id=22331005 - Feb 2020 (42 comments)
Nim vs. Crystal - https://news.ycombinator.com/item?id=21883882 - Dec 2019 (147 comments)
Lilith: x86-64 OS written in Crystal - https://news.ycombinator.com/item?id=21860713 - Dec 2019 (251 comments)
Crystal 0.31 - https://news.ycombinator.com/item?id=21053366 - Sept 2019 (15 comments)
Parallelism in Crystal - https://news.ycombinator.com/item?id=20897029 - Sept 2019 (41 comments)
Crystal 0.29 - https://news.ycombinator.com/item?id=20110253 - June 2019 (7 comments)
Crystal 0.28.0 Released - https://news.ycombinator.com/item?id=19694006 - April 2019 (30 comments)
Crystal 0.27.0 released - https://news.ycombinator.com/item?id=18370498 - Nov 2018 (36 comments)
Crystal 0.26.0 released - https://news.ycombinator.com/item?id=17753367 - Aug 2018 (40 comments)
Crystal 0.25.1 released - https://news.ycombinator.com/item?id=17424639 - June 2018 (152 comments)
Crystal 0.25 - https://news.ycombinator.com/item?id=17319689 - June 2018 (68 comments)
Why Crystal Is My Next Language - https://news.ycombinator.com/item?id=17280969 - June 2018 (192 comments)
The Crystal Programming Language - https://news.ycombinator.com/item?id=16842442 - April 2018 (73 comments)
Why I'm excited about Crystal in 2018 - https://news.ycombinator.com/item?id=16221053 - Jan 2018 (7 comments)
Crystal 0.24.1 released - https://news.ycombinator.com/item?id=16021965 - Dec 2017 (22 comments)
Overview of the Crystal language - https://news.ycombinator.com/item?id=15945262 - Dec 2017 (90 comments)
Crystal in Production: Diploid - https://news.ycombinator.com/item?id=15574714 - Oct 2017 (120 comments)
Journey from Node to Crystal - https://news.ycombinator.com/item?id=15240984 - Sept 2017 (6 comments)
Crystal Lang vs. Node.js vs. Go Benchmarks - https://news.ycombinator.com/item?id=14368050 - May 2017 (10 comments)
From Ruby to Crystal: A Quick Look - https://news.ycombinator.com/item?id=13949353 - March 2017 (43 comments)
Building a Command-Line Application with Crystal - https://news.ycombinator.com/item?id=13945591 - March 2017 (33 comments)
Porting Ruby to Crystal - https://news.ycombinator.com/item?id=13837109 - March 2017 (34 comments)
A new year resolution to have Crystal reach the 1.0 milestone in 2017 - https://news.ycombinator.com/item?id=13281333 - Dec 2016 (78 comments)
The Crystal Programming Language - https://news.ycombinator.com/item?id=13209575 - Dec 2016 (193 comments)
Crystal: Fast as C, Slick as Ruby - https://news.ycombinator.com/item?id=12223395 - Aug 2016 (429 comments)
Kemal: Fast, simple web framework for Crystal - https://news.ycombinator.com/item?id=11143811 - Feb 2016 (16 comments)
The future of the Crystal language - https://news.ycombinator.com/item?id=10803635 - Dec 2015 (36 comments)
Crystal-Lang – Beauty of Ruby, Faster Than Go - https://news.ycombinator.com/item?id=10014178 - Aug 2015 (31 comments)
Amethyst – Rails inspired web-framework for Crystal language - https://news.ycombinator.com/item?id=9716508 - June 2015 (8 comments)
Crystal Language - https://news.ycombinator.com/item?id=9669166 - June 2015 (171 comments)
Crystal: the programming language - https://news.ycombinator.com/item?id=8658358 - Nov 2014 (51 comments)
Meet Crystal language in its birthday. It is compiled and has ruby like syntax - https://news.ycombinator.com/item?id=6342609 - Sept 2013 (84 comments)
Side note: Anybody know of tools that auto-compile lists like this? I'm sure it's not perfect, but these sorts of reviews really add value to the current conversation.
Edit: I still want to make this a community/collaborative effort. As HN's archive grows it gets richer, so this gets increasingly worth doing over time. The recent changes are a small step in that direction.
Edit 2: maybe someday it could be integrated somehow with the criminally neglected Usenet archive and go all the way back.
when you browse via the web (unsure which apps support this feature), clicking the title - in this case, Crystal 1.0 – does what you expect and takes you to the article. in tinier font next to the title - in this case, (crystal-lang.org) - is a link that shows all the previous HN submissions for that website, which happens to be pretty similar to what dang posted.
this wont work for platforms like medium or reuters, but is a big head start for a single-issue website like this.
It would help if the front page were to say.
We embarked on a huge project (search engine) using Crystal a year ago after also considering Rust. We have been very satisfied with the exceptional performance, language capabilities and the community.
To me, dealing with JSON is always a challenge in truly static type language. Crystal solved it very nicely https://crystal-lang.org/api/1.0.0/JSON/Serializable.html
I think that would be good no only to reduce compile times but also to make code more readable
Perhaps you mean something else, but this is from the book Programming Crystal:
> Returning Values
> A method returns the value of its last expression, so there’s no need to explicitly return that or declare its type. However, if you want to document or directly control that return type, you can explicitly specify the type, as in this example:
> methods_and_procs/methods.cr
def typed_method : Array(Int32)
(42..47).to_a.select { |n| n % 4 == 0 }
end
typed_method # => [44]I would like Crystal compiler to take into account such definitions and speed up somehow. I know I am talking without experience, I just feel that coming from Ruby to Rust, declaring types on function definitions is not that much of a hassle.
# version 1:
def add(x : Int, y : Int)
x + y
end
# version 2:
def add(x : Number, y : Number)
x + y
end
# version 3:
def add(x : Number, y : String)
x.to_s + y # convert a number to a string with to_s method
end
# version 4:
def add(x, y)
x + y
end
# new methods:
# version 5:
def add(x : Number, y : Bool)
y ? x : 0
end
# version 6:
def add(x : String, y : String)
if x.to_i? && y.to_i?
add x.to_i, y.to_i # calls version 1
else
x + y
end
end
add(2, 3) # => 5
add(1.0, 3.14) # => 4.14
add("Hello ", "Crystal") # => "Hello Crystal"
add(42, " times") # => "42 times"
add 5, true # => 5
add 13, false # => 0
add("12", "13") # => 25
(also from the book)> I just feel that coming from Ruby to Rust, declaring types on function definitions is not that much of a hassle.
I'm another one who doesn't understand why declaring types is seen as a hassle, but then I went from C# to Ruby so perhaps I was already used to it. Happy to bring a little back!
How would providing types speed up anything? As far as I can see, the compiler still has to infer types in order to know whether the type you provided is correct.
They allow specifically that though?
https://crystal-lang.org/reference/syntax_and_semantics/type...
I find the API nice and easy specially for those familiar with CSP or go. Performance should improve once it gets the necessary love for it to be released to be on by default.
Windows requires a lot of boring work, especially considering most of the core devs use Linux / MacOS. Once they make it as priority it should be doable within a few months of work.
Note that all of Crystal's competitors give first-class support to Windows: go, zig, nim, ruby, etc.
I say this as a suggestion, rather than a critique. Actually, as a hopeful suggestion. :-)
There's sense in just releasing for nix platforms if you have them ready to go, especially when you expect your initial target market to be deploying on nix systems.
https://framework.embarklabs.io/news/2019/11/18/nim-vs-cryst...
A better Nim impl would use `parsejson` directly at which point its speed can approach the fastest of any impl. [1] Also, it is implementions (like stdlib) that are assessed not the speed of the language. There are like 10 json parsers beyond the stdlib. No idea about Crystal ecosystem diversity...
EDIT: Also, I just ran the base64 encode/decode and with `crystal build --release b64.cr` vs. `nim c --gc:markAndSweep -d:danger --cc:gcc --passC:-flto b64`, the Nim ran twice as fast not half as fast.
Anyway, people should not draw conclusions like "Crystal is faster than Nim" from things like this.
Plugging this great howto video, which I didn't make https://youtu.be/DxFP-Wjqtsc
My only complaints: * compilation speed * type inference is sometimes not intuitive (class members * too OOP oriented (no constness, no UFCS/pipeline, no general monad operator,...etc)
Now I'm going to spend time writing something non-trivial in Crystal. Very excited!
Can anyone who writes Crystal today dispel or confirm that information? I imagine the inferred typing makes building a fast compiler really tough.
There is no solution really. If you want a language to feel like it is dynamically typed but actually has types, something gotta give, and in this case it is compilation times. And it gets quite high fast.
You might know a lot more than me about the subject, so feel free to correct me, but I think it used to be truly global inference - as in you were expected to write the whole program with no annotations. But now you're required to annotate instance and class variable types, because compile times were becoming intractable.
In contrast, local inference considers a predefined context, such as the current module or class, and is much faster, easier to cache, etc.
Compiling isn't fast. By the language design it won't ever be possible to be in the same ballpark as golang. It's common for us that our 5k lines app, take >1m to build in release mode as we added more and more dependencies for integrations with different nosql databases (elastic, rocksdb etc).
Using LLVM as backend is great for getting top notch performance, but to generate multiple IR methods for each type signature consumes a lot of time, especially when doing optimisation in release mode builds. I guess it's a similar problem that cranelift is trying to fix in Rust.
Open classes makes it hard to cache compilation results from dependencies / libraries.
Yes, for this reason I think crystal could be one of the best languages for microservices or faas.
I've long wanted to get into Smalltalk and this looks like a very nice, modern way of doing so.
There are two pretty big web frameworks in the Crystal world right now, one is Kemal which strives to be the Sinatra or Flask of the Crystal world (lightweight, supporting plugins), and the other is Crystal which is trying to be the Ruby on Rails or Django of the Crystal world (full-featured, opinionated).
https://athenaframework.org is pretty unique. It's one of the more flexible frameworks IMO. It was inspired by Symfony and Spring, so takes a bit of a different approach as most other frameworks come from a more Ruby background.
Edit: Ok, looks like there is a "why" section on github!
The importance of asking why is that you validate whether a project is some ego trip or a legitimate finding worth pursuing.
The downside being a relatively slow compiler. It's the necessary trade-off for the type inference.
- tooling is abyssmal ( IDE etc ... )
- no mutli threading
- no real support, I'm not even sure if there is one paid guy anymnore on the project
- it's still immature
- they break the API all the time
As others have mentioned this is a side effect of how young it is.
> - no real support, I'm not even sure if there is one paid guy anymnore on the project
If paid support is all that counts then most languages would fall into that.
> - it's still immature
Tautological. Nothing can become mature without first being immature.
> - they break the API all the time
Isn't that what a 1.x is for? A stable major version?
Can you name another recent programming language that wasn't developed in-house at a major company. Go , Dart , and Rust all started out with paid teams of programmers working on them.
Not sure if Crystal will be able to progress at a rate fast enough to keep people interested. Likewise, you'd have to be insane to try and pitch this to your project manager. I consider myself to be a risk taker, but even for my own side projects I stick to older better known programming languages.
it feels like we keep trying to solve the same problems over and over again instead of utilizing solutions which already exist
Modern python has grown significantly from its first release in the 90s though. I don't think that can happen today, we all want too much out of our programming languages.
Unfortunately that’s not true at all - Python is far from simple. Sure, it’s no C++, but it’s very complex compared to languages like C, Go, Lua, even JavaScript.
The compile times are largely a side effect of the underlying language design, and it isn't likely to get significantly better, which is why I mentioned it.
- no multi threading: Crystal has lightweight green threads for lightweight concurrency. However, Crystal does have experimental native multi-threading guarded by a compiler flag. The ultimate goal is to have multiple native threads each running multiple green threads for maximum concurrency. Currently, with just green threads, Crystal is competitive with Go or Rust wrt throughput. You can checkout benchmarks on crystal-lang.org or search Google for "Go vs Crystal benchmark" blog posts.
- no real support: Manas Tech is the corporate sponsor for Crystal (https://manas.tech/projects/crystal/) and employs many of the core developers; the project also accepts donations on Open Collective. There is a crystal-lang IRC channel, Gitter, Discord, Discourse forum, sub-reddit, and even a "Matrix" channel where developers frequently ask and answer questions.
- still immature: a 1.0.0 version is a significant milestone for any project. Companies are already running Crystal in production, which was discussed during the Raw Crystal 2020 virtual-conference (videos on YouTube).
- API breakage: Crystal 1.0.0 was released to stabilize the API (SemVer)
So what is the performance like?
With the caveat of course being that benchmarks don't always reflect real world performance.
I'm curious about that, MLs usually have fast compilers (OCaml for example) and have type inference. I thought that the slow compilation times were due to LLVM .
I believe you can speed up your compile times a bit by being explicit with annotations (which I often prefer anyway), but there's still a lot of overhead for the global type inference.
Compared to Haskell, Ocaml, Scala, F#, ...? Writing it like a dynamically typed language is standard for full type inference.
Scala has both, F# too (though function overloading is possible, I think it's not idiomatic), OCaml has named arguments, Haskell has overloading.
It's right here:
This is a sweet spot for anything web-server related (which is quite a lot, these days, considering mobile API's and such...)
I would actually hesitate to use a framework for many use cases, although Amber and Lucky are competing for a "soup-to-nuts" total platform, Kemal fills the Sinatra/Express niche nicely, and you really find yourself wondering what you want.
The "shards" module system is very nice, and no external tool is needed, it comes with Crystal.
No need for a C FFI, you can directly wrap the C ABI or C libraries and call both ways (from C to Crystal, from Crystal to C)
It's not Rust nor C nor C++ but more like a better Go, in terms of being a statically typed compiled language that, nonetheless, is garbage-collected (everything is an object, check the API docs I linked above) but it's syntax is like Ruby, and so it' basically eats Elixer and all that's lunch... (Lucky takes on Phoenix, Amber takes on Rails? lol)
It's also extremely fast, and the memory footprint is tiny... (anecdotal, but a 40kloc that was around 500mb ram on node is currently using 12mb)
The compiler catches many errors and long-running applications don't apparently leak memory (cough cough anything dynamic) and one has the feeling that the static typing is allowing stack-frames scoping and Boehm to play nicely together, but this is just anecdotal.
I guess you have one way to find out "Why Crystal"
Let's not HackerNews Bikeshed this to death... go try it... you'll probably like it.
Tooling? I use gedit, so I'm the wrong guy to ask. It works fine on most "ruby" presets for text coloration/highlighting whatever you call that stuff...
I’m curious as to what others in here feel about pros and cons of learning Nim compared to Crystal?
Now that it is at 1.0, I agree - worth serious reconsideration. That said, it sounds like Nim's concurrency is far more mature than Crystals, and Nim works well on arm etc. So definitely worth another look, but might be a couple more years before I'm able to use it in anger unfortunately.
Ruby?
Initial Julia work at MIT was funded by some absolutely tiny grants for distributed linear algebra research. The total amount of funding that Julia has gotten via MIT is definitely orders of magnitude less than the salaries paid to Swift, Go and Rust developers by Apple, Google and Mozilla.
While a lot of this research does trickle back into the Julia ecosystem, it's much less direct than Facebook paying the day salary for a bunch of PyTorch engineers. In fact, it's almost the opposite: most of the people who are paid are graduate students, who may be at risk of graduating if all they do is contribute to Julia instead of getting the required research manuscripts written.
One of the major transformations that has happened to the ecosystem though is that post-1.0 it has gotten some pretty wide industry adoption, along with some major success stories built directly off of the Julia ecosystem. Julia Computing, Pumas-AI, RelationalAI, Invenia, and Beacon Biosystems are just a few companies that are noteworthy of hiring a good number of contributors to the language and package ecosystem. The hiring by these kinds of entities has actually been growing so rapidly that it's a great labor market to be looking for a job in. But most of this hiring frenzy is not driven by MIT.
As for other Ruby alternatives, I moved from Ruby to Elixir a few years ago. Immutable data and OTP more than make up for the language's lack of static typing in my mind. There is also Gleam, which I'm watching for now.
One of these days I'll dig into Rust, which fills a similar niche that Crystal would.
I played around with crystal two years ago and really liked the approach. To me it's a nice statically typed Ruby with a simpler "meta"-programming through macros.
Decent multi-core support is the next milestone for the project and I'm looking forward to it :)
Anyone knows how good the tooling around crystal is nowadays? When I used it for a pet project there was no decent language server and it felt as I was using a notepad. How is https://github.com/elbywan/crystalline or https://github.com/crystal-lang-tools/scry nowadays?
Would be cool if every language provided their own language server as part of the base release, I guess.
I couldn't find this with a casual Google search, but will they be developing a formal language spec? Is love to see a Graal, JVM, or LLVM target for Crystal eventually!
https://nlh.me/projects/celestite
It's a way to tightly integrate Crystal + Svelte, including server-side rendering and the ability to write your frontend purely from components.
Not React (yet), but it's heading in that direction...
I tried crystal recently to dump some data from a GraphQL endpoint into Redisgraph. I had done the same with Rust, but string manipulation was much simpler in Crystal (the string interpolation is amazing!) so it was much simpler to quickly throw together the queries I needed. I think for me, rust is something I'd use for building a solid library, whereas Crystal would serve more as an alternative to Go, something more for quickly assembling some building blocks into applications or doing glue stuff with decent performance.
both use significant whitespace
nim has better cross-platform and embedded support, as crystal targets llvm while nim targets c/c++/js
nim has more low-level support with the tradeoff of supporting dangerous things like raw pointers
crystal has better async imho
capability wise both are incredibly powerful genral purpose languages
For system language use, Nim is a better choice as you can use it with its Arc GC or no GC at all.
Perf wise, Crystal seems to be marginably faster in most benchmarks. Though at that level the difference isn't much.
crossplat wise, Crystal has no windows support while Nim has.
Tooling wise; crystal IDE tools are pretty far behind that of Nim.
Personally I like Crystal a lot due to its type system and syntax but you can't go wrong with either language.
Nim was a better fit for me because:
- More mature tools and 'beaten paths'.
- much faster compilation times
- much better cross platform support
- more people I can reach out to for help, amazing Discord server
- simple concurrency for my needs
- tiny binaries
- low memory usage
- simple to understand import system. (i hated ruby's import system)I tried Go, it looks ugly (sorry).
I tried Rust: it is beautiful in concept but I don't feel productive enough. I mean, maybe my use case is not align with Rust.
My secondary lang should allow me to do more exploration in data analysis and not system programming.
I looked and Nim/Zig and something else in between like Lua and Python (for the 4 times in 4 years I think). Nim is nice but the ecosystem is not there. Zig is also sys-programming focus. Python ecosystem is huge but I just don't feel like the perf is a killer. What I really love about Python is the list comprehension, i.e a_list = [(Ababa) for a in 1..100 for b in 'a'..'z'] for example.
Until I found Julia, everything else is history. It seems to have everything I have asked for and more. Albeit that the precompile time is abysmal but I know I could use the sysiamge hack-around.
I think I'd stick and be content with Julia! With VS Code's Julia extension plus the remote (SSH) development, I could do many "cool" things people do in Python except maybe more performant :-)
If you love Ruby, and want something with higher performance, then Julia is a pretty natural next step. Sure it does not have the same syntax.
Maybe I am just getting old but when Ruby came out, that was cutting edge. It was a fresh new way of writing code. I had all the cool stuff. I you want that same kind of feeling today, where you got super awesome modern features, meta-programming galore, functional programming, you name it, then Julia is it.
Julia is the new Ruby. Sure Ruby is a very object-oriented language and Julia is more functional, but that is where the industry has been heading these last years. And Julia is a pragmatic version of functional programming which I think most people can easily become accustomed to.
One step closer to a world of static, Ruby-like expressivity!
Is there any documents or comparatives that can help me selling this language to managers to use this language in a professional setting?
https://stackoverflow.com/questions/32916684/can-a-crystal-l...
https://gist.github.com/Papierkorb/02d6ba53c28b5035a80bf7695...
- You do the linking yourself, so that you can pass the flags to produce the kind of library you want. This is something the Crystal compiler helps with (it spits out the LDFLAGS that you need).
- Calling `GC.init` when your library is loaded if you want to use garbage collection seems a bit obvious in its necessity. Using `GC.free` to free memory that has gone beyond Crystal boundaries follows naturally (and most libraries have custom `free_*` functions to clean up complex objects).
The main complexity you have to write a C-friendly API for the library, but that's something that you have to do with C++ and so on as well
Again, have you ever actually tried to do this or are you just speculating?
I found that easier than constant crosscompiling.
I'd prefer it be a gcc frontend, too, to gain access to all the interesting targets that gcc supports and llvm doesn't.