Ruby 3.0.0 RC1
ruby-lang.org
ruby-lang.org
The concurrency stuff is cool but I can't really comment on it, my rule is "if you're going to even think about threads, just use Java or go" since concurrency is so nice there.
I really wish ruby had won over python as the "general purpose scripting language", I like it better. For example, I wish all the "big data" tooling was written in Ruby. But python is just the lingua franca. PySpark, Pandas, Airflow, etc are all python. I'm not really going anywhere with this, just lamenting I don't get to do more Ruby in my day job. I recently switched to writing python at home just because my productivity is so much better because I've memorized the standard library better from using it for years at work.
I much prefer Python for this. As an occasional Python/Ruby programmer I find it's just so much easier to work with. Ruby has a lot of features, which is part of its appeal, but that also means a lot of remember and ... a lot to forget. If you're not steeped in it, then that makes things quite a bit harder.
I programmed some Ruby a few weeks ago; it had been quite a while since I last did any Ruby, and ended up having to look up the syntax for blocks. This is really basic stuff, but I had written str.gsub!('pat', {|m| ... }), and the correct syntax is of course str.sub!('pat') { |m| ... }. Kinda silly I guess and a bit of a "oh, du'h" moment; I spent two years with Ruby as my day job, but that was also five years ago, and I think these kind of mistakes aren't that unusual if you're not programming Ruby too often.
And then there are caveats like using "return" where you should have used "next", and quite a few other things.
Don't get me wrong, I like Ruby, and there's also a bunch of things I don't like about Python. But I can definitely understand why people settled on Python. I think Matz once said "Ruby is 80% Perl and 20% Python"; I feel that with a slightly lower Perl-to-Python-ratio Ruby would have been better.
---
There may also have been implementation considerations by the way. I believe that historically at least, Ruby has been a lot slower than Python, although I think the differences today aren't very large. And a lot of these tools are written as native extensions, which Python seems to support a bit better.
I think people settled on python because for a spell it was really hard to install ruby (1.6-1.8), it got worse around 2.0, (by the time python also ran into these issues, it had way more momentum) and:
- schools (high schools, colleges) were teaching python, because "it forced students into code indentation discipline"
- linux distros shipped with python because core features were being written in python
Actually, Ruby's selling point, during the golden times (I guess it was around the mid-2000s), was actually the simplicity and human friendliness of it.
I guess (but it's hard to prove anything in this sense) that Python came out earlier as human-friendly scripting language (1991 vs. 1995), and Ruby didn't provide any killer feature - until Rails came out, but that caused Ruby to "be" Rails, while Python had other areas of application, for example operating system tools.
And yes, I've also forgotten plenty about Python; but in Ruby there's just so much more to forget. I've retained much more of my Python knowledge.
On a practical note, Ruby's heavy use of reflection, inheritance and method_missing style dispatch currently makes performance for big data tasks less than ideal. Python is less expressive but its "one true way" makes things like vectorization and type specialization easier for data tasks.
Sometimes you really just need a better Fortran.
While these can eventually come in handy sometimes, they are by and large seldom used, but Rails heavily leverages such patterns for flashy yet ultimately questionable magic.
As a long time Rubyist, I believe Rails, for all its innovation, has been warping the view of what Ruby is, and when and how to use its features responsibly.
I don't think they are seldom used in practice - I think large parts of the Ruby ecosystem use them pervasively, even if you disregard Rails.
But even if we don't agree on that - it's missing the point. The point is you pay for much of Ruby's metaprogramming, even if you aren't using it. So not using it doesn't help you.
A concrete example is that you pay for `Kernel#binding` every single time you finish using a local variable but Ruby retains it for the binding, even if you never ever call `Kernel#binding`. Even JITs cannot remove this cost.
I don't think there has been any meaningful attempt at assessing the performance of Python vs Ruby.
If we consider the benchmarks game, their performance is comparable.
I assume that PyPy is faster, but whether to consider it representative of Python's general performance is arguable.
The dynamic inheritance part of Ruby is still core even in a non-Rails ecosystem. That does make optimizing performance more difficult.
But on a much simpler level, a lot of Pythonic data code is just functions + values, without them being complected in an object. That makes a lot of stuff like wrapping numerical libraries in C very easy in Python. I think you'd still have a bit of a culture shock writing procedural, return-by-value code in Ruby. That's pretty normal in Python and I think one of the (many) reasons it got picked up by STEM disciplines in academia. Looks over shoulder at awful code in Numerical Recipes in Fortran book.
Different strokes for different folks and all that, I just think there are probably constraints (and cultural values) that Python has that make it more suited to data problems than Ruby.
One of Python's strengths in this space is that it's a procedural language with just enough in the way of object-oriented facilities. That makes it possible to write reasonably object-oriented libraries with easy-to-learn interfaces, while still sticking to procedural idioms for the actual data hacking.
Which, in turn, means makes it a comfortable environment and interfacing mechanism for both engineers and analysts.
That said, sometimes I look at tools like https://moosetechnology.org and desperately wish I could be using something like that at work.
Python has the full program with multiple inheritance, metaclasses, abstract classes, slots, attributes, properties and decorators.
One can go wizard level with Python's OOP facilities.
Another thing is that outside of scientific data applications, ordinary data processing apps at scale have been map-reduce for a long time. That's an inherently functional paradigm that's closer to Python.
Having parallelized heavily OO Ruby code across machines by unrolling it into functions that could be sent data payloads... that's not a fun thing to sell in an OO culture. In Python you're kind of already writing code that way, so increasing performance by splitting the data onto different machines is simple.
I don't like it when people break out their monocle and pipe and complain about this or that being true functional :-). I really do just mean "procedural functions + return by value instead of mutation" here.
It is completely normal to have a python script that is just ordinary functions, without state being enclosed in a class and accessed via `self`. This "My First Program" behavior scales extremely well, both from a parallelism perspective, as well from a complexity perspective. OO features can be applied iteratively on top of that.
With Ruby--syntax aside--it is just very rare to see people only write functions or private_class_methods. The "Everything is a Class" mantra is very strong, and there's lots of rich behavior in Ruby attached onto what would be considered primitives or basic structs in Python.
With data science specifically, this natural way of pushing simple procedures, primitives, structs from Python down into C has lots of performance benefits. Because those functions are "flat", you don't have to traverse the call stack routinely and lose performance there.
As for Python, Guido might not like it, but Python has enough features to do FP, in spite of crippled lambdas.
All you really have to do to translate map and reduce into an OO paradigm is have the map and reduce operations take objects that implement specific interfaces instead of functions that have a certain signature.
Yeah, but that required everybody learning a different style of programming that wasn't OO ;-).
The problem is for "map", you have to get rid of shared state and the interleaving of state and time. Interleaving state and time is basically the only thing objects do beyond providing a namespace and polymorphism. There's also an inherent 1-ary behavior in most OO code. If you've got a complicated object graph, making something a bulk N-ary operation is non-trivial. A lot of ORMs have this problem when trying to optimize queries. When you're passing a vector of "state" to a function, it's much easier to make things bulk. (Anyone who's had to make legacy code provide a bulk API with an efficient DB insert operation knows what I'm talking about).
What you usually get in Ruby is a bunch of code that's like Duck.quack() and a complex set of things below that interface. What you need for data processing is something like quack(ducks[]). The way of parallelizing real-deal objects keeping them objects is with actors, and then you need to worry about distributed state a lot more.
Anyhoo, that's a long way of saying that the much-derided procedural or functional style of Python avoids this problem from the get-go. You can just slap the "map" step onto a stream processing system without cutting your graph into pieces that don't share state.
map-reduce and functions do not require coordination until reduction. map doesn't require a serialized mailbox (like actors), or worrying about ordering in time. Smalltalk requires a consistent state and the location of where to send messages is important. Distributed map is stateless. All versions of OOP concern themselves with state encapsulation, whereas map is state elimination. map is functional.
Python has just as much access to reflection, it has more powerful and arguably easier to shoot yourself in the foot inheritance (python has multiple inheritance while ruby only has mixins). Python also has method_missing in __getattr__. Arguably python tends to use these less often then ruby but I've definitely used both reflection and __getattr__. I've also once had to debug a really hairy multiple inheritance issue(in a test framework that used base classes to test different hardware features where you might want to have both you'd need to use multiple inheritance and then make sure they were initialized in the right order, multiple inheritance can get really annoying)
FWIW the things you mention are available in Python. There are things Python doesn't have like singleton objects but I'd say those are syntactic sugar & conveniences (not unlike classes existing from the start of the `class` statement) rather than a lower level of expressivity.
Python absolutely does have extensive reflection, inheritance, and "method_missing-style dispatch" (cf `__getattribute__` and `__getattr__`), and so has to accommodate the possibility of them being used… unless it doesn't want to which specialised Ruby implementations (à la numba and friends) could certainly do as well.
I still use Python daily, since its so ubiquitous, I just find myself having a difficult time using it for anything remotely complicated without a lot of extra research.
I could see reasons for preferring Java over Ruby but concurrency isn't one of them considering that Concurrent-Ruby running on JRuby gives you access to all of Java's concurrency tooling.[1]
I think parallelism is just not part of the Ruby ecosystem anyway, at least in the present (possibly in the future, who knows).
Rails released officially support for multithreading long ago, but it was broken. And there is a Ruby global lock, anyway (3.0 will have true parallelism, although in alpha).
Concurrency in Java is not very great although there are some improvements in these years. The actor model (Ruby 3 / Erlang) is on par with CSP model (Go) though they have different trade-off and scenarios.
Just as a small example, Edit: .methods, "the Ruby equivalent of 'dir'" doesn't present the results as a sorted list
Also the optionality of parentheses sounds like a good idea but it actually makes things more confusing and less parseable
Dir[path].sort
Ruby lacks polish in certain areas but this is not one. A d I would argue Python can be pretty tough in places too.I fixed it in my original comment
This seems like an odd complaint: Both languages use hash maps to look up methods/attributes, which aren't inherently sorted. Looking at the implementation `dir` in python does an explicit sort, which just seems like arbitrary behaviour.
Especially when dir() is more used in interactive applications (and sure, sometimes programatically like in plugin architectures, etc)
Not to say I’m not grateful! I’m mostly an Elixir dev, but since the Erlang Runtime System sucks at POSIX (bad at pipelines, bad at exit codes, bad at trapping signals, etc.) I personally switch to Ruby when I need a glue language. It’s nice to see the available paradigms in these (actually very different) languages “coming together”, in a way where I can spend less time mentally mode-switching, and just keep thinking in (functional, pattern-matching, concurrent-actor) terms.
Just think how pleasant it will be to write a “show a spinner as you poll a resource in the background” code in Ruby, now that we’ve got actors. :)
One has to wonder.
I don't think I'll go back. I found that I was coding almost exclusively in a functional style, in Ruby (because it was easier code to test and it seemed to produce less bugs/fewer mental-model problems), then realized I was still missing out on the guarantees that Elixir gives you, then I thought, what's the point in staying when I could get guarantees that eliminated entire classes of bugs for free?
Having done Elixir for a few years now, I RARELY have to go into "debugging mode", I've never even had to fire up Elixir's version of "pry", and my tests have caught all but a few bugs. All my logic lives in sensible locations. It's pretty nice! At least for web dev.
Having spent the last year with Elixir in production, I don't plan to go back. Things like Ecto, ExUnit, error handling using "with", Phoenix Presence, domain modeling using Phoenix Contexts, Absinthe for GraphQL have made programming a joy.
That, plus Elixir was literally 5 to 10x faster at a sampling of tasks I threw at both (but this was a few years ago)
I would love love love to see two people approach the same exact problem with OOP and FP and see the rewards of both.
However I really love pattern matching from Elixir real game changer of a idea, excited to see it come to Java and Ruby soon and the lightweight threading models come to both of those languages also.
I always preferred Brian Gotez's argument that you should be functional within objects. https://www.youtube.com/watch?v=8GWZE2Y2O9E
No. That's the reason that we have multiple different tools at our disposal. :)
My rule of thumb has always been to use the tool that I, myself, find more effective—not what others say would be the most effective in an ideal world where I know everything about every programming language in existence. That means that I've written a handful of shell-scripts in PHP, some CLI-tools in Ruby (that would probably run a lot faster in Go), and a couple of "API"s in Bash.
Reduce (inject, etc.) are tricky to understand at first but once you do, you realize the power in it. Granted, it’s harder to understand than loops, but loops with iteration and mutability get you into a lot more trouble.
It sounds like you’re simply not over the learning curve yet.
Enumerable#each is quite deeply equivalent to a for…in loop.
But no one uses for loops in Ruby because not only are they basically an alternate syntax for Enumerable#each, but they became non-idiomatic because in earlier versions (through at least 1.8.x, IIRC; I think this may have been resolved by the switch to YARV in 1.9) “for” had some implementation inefficiencies which made it notably slower than #each.
It…depends what for.
There are things loops are more clear representations of intent for, there are things map is a more clear representation of intent for, and there are things reduce is a more clear representation of intent for.
Also any idea on how StimulusReflex / new Hotwire compares to Phoenix LiveView if you have had a chance to look at it yet?
Ruby will never be Elixir or Erlang but if you can improve an existing thing why wouldn't you? Adding useful features from Elixir/Erlang to Ruby seems worthwhile, even if it doesn't get you all the benefits.
There will likely to be much work that won't be rewritten from scratch that would benefit and money to be made doing so.
I see only a slight speedup of the 3.0 (JIT) compared to 2.7 (less than 15%).
See here for more information about the benchmark suite: https://stefan-marr.de/papers/dls-marr-et-al-cross-language-... and https://github.com/smarr/are-we-fast-yet. In contrast to other benchmarks the focus is on ideomatic language use and representative results (i.e. not just some random micro benchmarks) suited for inter-language comparisons.
Ruby 3.0 RC1 without JIT is factor 1.2 faster than Ruby 2.1.0; Ruby 3.0 with JIT is factor 1.4 faster than Ruby 2.1.0. So definitely less than factor 3.
Unfortunately the benchmark didn't work with Ruby 2.0.
Smalltalk/SOM, JavaScript and Lua share the "extreme dynamic nature" but still have much faster JIT implementations. From my reports you can see that V8 (Node) is about ten times and LuaJIT about five times faster than MRI Ruby 3 JIT. Here are some other performance results using the same benchmark suite: https://github.com/smarr/are-we-fast-yet/blob/master/docs/pe.... Also TruffleRuby is reported to perform much better than MRI Ruby JIT (see e.g. https://pragtob.wordpress.com/2020/08/24/the-great-rubykon-b...).
http://mruby.org/docs/articles/executing-ruby-code-with-mrub...
Maybe it was something with the corner of the Ruby world I fell into, but the syntax sugar options and “cleverness” that enabled was maddening to deal with, and permeated that crowd.
Code bases with mixed styles are a thing in any language, but the Ruby-ists around me found a way to make one project look like half a dozen languages were involved.
Insert Confused Jackie Chan meme.
Rails, if done correctly, is a remarkable tool to allow developers to focus on the business logic and UI of their app and forget about the "filler features" that pretty much every app needs implemented. On the other hand if you write Rails code without reading the docs and re-invent the wheel rather than doing things the Rails way, essentially running an app in the framework without using the framework methods, you can create yourself a monstrous unmaintainable codebase like no other.
I moved on from Ruby land right as AWS was blowing up, and admitted I have never worked in Rails.
Everyone brought their preferred syntax and there was much less of a “let’s solve the problem, not be clever” sentiment back then.
That said, Python almost makes such things impossible. Decorator patterns are the only thing I can think of in Python that ever made me really think.
6 to one, half dozen to another. How we start out thinking about problems has an impact on what syntax works for us best later on. I’m in my 40s and started in electronics, designing boards that shipped in Nortel kit.
I started with C and little else, not digging into OOP until hardware work went overseas. Perhaps my brain is over specialized to prefer a particular way of visualizing code I need to write.
You needn't use your real name, of course, but for HN to be a community, users need some identity for other users to relate to. Otherwise we may as well have no usernames and no community, and that would be a different kind of forum. https://hn.algolia.com/?sort=byDate&dateRange=all&type=comme...
I mention Rails specifically because I can't think of any other reason I'd use Ruby these days.
Oddly enough I found myself becoming less productive when I tried Elixir and Phoenix for a bit (compared to Ruby).
You could look at code written by 10 people and end up with 10 drastically different looking code bases. Not just the organization of files but the logic behind the functions. It makes it very hard to read other people's code unless you know every pattern they use very well.
Here's an example of that where I wrote something one day, asked if there's a better way to do it and someone else wrote their own version[0]. Take a look at the 2nd code snippet vs the 3rd. The first code snippet happens to be Python btw. Even after a year of casually working with Elixir I can't understand the 3rd snippet of code at a glance. When I first tried to parse it mentally I spent literally an hour picking it apart just to see what everything did and I forgot almost all of it a week later.
I also found myself much less productive mainly because the language tends to be quite verbose compared to Ruby, especially when you compare things like ActiveRecord vs Ecto. Ecto is one of those tools where it sounds unbelievably good on paper with great abstraction ideas but in practice it's pretty complicated because there's tons of different syntactical ways to do the same thing, the docs aren't inconsistent and every blog post you read has people implementing things in a different way. Then you combine that with it being fairly low level and you end up having to write huge amounts of very tricky code to do the equivalent of what you can do in AR.
At least with Rails, most projects look the same and unless someone dives off the deep end with meta programming, you can generally follow the code. I guess this is a testament to how good Rails is when it comes to developing its APIs. You can go such a long ways and develop fully featured apps based on Rails without really having to do anything crazy.
[0]: https://nickjanetakis.com/blog/formatting-seconds-into-hh-mm...
Rubocop (with some tweaked rules; it's IMO too restrictive out of the box) was a big lifesaver in terms of steering both teams toward a common, readable coding style and mostly eliminating debates over style at PR review time.
Did you have a look at https://crystal-lang.org/?
Or irritate a long term user base? PHP has been very successful long term, largely I would argue because of maintaining almost complete backwards compatibility.
How?
> PHP has been very successful long term, largely I would argue because of maintaining almost complete backwards compatibility.
Gemification of the standard library has very little impact on backwards compatibility; moving out of stdlib into default gems. Moving to bundled gems is transparent to code but has to be taken account of in build infra, moving out past bundled gems is where it would become visible to consuming code, equivalent to being evicted from stdlib before gemification.
Rails is good, but the ecosystem beyond Rails is better in many cases.
I don't know if it's the library itself or the documentation contents or the documentation website theme, but it jives absurdly well for me. I also really like the simplicity+flexibility of the library itself, and have yet to find anything even close to it in the c# or c++ world.
Python is a pervasively procedural (to the extent that it likes hiding OO features behind procedural interfaces), statement-oriented programming language with multiple-inheritance style OOP, and a structural aversion to functional idioms.
Ruby is a pervasively OO, expression-oriented language, with single-inheritance + mixin OOP, and an affinity for functional idioms expressed through fluent OOP APIs.
Ruby is very much not redundant with Python.
So, yeah, although there's less gain, there's less cost, and you might come across patterns you can transfer.
{b: 0, c: 1} => {b:}
p b #=> 0
It is so weird that a variable named `b` is initialized after an hash-key-like symbol is referenced (`b:`). It's something you've never seen in Ruby before and that I hope I'll never encounter along my career as Ruby on Rails developer. 3 => x # assigns 3 to x
[4, 5] => [x, y] # assigns 4 to x, 5 to y params = { a: 1, b: 2 }
{ a: a } = params
p a #=> 1I say it is pretty good.
In Go, you simply write a blocking IO call, and the scheduler in the Go runtime pauses your current lightweight thread ("parks the go routine" in their terminology) until the IO operation has some results to read. While waiting, the runtime is free to run any other go routine(s) on any number of OS threads.
What is coming to Ruby is a similar system, but where a library can provide this scheduler functionality. So when a lightweight thread (a "Fiber" in Rubys terms) calls a blocking operation, the runtime is notified, and can manage the IO operation while running other fibers.
As another commenter mentioned, there was a similar thing named gevent implemented for Python some years ago, but it didn't become "the way" to do things. I feel there are three main reasons it didn't catch on:
1. A lot of Python programs had blocking IO being performed inside native extensions (e.g. bindings to C/C++ database drivers). If you depended on such a library, the benefits of gevent were severely limited because it's runtime couldn't intercept the IO operations.
2. It was never part of the stdlib, so there was not a huge amount of incentive for the aforementioned driver authors to support it explicitly.
3. There were not that many examples of successful implementation of this model at the time. Go was around but pre 1.0 and not nearly as popular as it is now. Lua was also around, but again, not extremely well known.
I expect that Ruby will also suffer a bit from #1, but hopefully, by providing this concept as part of the language and standard library, problem #2 will be mitigated. #3 is IMO a thing of the past, while I'm not a huge Go fan, it does serve as a compelling argument for an implementation of runtime-managed lightweight threads.
Edit: I should mention I'm in no way involved in this, just interested in what will come of it over the next few years.
Ractors resemble Node workers: you get true parallelism, but the two worlds are entirely isolated, sharing nothing. You communicate by passing messages between both, where message content is either copied or moved (ownership change) if it’s not immutable.
(What I described is not 100% accurate, more of a flawed bird’s eye analogy for Ruby foreigners)
There's a python library gevent which experimented with a more implicit design. Or more, this is about green threads vs async/await
It can also be a bit confusing when you don't allow separating await from the call: how do you implement Promise.all?
For official rationale see https://www.python.org/dev/peps/pep-0492/#why-async-and-awai...
- Fibers
- Also fibers, but scheduled
- Threads (Not what you'd normally calla thread, because GVL)
- Ractors (what you'd normally call a thread)
I wouldn't say this is a bad thing per se, but I imagine it will be quite confusing for people trying to learn the language.
Not from language semantics perspective..but specialisation in a very narrow usecase and around DX. But really unbeatable in that usecase.
all racehorses are one-trick really.
low level? Rust/C/C++
'corporate' work? Java/C#
Android? Kotlin
iOS? Swift
ML / Data statistics? Python
Anyone know the status of using JIT in something like a rails app?
Have things improved on that side?