Hacker News new | comments | show | ask | jobs | submit login
The Crystal Programming Language (crystal-lang.org)
143 points by type0 68 days ago | hide | past | web | favorite | 73 comments




Some news in the past 5 months:

Crystal is now %100 funded for a full-time developer working on core - https://twitter.com/CrystalLanguage/status/98229223882167500...

Crystal Automated Release towards 1.0 - https://crystal-lang.org/2018/03/09/crystal-automated-releas...

A summary of why vs. why not - https://medium.com/@DuroSoft/why-crystal-is-the-most-promisi...

Web Frameworks Status:

Rails inspired (with Influence from Phoenix):

Amber - https://github.com/amberframework/amber

Lucky - https://github.com/luckyframework/lucky

Both look healthy in terms of community and updates but Amber seems to have a slight edge.

Sinatra inspired:

Kemal - https://github.com/kemalcr/kemal


I had a quick look at the language and checked about my two pet pieves. What does the code look like when handling JSON data? (that is when I slammed the door on Golang) What does the code look like when starting an external process, supplying stdin, args and capturing return code, stdout, and stderr? (that is when I slammed the door on lua). Well, I must say that crystal-lang manages to dodge the bullet twice. It handles both situations quite elegantly. So, yes, I am interested.


There are basically two ways for a programming language to deal with JSON:

(1) Expect it to conform to a contract. Marshal and unmarshal it to and from a backing data structure that represents that contract. Perhaps with some tolerance for missing or unexpected values.

(2) Have no expectations of a contract. Parse the JSON string into a blob-tree of your programming language's "any" type. Just kinda yolo-wing-it with tree traversal, and hope that your language syntax is very null-tolerant. Try to overlook the fact that ultimately you DO have an expectation of contract after all (!)... it's just scattered through your tree-traversal logic, rather than consolidated in a data structure.

I think pretty much all modern widely-used languages do a decent enough job with approach #1. While approach #2 is pretty much equally awful with all languages.


Ruby does fine with #2. Fans of static type systems love to overstate perceived drawbacks of dynamic languages, but the simplicity of reading Ruby code built around JSON.parse vs. Scala or Kotlin with Jackson more than makes up for any type safety from the compiler.

Also, there's a sort of intermediate step, a dynamic language with schema validation, for which I wrote a modestly well-used (within the overall schema validation gem market) gem a while ago. Then you can validate at the point of translation, without being beholden to a concrete data type (POJO, data class, case class, etc.) that has to be updated whenever the wire format changes.


"but the simplicity of reading Ruby code built around JSON.parse vs. Scala or Kotlin with Jackson more than makes up for any type safety from the compiler."

"Readability" and "simplicity" are except for trivial or outlandish cases subjective.

For example, I rather enjoy reading code using Rust's serde crate: the code is quite readable and comes with all the benefits of Rust's static type system and safety.


Ruby also does a decent-enough job with #1, albeit with a bit of elbow grease required to massage arbitrary JSON data into Ruby class instances. Not as elegant as a static type system, sure, but still workable.


> approach #2 is pretty much equally awful with all languages

Some are far more awful than others. Python for example has no issue here at all, json types map nicely into its basic data types, while most statically typed languages make json handling terribly verbose and make it feel like it doesn't belong there. And while there usually is some contract, there are many api providers that don't provide schema, make frequent changes or explicitely expect you to deal with "some random json here" part of the data.


What exactly frustrates you about Go's JSON handling? In my experience its among the best in typed languages. Define a struct (or just use an untyped map), optionally specify json tags for field names, pass it and the json to json.Unmarshal. Go is literally designed for this use case; systems programming, sharing messages between systems. Compared to other typed languages like Rust or Java, its at least as simple, if not simpler.


Try having a default value for a key in a JSON document that isn't null. In Go, this is pure torture. Then compare with the serde library for Rust. Go's lack of generics makes this kind of use case much harder than reasonable.


Compare it with F# type providers where you don’t need to generate any struct and you can access the JSON object in a type safe way with autocompletion in your IDE and maybe you will understand what is frustrating...


Hey, I found this tool very useful https://mholt.github.io/json-to-go/ when working with large JSON structs :)


How is that possible? You either need to specify the types up front (like Go) or give up on static type safety (like Python). It's impossible to have both.


