Hacker News

Top stories

Live mirror
30 storiesupdated just nowView source snapshot
  1. AI-generated posters don’t have to be horrible(john.hartnup.uk ↗)
    226comments
  2. Laya the open source version of Jev(convaiinnovations.com ↗)
    13comments
  3. Human brain is two separate organs, Stanford Medicine-led research finds(stanford.edu ↗)
    125comments
  4. “The Secret Life of Circuits” is here(coredump.cx ↗)
    30comments
  5. If math is more than proof, we need to better celebrate the rest of it(terrytao.wordpress.com ↗)
    115comments
  6. GPT-6 Astra Solves a WWI German Radio Cipher(prinzai.com ↗)
    93comments
  7. Android 17 is the first since 3.x to add new APIs without releasing to the AOSP(grapheneos.social ↗)
    471comments
  8. San Francisco Onion Futures Company(onionfutures.com ↗)
    83comments
  9. Show HN: I wrote a custom assembler for CHIP-8 in C++(github.com/tackx ↗)
    discuss
  10. Communication by means of modulated Johnson noise(pnas.org ↗)
    6comments
  11. Cloudflare Quick Tunnels(cloudflare.com ↗)
    292comments
  12. How to Write with an LLM(sockpuppet.org ↗)
    344comments
  13. You can run Git on object storage if you re-make packfiles(tigrisdata.com ↗)
    19comments
  14. SDCC – Small Device C Compiler(sourceforge.net ↗)
    20comments
  15. From Stonemasons to Carpenters(thelastsoftwareengineer.substack.com ↗)
    2comments
  16. Saving another 100TB of RAM(cloudflare.com ↗)
    84comments
  17. Science Is Open Software(jepedersen.dk ↗)
    41comments
  18. Why building a Rust LSP is hard(rust-glancer.github.io ↗)
    37comments
  19. How OpenAI Used Its Own LLMs to Design Its Jalapeño Chip(ieee.org ↗)
    98comments
  20. Ctenophores: Wonders of Biology(quantamagazine.org ↗)
    6comments
  21. NASA-IBM Lunar Foundation open-Source Geospatial AI Model(usra.edu ↗)
    4comments
  22. The first new cat species discovered in 100 years(nationalgeographic.com ↗)
    108comments
  23. OpenJev(openjev.com ↗)
    273comments
  24. Goroutine Leak Profiles(go.dev ↗)
    5comments
  25. Show HN: Cactus Needle 3: 8-29MB automation models can match DeepSeek V4 Flash(cactuscompute.com ↗)
    89comments
  26. Photon-Emission-Guided Laser Fault Injection Enables RP2350 Secure Debug(ledger.com ↗)
    73comments
  27. Veronese's Dogs(publicdomainreview.org ↗)
    2comments
  28. Warez: The Infrastructure and Aesthetics of Piracy (2021)(archive.org ↗)
    87comments
  29. Cache-to-Cache: Direct Semantic Communication Between LLMs (2025)(arxiv.org ↗)
    14comments
  30. Inside ZCode: Silently uploading your Git history to the cloud(ferstar.org ↗)
    105comments

The Crystal Programming Language

254 pointsby 11y agocrystal-lang.org
171 comments
11y agoHN ↗

Crystal looks like a neat language, and it's fun to see how many entrants there are in the modern renaissance of scripting languages that compile to native code. I wish there was some more documentation about it though... for example, does the bullet point "Never have to specify the type of a variable or method argument" from the home page imply gradual typing as per Dart and TypeScript, or is it something else entirely?

11y agoHN ↗

It has global type inference, there's nothing dynamic in the language. You can specify type restrictions to allow overloading methods, for example, or doing multiple dispatch. But in the general case you don't specify types (except for generic type arguments) and the compiler figures out everything.

11y agoHN ↗

I'm curious how possible it is in practice to write large software that relies exclusively on global type inference. Does the compiler itself go without ever explicitly specifying a type?

11y agoHN ↗

Yes, you can browse the source code if you want, check it out and compile the compiler. It has between 30k and 60k lines of code (because you have to consider it also includes the standard library) and on a Macbook Pro 2015 it takes less than 10 seconds to compile (in non-release mode).

We'll have to wait until we get project that's larger than the compiler, but we believe there's still room for performance improvements.

11y agoHN ↗

It seems to have a fair slew of type-system features, so how does it manage to compile so fast, compared to for instance the Rust compiler, which doesn't do global type inference?

11y agoHN ↗

