Has anyone used both in any serious project, and if so can you compare? I've spent a tiny amount of time with Nim but found documentation, training videos, books etc extremely thin on the ground. Lots of Nim projects on github are ancient and haven't been updated in years. Chat GPT never gave me working code. How is Crystal in this regard? How would you compare their std libraries and available modules?
I am asking this because in that case, can’t I run it myself and share the code with the precompiled parts so others don’t have to experience the slow start?
So it looks like you could easily check that in with your gem.
I don't actually see any object code in the directory but this is a new project so they may not cache that yet but I don't see why they couldn't in future. e.g. as a build step when installing a gem.
This seems to be a fantastic bit of work so far with a lot of potential for the future.
Crystalruby ia really interesting though. This has got me wondering:
1. How this compares to Sorbet Compiler in terms of performance?
2. If you could use Sorbet or RBS type annotations to drive crystalruby?
3. How would YJIT perform if it was fed with type annotations?
4. Could crystalruby type signatures be used by Sorbet-lsp, ruby-lsp, etc in to improve developer experience?
I really wish we had a single standard for inline types in ruby. I wish that was an inline RBS type annotation but I guess I’d take anything that works.
Coming from both python and statically-typed languages, your description 100% squares up with every experience I've had with ruby
Folks are free to throw stones at the Spring Framework similarly, it's not out of bounds, but ruby seems to embrace the monkeypatch
How recent were your experiences with this? In recent years I've seen monkeypatching treated as more of an antipattern. If you are on a project where people are doing that without very good reasons, that says more about the team than the language.
How much magic is really going on, especially compared to for example JPA/Hiberbate, or any of the aspect orient projects?
Metaprogramming like looping over an array to dynamic create methods is far less common. It definitely makes me cranky when I can’t find a method definition. ORMs are one of a few exceptions where the utility justifies it.
> type annotations are rarely necessary, thanks to powerful type inference. This keeps the code clean and feels like a dynamic language.[1]
I prefer Sorbet[2] for Ruby, which is pretty fast and a reasonable compromise. People complain about the verbosity, but you can't have both speed and type inference. Pick one.
Some of the verbosity in Sorbet, however, comes as an indirect result of an underdeveloped Generic type system (the rest of the parts work great though). Some of the verbosity comes from lack of fluency with the tool, which gets better with practice, and doing research. The bottom line is I often think its a skill issue when people say it leads to code that is too verbose.
1. https://crystal-lang.org/#type_system 2. https://sorbet.org/
> > type annotations are rarely necessary, thanks to powerful type inference. This keeps the code clean and feels like a dynamic language.[1]
There are fast type inference implementations. For example, the OCaml compiler is pretty fast
No reason Crystal couldn't improve this part of compile time in future
My experience with F# (which was way back in version 2.0, but based on OCaml which does type inference very similarly), was that the inference would become slower as the size of the project increased, eventually taking multiple seconds. I eventually adopted a habit of adding type annotations to commonly-used functions because it mitigated the problem. These projects were not huge by any means (5-10K LOC).
There is probably a good compromise to be found here. It should be possible for a tool to suggest areas to add annotations where it would do the most good for performance. But maybe those are non issues with modern implementations.
Inference is a big part of what increases Kotlin ergo over Java and while it does compile slower you are going from a very fast base so it doesn't feel slow relative to other options.
With OCaml you can pick two :)
let's say there's a generic data transformation in a method which could be extracted to a private helper method. Sorbet didn't have any issue figuring types of the result while those lines were in the parent method. But when those three lines are extracted, sorbet now requires you to write a generic signature for that method, otherwise type checking is not working anymore. Generic signatures are even more verbose and hard to write than for methods with specific types.
So, a kneejerk reaction would be just not extract those, because it becomes too much work, and readability is not improved anymore, because signature for three lines of code is taking five lines. And that's until someone "clever" will add a rule to rubocop limiting all methods to a specific amount of lines indiscriminately, so I'd spend additional work time imagining that person being punched in a face.
In theory something like that could be solved by marking helper methods as inlined, basically promising that you're not going to use them in subclasses or whatever and allowing the same inference to work inside. But "sorbet is feature complete".
I hear you on the verbosity of generic type declarations. It's something that I pushed hard for us to improve in the early phases of the project, but I was outvoted by other members of my team. But... at this point those members have all left the team and in the meantime we've heart actual users complain about the verbosity (not our own hypothetical "what if" complaints in the design phase) so I'm optimistic we'll be able to reduce generic type verbosity in the future. For example, a while back I did a prototype/experiment to drastically drop the verbosity of generic type annotations[1]. It's definitely on our radar.
Happy to chat more either on Sorbet's issue tracker or Slack group.
That said, in general, I often end up rewriting things a different way if something doesn't work, and the vast majority of the time that is good enough for me. But it takes time to find the one solution that doesn't create unacceptable bloat or complexity, and even then it is not as simple as I think it could be if there was another layer of polish to the Sorbet ergonomics (don't get me wrong, it's already in a good spot considering where Ruby was before that). Though someone actually complained to my boss that I spend too much time getting things working with Sorbet instead of "just shipping it", even though one of my main areas of responsibility is with architecture.
This is where having redundant features (multiple ways to accomplish the same thing) could be of some benefit, but I'll have to think about it some more with specifics. And I understand from looking through the repository that it's not an easy task to add features at all, so I'm thankful enough for what it is there as it has made my job much easier overall.
The main thing I struggle with regarding generics is the scenario where you must have separate type_template and type_member for class methods vs instance methods, and there are many use cases where these values are equal. But there is no way to tell sorbet that they are equal, requiring casts which makes the code less readable and often frustrating to write.
I also think that on a broader level, based on my searches, that the ruby community criticizes the idea of Sorbet for the reasons I mentioned in my previous comment. Adding more polish to the ergonomics, even when difficult or seemingly unreasonable, could do more for the branding of the project, which would lead to more community adoption, which would lead to better "just-works" type support with many common ruby libraries that are currently untyped. That could be a massive improvement to the status quo IMO. But it's hard to say because it could also end up not really moving the needle...
Appreciate all the work you've done! C++ isn't my strong point but someday I hope to find time to do a deep dive on the internals of the project.
Having to wait longer to see the results of one's work results in loss of focus and productivity.
Author of the gem here. Appreciate the attention!
I hacked this together last weekend to scratch an itch and have some fun. This got a lot more attention than I was expecting so early on.
I've had to scramble a bit to start hammering this into something that's less prototype, more legitimate project. I hope it's now close to a cohesive enough state that someone trying it out will have a relatively smooth experience.
Given the interest, I definitely plan to sink a bit more dedicated time into this project, to tick off a few key missing features and add some polish. It would be great to see it grow into something that can be useful to more than just me. Seems like there's definitely some shared desire for this type of tool.
It probably goes without saying that you probably shouldn't convert large amounts of mission critical code to depend on this (for now).
It's still early days and the API hasn't been "crystallized" yet...
But this does seem to be the first attempt at interop between crystal and Ruby, which is notable because crystal is effectively a typesafe Ruby.
This seems way more verbose than the project in the post.
rubocop --show-cops
I can see that the NumericLiterals cop that's bothering you supports autocorrect so running: rubocop -A <filename>
should fix the problem for you.As a latecomer, C23 went with ' just like C++ did almost 10 years earlier with C++14.
One feature it seems to lack is the ability to crystalize instance methods?