Actually, you can have both. Many languages in the ML family (Haskell, F#, etc.) can ensure complete type safety without having to define the shape up-front.

Your data comes out of the JSON structure wrapped in a functor, and you can only access it in ways that deal with missing data.

It’s hypothetically possible to recreate this approach in Python, but I don’t think it would be Pythonic.


Its hypothetically possible to recreate this approach in any language. Grab the item in the map at that key -> check that it exists -> check that the type is what you expect.

Its not idiomatic in most mainstream languages. Except, that's how most JSON libraries which validate against a static structure are implemented, they just abstract the reflection away via some interface.


> Its hypothetically possible to recreate this approach in any language.

Yes, I didn't mean to imply that I was providing an exhaustive list of languages where this approach could be implemented. I merely mentioned Python because it had been brought up several times in the thread.

> Grab the item in the map at that key -> check that it exists -> check that the type is what you expect.

I'd argue with that phrasing. You don't check for the key, nor do you check the type. That would imply something like an if-statement to ensure the presence of a key, and switching over a type. In such a system, you could forget to check for null, or make some other mistake that causes the program to fail at run time.

The functor approach does not rely on the developer checking anything. It is 100% type safe, enforced by the type system, cannot crash at run time, and does not require you to provide a data type to extract the data into.


That sounds like how you have to access JSON data dynamically in any statically typed language.


But these other languages wouldn't necessarily remain type-safe.

The functor approach doesn't involve any type casting, and null pointer exceptions are impossible.


So, like this sort of thing:

int JsonValue::getAsInt(int defaultIfError);

Or, in Go:

func (JsonValue*) AsInt() (int64, error)

Or are you just talking about it returning the value as a Rust-style Result sum type?



That says you need a JSON schema file, which is deciding the type up front and basically how Go works too.


You don't necessarily need a schema file. You can take a sample document as input for an inferred type. This is compile type meta programming

https://fsharp.github.io/FSharp.Data/

https://medium.com/@maximcus/magic-of-f-type-providers-225b1...


Agreed. Go is wonderful with JSON. Reasonable defaults and very flexible.


From what I know about go and JSON and what is possible in other languages I would never call it wonderful.


It might be wonderful now, but I first used Go in 2009 and it was really hard to work with back then, so maybe the OP has a warped view of the current state?


That was almost 9 years ago. Go has changed at a pretty rapid pace since then.


When I was younger, I programmed predominantly in Java and types were infuriating to me.

Then I found ruby and loved how simple it was to not worry about a type system.

Then I found out how infuriating it was to not have types.

Now I’m older and appreciate having types.

Cool story, bro.

Crystal looks cool!


There's a lot more to Types vs No Types than Java vs Ruby would imply!


Indeed. You learn e.g. Haskell and find that Java painfully lacks in the types department. You learn Perl and find that Ruby is unexpectedly rigid in the types department :)


Perl also has the ability to effectively build class instances from scratch by manually blessing arbitrary data structures (usually hashes, but not much stopping you from blessing an array or scalar). For some folks (like me), it's a blessing for making hard problems at least slightly easier to solve. For others, well, there's always Moose.

Basically, a lot of Perl's object-orientation weirdness derives from it all being exposed to you. It's an excellent learning experience if you ever want to know what it means to be a "class" or an instance thereof, since "methods" are just ordinary subroutines that expect your object (or class, for constructors) as the first argument, and class instances are just ordinary scalar/array/hash objects blessed with a package name. Nothing hidden or abstracted away. Simple and flexible enough to fit into the TMTOWTDI philosophy.


I built a client for http://woordenlijst.org/#/ (Dutch word existence checker basically) and it is super fast. The syntax is only slightly different from Ruby and it starts up instantly.

The only thing that I have against crystal is that my compiled binary didn't work on all other machines I tried it on. Even the practically identical macbook of a friend of mine. Looking around on their wiki pages and in the issues on github didn't give me any real solutions. So right now, as the version numbering suggests, it isn't ready for production.


When a compiled binary doesn't run on a different machine with the same hardware, it's probably a problem with dynamic linking. That seems to have been discussed on the issue tracker https://github.com/crystal-lang/crystal/issues/2003


Thanks, I have looked over this thread a while back, but it did not fix my issues. I might give it another go sometime.


Crystal is pretty neat and ticks off almost all of the boxes for me atm (For example the websockets server functionality in Kemal is super neat), but the lack of proper parallelism is kind of a killer. I know it's in the works, but afaict it has been so for almost a year now without much news...


Parallelism is always possible UNIX old style, use multiple processes.

It is anyway the trend we are getting back to, after we have learned our lesson how threads and plugins can bring whole processes down, while being good vehicles for security exploits.


