The Crystal Programming Language
crystal-lang.org
crystal-lang.org
http://programmingisterrible.com/post/42432568185/how-to-par...
foo();
int foo;
(int)foo;[0] your method declaration has a small problem, it apparently cannot have parenthesis around returned type.
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.
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.
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.
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.
I know syntax shouldn't matter, but personally I think consistency is very important.
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.
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.
This isn't a tradeoff worth making in all languages. But it is a tradeoff, and not a rejection of elegance.
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?
(reduce + (range 1 100001))
but you can make yours a little nicer by relying on a Range being enumerable, hence:
(1..100000).inject(:+)
+/>:i.100001
You could takeout the >: but then the range is 0 indexed and not 1-based: +/i.100001The Lisp family, Scheme in particular.
(define sum (reduce + 0 (iota 100000 1)))
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.
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. :)
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.
Does this mean that if a library doesn't have complete test coverage compile-time errors could be discovered only by clients?
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.
It's the same with Crystal. Their generics require type annotations.
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...
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!
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.
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.
Have you tried InfraRuby? InfraRuby is a compiler and runtime for statically typed Ruby: http://infraruby.com/blog/why-infraruby
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!
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.
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.
Or this discussion (which is two months old, so unsure what exactly has changed since. Looking at commits, not much?) http://www.reddit.com/r/programming/comments/30y9mk/go_vs_ru...
Or this one, though it goes off track a bit: http://www.reddit.com/r/rust/comments/38s1n3/why_is_rust_muc...
- 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.
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.
The Null pointer analysis is great, hope this stuff pollinates some of the more mainstream languages
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.
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.
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].
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.
Unions seem like more of a language-level feature, so I'm not sure you could abstract over them in the same way.
Nonetheless, I am thrilled that we are seeing more and more languages that don't have implicit nullability on any type.
With something that leaves "Nullpointer analysis" obsolete/useless.
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.
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.
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.
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.
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).
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.
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! :)
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.
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.
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.
C has -O3, why isn't it the default? Because it takes a lot more time to compile. So I think no optimizations by default is the best choice. And I think Rust should do the same, you will be compiling more things in non-release mode than in release mode.
It may seem obvious to you and I that compilers have optimization levels, but consider how many people for whom Java, with no optimization level flags, is their only exposure to a manually-invoked compiler.
There's this tag: https://github.com/manastech/crystal/releases/tag/ruby
You can read about the bootstrapping moment here: http://crystal-lang.org/2013/11/14/good-bye-ruby-thursday.ht...
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.
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.
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.
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.
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.
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.
See Wiki 1. to 9. If you enjoy your first try for Crystal Language, it's nice.
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.
[1] https://www.omniref.com/blog/blog/2014/11/17/matz-at-rubycon...