The current slowness of the Rust compiler is largely due to putting more developer attention into improving the runtime speed of fully-optimized binaries. Hence, regardless of which mode it's in, the compiler does a lot of unnecessary work that a debug-mode binary doesn't need or benefit from. There's also simply a ton of low-hanging fruit lying around: we're only two weeks out from 1.0 and the nightly compiler is already reportedly 30% faster for code compiling in the wild. There's another 10% compilation speed reduction coming from work around optimizing linking. There's also work towards parallel codegen coming along nicely (https://github.com/rust-lang/rust/pull/26018) which reports a 30% reduction in compilation time. In the longer run there are plans for incremental compilation which should drastically reduce the amount of work that gets unnecessarily repeated with each compilation cycle (https://github.com/rust-lang/rfcs/pull/594). In the even longer run there are plans to fully parallelize the compiler phases and also to pre-optimize code before it gets to LLVM in order to reduce the sheer quantity of IR that we shovel into it.

TL;DR: Crystal manages much faster compilation than Rust because Rust's compiler developers may have deprioritized optimizing the compiler for a bit too long. :)

11y agoHN ↗

Thanks for linking to those RFCs. The planned or in-progress work for Rust compilation speed is very exciting. I can't wait to reap the benefits!

11y agoHN ↗

It's because we spent some time thinking the algorithms and optimizing them, and whenever we make changes to the compiler we make sure the times remain pretty much the same (it's hard because the compiler's size grows so the times inevitably grow, at least for the compiler). And from time to time we profile and optimize further, we like speed.

I believe with time Rust can achieve a similar performance, maybe even better because they will be able to do incremental compilation (maybe, I'm not sure how will they do that with parameterized types).

There's also the thing that most of the things in Crystal are lazy: if you don't invoke a method there are no type checks to be done for it. This means that you only pay for what you use (in terms of compile speed) but also what you don't use doesn't end up being in the resulting executable.

11y agoHN ↗

most of the things in Crystal are lazy: if you don't invoke a method there are no type checks to be done for it

Does this mean that if a library doesn't have complete test coverage compile-time errors could be discovered only by clients?

11y agoHN ↗

Almost. You at least need to exercise the code at compile time. For example you could write dummy usage tests like this:

typeof(MyClass.new.some_method(1, 2, 3))

That basically says "the above compiles and has some type", so you don't have to test what that method really does, just that it compiles.

I don't think it's a big problem, though. In Ruby it's the same: unless you exercise your code you don't know if it works. Now, think of a classically compiled language like C and C++: if it compiles, does it mean that it works? I doubt you'd release your code without at least a few tests (or a small test-app) to try it.

11y agoHN ↗

GHC does global type inference, and has very sophisticated types.

11y agoHN ↗

My point was specifically in regard to the feasibility of omitting the types in all cases. I see types often in Haskell code, likely because the community regards that as a best practice (in contrast to Crystal).

11y agoHN ↗

Best practice and it's required to make certain more advanced type features work.

It's the same with Crystal. Their generics require type annotations.

11y agoHN ↗

In Haskell types are used a lot as documentation and to make static guarantees about the program logic, not just runtime safety. If you're only worrying about illegal operations you practically don't need signatures.

11y agoHN ↗

...and is a slow compiler compared to for example Go :-(

11y agoHN ↗

OCaml has separate compilation, a REPL, and it's very fast. None of those things is incompatible with global type inference (although particular type systems can be).

11y agoHN ↗

Doesn't OCaml require everything to be defined before used though?

11y agoHN ↗

That's a problem with Cabal, not GHC. They're working on it.

11y agoHN ↗

What algorithm are you using? How do you deal with subtyping and paramatricity?

11y agoHN ↗

They have a couple posts on their inference algorithm, but I confess I haven't read them.

11y agoHN ↗

The algorithm isn't very easy to explain in a few words. We don't use any well-known algorithm. It has some bits of the cartesian-product algorithm, but just tiny bits.

There's some info here:

http://crystal-lang.org/2013/09/23/type-inference-part-1.htm... http://crystal-lang.org/2014/04/27/type-inference-rules.html

About subtyping and paramtricity, I'm not sure what's that, but the compiler just keeps track of all the types and forms unions, except in one case which is this one (a union of references with a base type): http://crystal-lang.org/docs/syntax_and_semantics/virtual_an...

11y agoHN ↗

If you are using Agesen-style CPA, then it is more of a global analysis (type recovery) then type inference. I would be a bit worried about scaling, but if you've put 50kloc through it, maybe you've figured it out? I'm working on my own type-less type inference system that I'll share more about soon.

11y agoHN ↗

Do you have a (more or less) formal description of the type system?

11y agoHN ↗

How long did it take to become self hosting? I checked the first tag on GitHub and it was still implemented in Crystal. Did you originally implement it in C or another language?

11y agoHN ↗

From what I understand in another post of their's, Crystal used to be written in ruby, before they successfully compiled it using Crystal.

11y agoHN ↗

I've been looking at the benchmarks https://github.com/kostya/benchmarks and am pleasantly surprised about the speed. It definitely smokes Ruby, but also is usually faster than Go. I know that all benchmarks are relative but Crystal seems a great language from a performance viewpoint.

11y agoHN ↗

Indeed it's becoming quite promising (using crystal already for few pet scripts which were too slow even for rubinius). Where it's most lacking at the moment is gc - it uses stop-world off the shelf boehmgc which is ok but not exactly great for memory heavy tasks.

11y agoHN ↗

imho, starting a new language with Boehm GC is a very bad design choice. It means, that one just allocates memory, and does not care for managing it. Even worse, it prevent linking any library, e.g. a 2nd thread running Lua+C, that cares for its own memory, because Boehm GC runs over the complete memory, not only the one the language has to manage.

You basically need 3 types of memory: First for the objects in your language, 2nd for foreign light weight objects, where you only know a pointer, and 3rd for foreign heavy weight objects, that gets their memory from your GC.

it uses stop-world

A fully concurrent GC is impossible, if variables are mutable. Regardless how tricky your GC delays the problem, there will come a point, where it has to stop all threads to collect the edge cases. This creates the GC dilemma, because currently only number of cores and amount of memory becomes cheaper, while single core performance stayed same for nearly 15 years.

11y agoHN ↗

In the beginning Crystal didn't free memory. We needed a GC and Boehm was a super easy way to get that. It worked out of the box with very little effort.

Eventually we can write our own GC or use another one. It's only a matter of time. But right now there are more important things, we think: finishing the language rules, stabilizing things, fixing bugs, completing the standard library and writing documentation.

Nothing is set in stone in a language, things can always evolve and improve.

11y agoHN ↗

A fully concurrent GC is impossible

That's a common misconception, of course mutability gc barriers can be made atomic. But it comes at significant synchronization cost, plus using full shared world like in C does not seem like a good design decision anyway in high level language like crystal.

Which is why I'd be more in favour of refcounting, and let the user make the choice - a simple stop-world gc mark&sweep is alright for tasks which can afford the higher memory usage and pauses (one gains good throughput), or rc - good for low latency, low memory usage (and low throughput and high cache pollution).

Regarding multi core threading, crystal has next to none. All modern gc design decisions depend on how exactly multicore threading will be eventually implemented. hence why fixing gc is not a priority, but rc could be readily useful.

11y agoHN ↗

I don't see a reasons for null to exist in a new language in 2015.

11y agoHN ↗

How do you model a "zero or one" relationship without null?

Maybe your answer is "with Optional" (or Option, or Maybe). We just choose to use union types and have "Nil | T" (Nil or T) be the same as "Option(T)" in other languages.

11y agoHN ↗

Yes, Maybe (Optional) is the way to go. The difference is, with nil you basically make every type optional allowing it to have nil as a value.

11y agoHN ↗

Well, in Crystal Nil is a separate type that can be combined with others. But, say, a String is always a String, it doesn't implicitly have the Nil type. Same goes with every other type.

Maybe you are thinking of Java/C#, where reference types can also be null, but this is not true in Crystal. It's also in a way similar (but not quite) to Swift, where optional types are different than types that can't be null.

11y agoHN ↗

Do you have a technical write-up of how Crystal does that? Or otherwise some links/papers that explain the principle in a language accessible for someone who is basically self-taught and lacks a formal CompSci education?

11y agoHN ↗

It's a nice approach, I like it. There is a difference between Option[T] and (T | Nil) that's worth mentioning, however.

Option[T]'s are composable. For example, let's say we have a "get" method to get the value for a given key, whose type looks like:

    get :: String -> (T | Nil)

If we were using Option[T]'s, it would look like:

    get :: String -> Option[T]

So let's say we have a map, and want to lookup a key (syntax is made-up):

    let m: Map[String,(Int | Nil)] = make_some_map()
    let result: (Int | Nil) = m.get("some-key")

If result is nil, was the value of the key nil, or was the key not in the map?

With Option[T]:

    let m: Map[String,Option[Int]] = make_some_map()
    let result: Option[Option[Int]] = m.get("some-key")

Here result will either be None, in which case the key wasn't in the map, or Some(None), which means the value of the key was None.

So there is an observable and potentially useful difference between (T | Nil) and Option[T].

11y agoHN ↗

I find the distinction important as it allows to establish a contract without falling into defensive programming. Null pointer analysis is great, I admit, but how it would help to write a library function without checking explicitly if its parameter is nil?

11y agoHN ↗

It's ONE way to go. There are others.

Sum types supported in the language such as in Ceylon is another one.

And yet another one is safe dereferencing operators (?.) such as in Kotlin.

11y agoHN ↗

And to be honest, (T | Nil) is pretty difficult to distinguish from (Maybe a = a + 1). Complaints feel difficult to motivate to me anyway.

11y agoHN ↗

They both accomplish the same thing: no unexpected null. The exact mechanism was never the interesting part. Maybe being monadic does offer some other benefits but the killer advantage is the responsible handling of nil. Crystal doesn't seem to have a foundation in monadic programming so the type unions seem like a reasonable approach there.

11y agoHN ↗

The advantage of the monadic approach is that it's easy to abstract over, because it's just an ordinary type in the language. So e.g. in scala I can call the same "sequence" method on a List[Option[Int]] as I do on a List[Future[Int]] or a List[ErrorMessage \/ Int]].

Unions seem like more of a language-level feature, so I'm not sure you could abstract over them in the same way.

11y agoHN ↗

I totally agree. Ridding yourself of nils via the Maybe monad offers incredible abstraction potential, but would feel out of place in Crystal's Rubyishness without deeper thought into bringing other monads into play too.

Nonetheless, I am thrilled that we are seeing more and more languages that don't have implicit nullability on any type.

11y agoHN ↗

I re-read my initial comment and can see some ambiguity in my phrasing "Maybe being monadic ...". That wasn't me saying "maybe it is true that being monadic..." but rather "the fact that Maybe is monadic..."

11y agoHN ↗

That just depends upon whatever type abstraction capabilities Crystal grows. It probably won't be getting higher-kinded nominal types so generalizing over Monad is sort of dead in the water anyway.

11y agoHN ↗

How do you model a "zero or one" relationship without null?

With something that leaves "Nullpointer analysis" obsolete/useless.

11y agoHN ↗

To be replaced with an analysis that finds unmatched cases (like None when you assumed Maybe)?

11y agoHN ↗

Why would a constructor be confused with a type constructor? I guess I just don't have any experience with this particular approach to "sums". Untagged variants?

11y agoHN ↗

A tiny observation here: we don't do "null pointer analysis". We do this kind of analysis for every type: if you invoke a method on a type and that method doesn't exist, you get a compile error. In the case of a type union, all types must respond to that method. "Nil | T" is just one particular type union, but you can also have "Int32 | String", etc.

11y agoHN ↗

Thanks, that clears things up. I assumed that Nullpointer analysis was a special case.

11y agoHN ↗

I love that we're getting new languages lately, but almost all of them seem to be ignore the significant new requirement of our age: parallelism & concurrency.

Specifically, you need lightweight processes and no-shared-memroy architecture.

While the number of cores on a machine is remaining relatively low, the number of machines in a system are going up.

Erlang got this right and build a lot of infrastructure around it (OTP) and while you can't replicate that infrastructure quickly you can get the fundamentals right.

Simply getting this wrong rules out a lot of languages from consideration (because why learn a new language that is going to be obsolete, or only chosen by people who don't understand how to build systems?)

It's a lot easier to get this in when you're new and can make major changes to the language. Once you start to solidify it would break things- this is why Go's fake concurrency is a tragedy and a huge missed opportunity.

11y agoHN ↗

I largely agree with what you are saying, but doesn't it only apply to languages that will be used in back end systems? If you are just making a phone app or something, it doesn't seem like parallelism is nearly as important.

11y agoHN ↗

Phones are getting more and more cores. It won't be long before languages for mobile need to solve the same issues - parallelism and concurrency.

11y agoHN ↗

You are right, it wouldn't be wise to create a new language without having concurrency in mind.

Starting from the last version there's spawn (lightweight processes) and channels for communications, similar to Go (although it's in a very experimental stage right now). We aren't sure immutability everywhere is the solution for everything, some algorithms and programs are much more efficient with mutable data.

I think Go is quite popular and have an amazing concurrency support. It's true, you have to make sure to communicate using channels and not via shared memory, but if you follow that rule than you won't have problems, and you can get pretty efficient programs.

11y agoHN ↗

I have to disagree with you on this.

A language should be expressive enough that it can easily support correct concurrent programming, and readily expose runtimes (OTP is a great example) that have support for concurrency and distributed programming.

However, patterns like message passing instead of shared memory are not a panacea. Deadlocks and non-deterministic behavior can still happen in such systems if programmers do something stupid.

Enforcing these patterns at the language level smells of the old "let's make a language that doesn't allow programmers to write bugs!" trap.

11y agoHN ↗

> I love that we're getting new languages lately, but almost all of them seem to be ignore the significant new requirement of our age: parallelism & concurrency.

Yes. I expect this to be the norm in future languages. App developers shouldn't have to worry about these kind's of things, and we can just focus on making apps. Parallelism and concurrency should just work out of the box.

11y agoHN ↗

Specifically, you need lightweight processes and no-shared-memory architecture.

No you don't. I mean, that's one way to do it, but its not the only way and is often not an option if you want real performance. CUDA (a language specifically designed for SIMT parallelism) supports shared memory for good reasons; the threads are also not lightweight in that way (though they rely on memory and branch coherence).

11y agoHN ↗

It can be argued that shared memory should be explicit-when-needed rather than everywhere-by-default though.

11y agoHN ↗

It can also be argued the other way, of course. Do you like eating vegetables or ice cream? It's like garbage collection, some people love it because it frees them from worrying about allocating and freeing memory. Others hate it for the same reason.

11y agoHN ↗

Just curious, why Crystal? It looks just like Ruby. What problem(s) are you addressing with Crystal?

11y agoHN ↗

The important thing is that it’s statically-typed. All languages are compiled nowadays, except Bash. Ruby, Python and the likes all use VMs, and the code you write is compiled in VM bytecode before being executed.

11y agoHN ↗

We like the way Ruby lets you quickly prototype things, but its performance isn't very good (it's just good) and it also lacks static type checks (for example "undefined method '...' for Nil" is a very common runtime error).

So, we are trying to create a language with all the nice aspects of Ruby but with static checks and better performance. Of course that comes at a price: no dynamic aspects (no eval, no instance_eval, no methods or classes created at runtime, no methods redefined at runtime, etc.). But we try to compensate those with macros and compile-time reflection.

One thing that is definitely not one of our goals is to replace Ruby: every tool has its place.

11y agoHN ↗

I recommend adding something about static type checking to the list at the top of Crystal's homepage. I saw "never have to specify the type of...", and believed it was another dynamically typed language.

I actually didn't know it had static type checking until I had closed the tab, and glanced at this comment you posted here. (I know, I didn't read very far in the linked page.)

That aside, it looks neat! :)

11y agoHN ↗

You are right, it's far from obvious after reading that list. I updated it. Thanks!

11y agoHN ↗

I came across this a few months ago and ignored it for similar reasons: the homepage doesn't talk about the fact that nil is a type not a value (which is AWESOME, btw). It wasn't until a coworker encouraged me to dig into the docs that I realised Crystal is actually pretty sweet.

I recommend stating something like "No unexpected nils at runtime" and for those curious, a link for where to read more. Not having nil is basically the most important feature in most new languages I take the time to play with. Now that I know Crystal treats nil responsibly, I'm really keen to start playing with it.

11y agoHN ↗

People talk about how Crystal's performance is better because it's statically compiled and removes Ruby's dynamic features, but I'm not sure static compilation is the best way to achieve performance, and I don't think the dynamic features need to damage performance.

For example, JRuby+Truffle runs Crystal's own sample programs around twice as fast as Crystal does, without static compilation, and while still supporting all the metaprogramming and dynamic features of Ruby such as monkey patching, send, method_missing, set_trace_func, ObjectSpace etc.

https://gist.github.com/chrisseaton/91c7cbf8f6f4f6ea44bb

However Crystal does start faster, and I'm sure it has lower overhead.

11y agoHN ↗

Right, but if you want the static checks for safety, then even if you run on something like Truffle, you will lose a lot of dynamic behavior. Either that or your static checks won't be able to check as much, and will give you weaker guarantees.

11y agoHN ↗

Did you compile the Crystal program with the --release flag? That turns on optimizations. On our machines we see Crystal is faster in this benchmark, it always takes about 1.6s while JRuby+Truffle reaches 2.38 at most.

11y agoHN ↗

No, I didn't, sorry!

I don't see that option documented anywhere except the changelog, and one passing reference in the docs that says it sets the release flag, but neither say it has any effect on optimisation.

11y agoHN ↗

Yeah, we need to document this better, sorry!

11y agoHN ↗

Get used to this, watching people run benchmarks after having forgotten to compile with optimizations is basically a meme in the Rust community. :)

11y agoHN ↗

Macros is something I really miss in Ruby. But static classes strikes me as a bit of a limitation. E.g. a Crystal REPL can't create classes if I understand it correctly.

11y agoHN ↗

Awesome! Did you consider synchronizing your syntax with Mirah / typed ruby? Also what's your opinion on the Vala/Gini toolchain?

11y agoHN ↗

At first look, I like it. Would be great if we can build a ruby-to-crystal bridge so all the ruby goodness can run on top of it without requiring programmers to learn a whole new language. No GIL, faster than ruby but syntactically close enough, the dream.

11y agoHN ↗

Current channels implementation uses fibers, ie everything runs on single thread and only stack is switched. There is no GIL.

If you want true multithreading, simply use pthreads like you would in C. Just make sure to mark gc roots of new thread stacks (gc is thread aware). But this also comes with a lot of headaches as you're now responsible to synchronize everything and manage mutexes by hand.

11y agoHN ↗

I'm looking for something beautiful like Ruby but fast like Go. Do you think Crystal fits this bill?

Also, are there packages/libs/gems for Crystal? What are they called? What do I google for?

One of the major reasons why I dumped Go is that it's just too verbose and makes me write too much boilerplate code. I want to sort a collection and I have to write the same algorithm every single time for every single type. It's just boring and my time could be better spent elsewhere.

I appreciate the feedback HN!

11y agoHN ↗

You're looking for Scala. The "good parts".

11y agoHN ↗

You're sounding like a broken record by now, Cedric.

11y agoHN ↗

Elixer and Erlang are not faster than go.

The ability to respond in microseconds is a latency issue, not a raw speed issue. Erlang and Elixer can do that because of the way the core systems are architected but the underlying VM and language constructs are quite a bit slower than anything that Go has to offer.

For maximum throughput out of a given chunk of hardware you'd want Go.

Erlang / Elixer and the associated VM and eco-system have their own strength, reliability for instance, with some C code thrown in for heavy lifting if required.

11y agoHN ↗

If we are talking about maximum throughput, then you want C++ and in my tests Java has been better than Go, .NET probably is better than Go as well - because when speaking of maximum throughput RAM becomes the biggest bottleneck. And as soon as you're talking about platforms with a non-optional GC, then we start speaking about the performance and predictability of that GC. Go doesn't bring improvements for managing memory access patterns over other GC-enabled platforms, like Java or .NET and compared to those, its garbage collector story has been much, much worse, albeit improving.

11y agoHN ↗

That's not entirely true, not everything is garbage collected. With some care, you can get a whole lots of things allocated on the stack. Tooling can also help you figure if things escape to the heap.

You can also control the size and number of objects your program creates, which impacts the performance of GC. Sure you can't pick a GC from a stack of GCs, but you do have many things in your hand to control how memory will behave.

11y agoHN ↗

But we're not talking about maximum throughput above all else. We're talking about the differing throughputs of high-level languages, which Golang is, but C++ is obviously not.

11y agoHN ↗

What makes InfraRuby better than Crystal (or vis-versa)?

11y agoHN ↗

Cool. I assume that makes interop with Ruby easier?

11y agoHN ↗

Yes! Ruby language features supported by InfraRuby have the same behavior as Ruby interpreters.

InfraRuby projects are created with rake tasks to run your code with either the InfraRuby runtime or Ruby interpreters. You could implement a file format or protocol in InfraRuby, and use that code in a Ruby project too. You could use Ruby interpreters for development and the InfraRuby runtime for production.

And if you decide that InfraRuby is not for you then you can take your code with you!

11y agoHN ↗

Nim perhaps? There's very little boilerplate but it is statically typed. Someone mentioned infra-ruby, which is worth checking out. Lastly, crystal-lang is pretty awesome. You could use this opportunity to create what is missing in the ecosystem and make it open source. This will encourage others to chime in, accelerating development for everyone.

11y agoHN ↗

You definitely should at least look into Julia. I would say it is more elegant than any other language I've seen while being faster than pretty much anything except for straight C / C++ / D / Rust (native systems languages).

It should be easy to beat Go in performance and match or exceed Ruby in elegance and simplicity.

It's Achilles heel(s) right now though are multi-threading and JIT compilation times (which should be alleviated once caching is implemented).

It sounds really close to what you want. Its package management is even based off of github. Want to get a package to write out image files? Pkg.add("Image"). Update all your libraries? Pkg.update()

Over the next 5 years I think it might eat into a lot of the territory of scripting languages.

Also the generic programming focus means you should never have to write anything more than a new comparison for your new types.

11y agoHN ↗

Julia's 1-based array indexing is counter-intuitive and a turn off.

11y agoHN ↗

I've seen people say this but really 1 based indexing should be extremely trivial to all the other complexities of writing a worthwhile program for anything more than a one liner.

11y agoHN ↗

Wow, are people really that offended by someone suggesting Julia? It is a bullseye answer to the question.

11y agoHN ↗

From your description, it sounds like Crystal is exactly what you want. But yeah, I don't know if it has a library ecosystem yet, or anything like that.

11y agoHN ↗

I think Crystal could fit this bill, yes: it has Ruby syntax and the concurrency model will be something like Go (spawn and channels, but we are still working on this). And the code ends up without much repetition, as you have generics, macros, very few type annotations, and things like sort, map, select, inject, etc.

The community likes to call a library as "shard", so I think we'll eventually end up using that name :-)

You can see a list of shards here: http://crystalshards.herokuapp.com/

For now that just lists GitHub repositories. Right now there's a very basic package manager that fetches repositories from GitHub, but it's too basic. Someone is working on a much better package manager ( https://github.com/ysbaddaden/shards ) that we'll probably incorporate in the future.

11y agoHN ↗

Wow! Ruby was the first programming language I learnt and I still love it. Crystal is going to add features I've always missed: static typing and compilation. Thank you guys, it will be awesome!

11y agoHN ↗

He probably meant the results of compilation: superfast binaries ;)

11y agoHN ↗

In that case, what he missed was AOT optimization :)

Many Lisps have had compilation to binaries. But having a binary doesn't imply being faster than an interpreted language.

Theoretically, a JIT optimization can end up with faster code than an AOT optimizer can produce, thanks to runtime profiling.

In fact, I'd be interested in seeing benchmarks, which Crystal is probably not ready to share, but may have conducted: https://github.com/manastech/crystal/blob/master/samples/fan...

Edit: this[0] seems to indicate that the optimizations are in the same ballpark as Go and D, which is really impressive.

[0]: https://github.com/kostya/benchmarks

11y agoHN ↗

Edit: this[0] seems to indicate that the optimizations are in the same ballpark as Go and D, which is really impressive.

not sure about D, but Go is known to have very little in the way of optimisation, that's a big factor in its speedy compilation.

11y agoHN ↗

More like Crystal could become to Ruby what RPython is to python

11y agoHN ↗

That makes no sense. RPython is not a general-purpose development language, it's a strict subset of Python for writing VMs. From what I understand Crystal aims to be a general-purpose statically-typed native-compiled language with a syntax similar to Ruby in much the way Nim aims to be a general-purpose statically-typed native-compiled language with a syntax similar to Python.

Neither Nim nor Crystal exist solely to write VMs and auto-implement JITs, and programs in neither is expected to run unmodified on a VM for the inspiration language.

11y agoHN ↗

Now someone convince Matz to reimplement Ruby in crystal, add optional typing to CRuby, and you have won big time.

11y agoHN ↗

Except for the parts where you've lost spent years reimplementing an implementation-defined language and you've lost the existing C API so most any C extension which doesn't use Fiddle (or similar FFI mechanisms) is incompatible.

11y agoHN ↗

Elegant to program with. Not write a parser for.

11y agoHN ↗

An ambiguous syntax is not elegant to program with. Is this a variable reference? Is it a method call? I dunno!

11y agoHN ↗

Wait - are we talking C/C++ here? where a function call, a variable declaration, an expression cast all look identical?

11y agoHN ↗

You are probably talking about C++, not C, unless those look identical to you:

    foo();
    int foo;
    (int)foo;
11y agoHN ↗

the cast can equally be int(foo); or (int)(foo); The parenthesis are allowed and optional. The method can be declared (int) foo(int(x), int(y)); which can also be an invocation. etc.

11y agoHN ↗

I wouldn't really say they are optional. The two do different things. (int) foo (or (int)(foo)) is a c-style cast. int(foo) isn't a cast. Instead it invokes the int constructor with the argument foo.

11y agoHN ↗

int(foo) and (int) foo(int(x), int(y)) are not legal syntax in C. However, they are in C++ [0], which is my whole point.

[0] your method declaration has a small problem, it apparently cannot have parenthesis around returned type.

11y agoHN ↗

Is this a variable reference? Is it a method call? I dunno!

Neither. It's a message sent to an object. The object decides how to respond to that message.

Caring about the implementation of the message receiver breaks information hiding. If you have to distinguish between .message and .message(), you already know more than you need to know to interact with that object.

Every language is confusing until you understand the mental model (even if you disagree with the utility of that model). If you come to Ruby thinking in Java/C++/C terms, you'll be unhappy, because the mental model is very different.

For Ruby, the book to read is The Well-Grounded Rubyist, which makes the language quite obvious.

11y agoHN ↗

I've been programming in Ruby for years, and the inherent ambiguity proven by the complexity of the parser isn't made better by telling me I'm not thinking right.

Variables and methods are treated differently. I can't pass arguments to a variable, I can't even write foo(), but if it were a method I could. I can't treat variables like any other object because they have different rules applied.

11y agoHN ↗

I can't pass arguments to a variable

I'm confused -- if you're knowingly invoking a method, why are you unsure whether it's a method or a variable?

I programmed for years in Ruby too. I just accepted a whole bunch of stuff as mysterious and shrugged. It didn't mean the language was ambiguous, I just didn't have any idea of what the actual model was.

11y agoHN ↗

Actually, you're wrong. The syntactic ambiguity between local variable references and implicit self method calls us not accurately resolved by thinking of it as message sends because it's only a message sends if it is a self method call, not it is a local variable reference. This is a real syntactic ambiguity in Ruby.

11y agoHN ↗

The book I pointed to also discusses the concept of self. It's not really that ambiguous.

11y agoHN ↗

Boring arm chair language criticism. I have programmed professionally in Ruby for 5 years and it has never been a problem. You may as well complain that the ability to import an identifier leads to confusion about which identifier from which namespace you are calling when you reference one. I have 99 gripes about Ruby, but this ain't one.

11y agoHN ↗

I just started working with Ruby on Rails a few weeks ago and I think the so-called "elegant" syntax is the reason why I went with it. That and the fact that Rails is a joy to work with.

11y agoHN ↗

Have you written Ruby often and still felt like this? I'm asking because I'm newer to Ruby and this does come up a fair bit, but I was assuming that with time the ambiguity would disappear.

11y agoHN ↗

I have written Ruby for many years now. The ambiguity never disappears, and the delight at how "expressive" the syntax is quickly turns to loathing. YMMV, of course.

11y agoHN ↗

Among people who like Ruby, this is seen as a feature, not a bug. It makes refactorings easier when you change something from a local variable to a method call and don't have to search for all the places you would have to add parentheses in a language like Java. Granted, you could argue that your IDE should take care of that for you with automatic refactorings, but to a Rubyist this is a crutch and also something that forces her to use a particular IDE instead of her favorite editor.

11y agoHN ↗

I don't think it matters much that it's hard to parse: very few are going to write a parser for that language compared to the ones that are going to write programs in the language. It goes the same for everything else in a language: prefer a fast compiler rather than an elegant (compiler) but slow one.

Ruby's syntax is very short and concise if you compare it to other languages. To know if "x" is a variable or a method you just need to see if "x" was assigned a value in the method, that's all.

11y agoHN ↗

Yeah, the Ruby-like syntax of Elixir made me stop looking into it more. I remember reading an article (or was that a chapter in a book), which basically said something like "ok, these parentheses are optional, but only for this case. For other cases, parentheses are required." Which is a shame, since the tooling built for it is the most amazing I've ever seen.

I know syntax shouldn't matter, but personally I think consistency is very important.

11y agoHN ↗

I think syntax matters a lot - if it incurs development costs through inconsistencies, for example.

I'm going to a meetup in a couple of days where Elixir and Phoenix will be presented, I think I'll keep your comment in mind and maybe ask about inconsistencies in the syntax, as I wasn't aware of this. What I know of the syntax relation to Ruby is that it is only superficial - the semantics are very different.

11y agoHN ↗

Lots of people find it elegant in actual use because of the same features that make it hard to parse; this is much the same thing that is true of natural languages.

11y agoHN ↗

I don't understand what parsing has to do with elegance in this context. There are some 'best practices' but I don't see the different syntax support as a bug. I feel that it's a trademark of a modern language.

Let's take rust as a counter example. IMHO this is ugly:

    fn foo() {...}

To make it elegant I should be able to remove the parenthesis because there's no argument inside:

    fn foo {...}

But this will come up with an error: "expected one of `(` or `<`, found `{` ...", which is fine. It's easy to parse, but it is not elegant IMHO.

Elegance != easy parsing.

11y agoHN ↗

It's not just about making parsing easy (which is usually motivated by the promise of better tooling), it's about making costs explicit. `foo.bar` with no trailing parentheses is always a field access in Rust, and a field access is basically the fastest thing one can do: take a known offset from a memory location. In comparison, `foo.bar()` has a relatively tremendous cost, depending on various factors. It's for this same reason that Rust requires you to write `(foo.bar)()` if you want to call a function stored in a struct.

This isn't a tradeoff worth making in all languages. But it is a tradeoff, and not a rejection of elegance.

11y agoHN ↗

`foo.bar` is always a call in Crystal. If `bar` is just an accessor, then the method will be inlined and there is no extra cost compared to make an explicit field access.

11y agoHN ↗

Try to build a class with it and you will understand.

11y agoHN ↗

I've built many a class in Ruby.

I see.....

Anyway, this is elegant to me:

sum ||= (1..100000).to_a.inject(:+)

Maybe you have a different aesthetical sense than the rest of us. That's perfectly fine, we don't have to like the same things.

But since you questioned, now I'm curious and I would like to ask you: what language in your opinion has the most elegant syntax? Also if you have 2 extra minutes free, would you mind to code that same one-liner above in that language so we could compare the two?

11y agoHN ↗

In Python it's x = sum(range(100000)). You might say that's cheating because there is a specific sum function, but having smart builtins covering common programming cases is exactly what an elegant language offers.

11y agoHN ↗

I wouldn't say it's cheating but without using that function maybe we could see more of the language syntax itself. That way we are just seeing method calling.

11y agoHN ↗

Having migrated to Clojure I prefer:

(reduce + (range 1 100001))

but you can make yours a little nicer by relying on a Range being enumerable, hence:

(1..100000).inject(:+)

11y agoHN ↗

I prefer J:

  +/>:i.100001

You could takeout the >: but then the range is 0 indexed and not 1-based:

  +/i.100001
11y agoHN ↗

Now that is incredibly terse. "/" is the J reducing function? Or is it not comparable?

11y agoHN ↗

It is J's 'insert' function. It inserts the verb on the left between the items on the right. I think I've see it called 'apply' for J too, and I think 'reduce' as defined in other functional languages would be correct; it's just that J calls it 'insert'.

11y agoHN ↗

what language in your opinion has the most elegant syntax?

The Lisp family, Scheme in particular.

(define sum (reduce + 0 (iota 100000 1)))

11y agoHN ↗

Now. I just can't wait till Rails or something similar gets ported to Crystal.

11y agoHN ↗

I'm a go user right now, but I really want to ditch it because I'm in total disarray with the way the go language is managed and the deafness of the go team. Been looking at D,Nim and Crystal.

- D is neat but I'm not interested at all in unsafe stuffs and don't want to have to debug programs or 3rd party libs that relies on that, I want a safe language.

- Nim looks really good, although some features like (foo_bar = FooBar ) are just disgusting

- While Crystal is new and libs is non existent, it feels like a good candidate for the long run. I hope it will have the same concurrency capabilities as go. Good luck with the language.

11y agoHN ↗

D has a memory safe subset (@safe). You could argue that Rust 3rd party libs rely on unsafe blocks.

11y agoHN ↗

The person you're replying to wasn't even talking about Rust. You'd also be vastly overestimating the amount of Rust libs that need unsafe code. For example, Rust's most mature web framework, the one powering crates.io, doesn't use unsafe code at all: https://github.com/iron/iron

11y agoHN ↗

Ah, so it does. :) Fortunately Conduit doesn't use any unsafe either.

11y agoHN ↗

Have you looked at the new generation languages on the JVM such as Kotlin or Ceylon?

11y agoHN ↗

I am assuming your requirements are statically typed with GC, no separate VM, and lightweight runtime. One language I have been watching with a keen eye lately is Pony[1] though the docs are not quite complete.

1 - http://ponylang.org/

11y agoHN ↗

If you don't like unsafe stuff perhaps you should be a bit wary about Nim as well. The attitude seems to be that unsafe things aren't that big of a deal as long as you decent tools to debug them.

11y agoHN ↗

Well if you have decent tools to debug them, why should he be wary?

11y agoHN ↗

Quick fix, in nim:

  foo_bar = fooBar
  Foo_bar = FooBar
  foo_bar != FooBar

That is, it's case sensitive with the first character of the identity.

I've mentioned elsewhere that this one bugged me at first, but in practice it simply means that you get to use the language as if your preferred convention (whether snake or camel) is the "official" one- even when calling other libraries. Nothing more and nothing less. Cases where you need both styles but need them to be different things tend to be non-existent / code-smell.

11y agoHN ↗

Wow, I started designing a language called Crystal in the 90s, which a) looks similar to this, and b) I'm pretty sure I even bought the domain crystal-lang.org at one point, but I never did anything with it! I had a very similar logo too :)

11y agoHN ↗

Please don't be inspired by Ruby, please... aaaah god dammit!