While forking is always possible, there are some things that it does solve well enough IMO. For example, sharing and reusing database connections via a connection pool is much nicer if all threads can actually access the same memory. Shared memory is both a curse and a blessing of course :|


One can always use a bit of shared memory between processes. At least it would be contained to the parts that one really want to share. There are a few ways to do that. The easiest one is mmap MAP_SHARED|MAP_ANONYMOUS.


The conventional approach would be to have a process dedicated to database communication. Other processes would then talk to that process via some IPC mechanism.

This is reflected in the Erlang way of doing things (since there's no such thing as "shared memory", at least not directly); a process needing data from the DB sends a message to the DB connector process, which runs the query via one of a set of pooled connections and sends the result back as another message to the requesting process.


> For example, sharing and reusing database connections via a connection pool is much nicer if all threads can actually access the same memory.

Why is this inherently easier to you with threads rather than forks? Many very successful apps use a forking model and do what you say, so I’m genuinely curious on your reasoning here.


With Rust and other languages focused on correctness, the "threads can bring whole process down" is less relevant (they still can, but it not as common as it used to be), while having one process makes a lot of things much easier.


I've been a fan of Ruby for about 5 years, but lately I've been leaning a lot more heavily towards statically typed languages. Which is why Crystal is at the top of my "want to learn" list. Ruby inspired syntax and statically typed? Yes please. The speed is just a bonus for me.


I consider speed to be extremely important, because it enables me to use the language in many more practical scenarios. And that's a joy. Writing in most other imperative languages after having learned Ruby feels so painful to me.


I've been using Crystal on a mobile checkout application to watch for and parse incoming pricing files. I've been extremely happy with it so far. It is fast, the type system is nice, and I really enjoy the Ruby-like syntax and ease of development. I've also been happy with the developers and the community. The community is supportive and the developers are making good progress on the language and are quite responsive to community feedback.

If you like the idea of a fast, compiled, statically-typed, Ruby-like programming language, you can support Crystal development here (NOTE: I'm not affiliated with the Crystal project in any way. I'm just a happy consumer): https://crystal-lang.org/sponsors/


I played with Ruby like 15 years ago and loved it, but surely speed wasn't exactly a selling point, so I welcome this project. One question though for the gurus out there: they say the compiler is written in Crystal; is it good practice to do it that soon? To me it could slow down development, if a subtle error occurs it could make harder to nail it to a bug in the compiler or in the language.


Sure it's a little risky, but if you bootstrap from a stable, frozen version, it serves as a good integration test.

There are still bugs, but if it compiles itself and the compiled version still passes all the tests, how bad can the bug be?


I found it a bit weird too as introducing a cyclic binary dependency is definitely not best practice for a standard programming project. However the C compilers do the same thing and so you already have these types of cyclic binary dependencies and it is unfair to force all languages to be written in C.


> is it good practice to do it that soon?

1) How do you think most compiled languages were created back in their early days?

2) Crystal isn’t really that young of a language

3) even many non-compiled languages are run on an interpreter written in the language they implement these days.


Bootstrapping your compiler is a vanity project for languages. It's cool to show you can do it, but pretty pointless in a practical sense unless your language is targeted at building compilers.


Additionally, writing a compiler really helps a team find their edge cases much faster. Without a major software undertaking for an immature language, most of those bugs would be found (and likely not reported) by users.

But yeah, there's a little vanity too.


Sure writing a compiler can help there, but like I said, there are probably better ways to test that taking into account the design goals of the language. If you find that writing compilers is a pain in your language, but no one in your community is using your language to write compilers, are you going to invest resources in making your language better at writing compilers, or are you going to work on things important to your community?


Yeah, that's a good point. In the sense, RubyOnRails was a great blessing to the language.


It is not vanity at all.

It takes out of the table the typical argument that the language is not serious enough because it is written in C or something.

The usual old time joke "my compiler compiles your language".


Not all languages target C or C like workloads. Not all languages are particularly good at expressing parsers either. If your language is targeting e.g. Writing graphical visualizations, I don't see how writing compilers in the language serves anyone's interests except the language designer's.


I agree if the language is something very specific like SQL, or an extension language to embedded as plugins.

Otherwise any general purpose language, should be able to be used for all kinds of application, including compilers.


This is typical of new languages. A good showcase of dogfooding.


I have written a few small apps in it and so far I like it. I hope it gets traction and we see broad support for it dev tools. Would be cool to have debugger support in rubymine or idea for example.


Looks like a nice effort. As a fan of Python over Ruby, I would love to see something like this for Python fans, but efforts like this give me hope that one day it will happen.


Strongly typed language with Python-esque syntax: http://strlen.com/lobster/


One of the few languages that I've seen in years that I liked right away. Checks a lot of my boxes, I like the inferred types and I like static typing. Nice job!


It is also a potential compile-to-webassembly language.


Crystal looks really cool but if speed is what its after, i think it misses the target for most people.

because for most apps, you will be bottlenecked by the database server. it is the slowest hanging fruit on the tech stack, be it postgres , mysql, or mongo. so unless you are working on number crunching tasks that do not involve any db queries, i fail to see the benefit of a language like Crystal.

i wait for the day until the day Crystal becomes a 100% drop in replacement ready for Ruby apps.

until then, Sinatra is more than fast enough for most things.


I've heard this argument a lot, and have even used it myself as a justification for using Ruby on Rails over other technologies, because I like working with Ruby. However, I rewrote one of my large CRUD applications in Elixir and Phoenix a year or so ago (same database and same data) and was amazed at how fast my web app was when compared to the Rails version. I've had similar experiences with the Crystal code I've written (replacing vanilla Ruby), so I've had to step back from this argument and admit it isn't entirely valid.

I don't say this to knock Ruby. I love Ruby and think Matz is an outstanding human being. I'm just pointing out that while the database server can be a bottleneck, to be sure, there are still plenty of benefits to using faster languages to accomplish database-centric tasks.


> i fail to see the benefit of a language like Crystal.

For me it's a Ruby like language with static typing. For my typical use cases I don't really need the extra performance, but those two things are enough to make me interested.


This certainly used to be true, back when ram was expensive and drives were slow. But progress on both of those fronts has massively outpaced the increase in typical structured data, which is what you would store in a db. It's rich/unstructured data that has increased hugely in most usecases. But I would argue that it's not as cut and dry nowadays, particularly with the amount of serverside processing that is being done in other non-db areas.


It only takes a couple of gigabytes to build a dataset that can get painfully slow to go through in Ruby. Especially if you're not using the right techniques to store/parse it.


because for most apps, you will be bottlenecked by the database server.

Is there anything to suggest Crystal is primarily intended for use writing those kinds of applications? If we're talking about all of software development, most programs probably don't talk to that sort of database server at all.


This. Ruby is used where it's used because it's slow, and you can't write any professional, performant app with it. But if you have a similar language that it's safer and faster, Ruby syntax is a pleasure to work with.


How small is the lightest sinatra app though, when deployed in a container? We lately had some experience with dockerizing a moderately complex ruby app and it was pretty shocking how large they get compared to simple statically linked binaries.


This very much depends on the way you build a container.

If you take e.g. Alpine Linux as a base image, then build almost everything as a single step in a shell script, removing useless intermediate files, you get a pretty small image. If you take e.g. a normal Debian image and build it so that every step adds a layer, you end up with a large image.


im not sure what you are asking for in terms of lightness but its incredibly light in both concept and execution.

i mean it doesnt get any lighter than this.

require ‘sinatra’

get ‘/‘ do

  “hello”
end

you should check out https://github.com/jaequery/jasis

its a dockerized Sinatra app with all the bells and whistles and see how light it is yourself.


You do realize you are talking about totally different things compared to the post you answer to? You are talking amount of code, the post you are talking to are talking system resources. That is an apples to pears comparison.

Sinatra may be lighter than Rails resource wise, but it is still not in any way light compared to a compiled binary. The difference is pretty staggering in practice.


Sure but no useful app can be described by this "Hello HTTP" example. I've seen the same as parent: Ruby services require a lot more memory in their steady state than say, their Go service counterpart, for a same complexity.


you must be referring to Rails. sure they can be pretty big, as they are what you call a monolithic framework.

thats why i recommend Sinatra for performance conscious devs, as its a fast and minimal framework. and you get to load only what you need. check the github.com/jaequery/jasis github project and peek at the Gemfile / boot.rb to see all that it loads for a full fledged mvc app. its pretty explicit and lean if you ask me.

if are talking about server resource then you probably referring to the web server. for which we have ‘puma’ and it is also incredibly light and fast. much lighter than a php stack running Apache and mod_php for sure.

but i see you compared to Go to which we have no comparison Go will run circles around Ruby. but Go is not really a well suited environment for web applications yet so ill just leave it at that.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: