Very clear APIs and syntax(with the possible exception of blocks which can be weird because they aren't quite functions), and tons of raw metaprogramming powers.
You can argue it sacrifices too much of the other things to deliver on these things, but it's hard to argue against it doing well at what it optimizes for!
Metaprogramming is Lisp's canonical super power. Ruby is going to win out on tasks where it has built in syntax, like matching regular expressions.
But once you get to metaprogramming Lisp macros are going to give Ruby a run for its money.
I will say one of the under appreciated aspects of Ruby is the consistency of its semantics, where everything is message passing very much like Smalltalk.
rubygems.org also has decided to, rather than fix on existing problems, eliminate all former maintainers and instead put in Hiroshi Shibata as the solo lead - the same guy who keeps on writing on different github issue trackers how he does not have time to handle any issue requests for low-used projects. Wowsers.
Other than that, how was the play, Mrs. Lincoln?
Also, add readability and maintainability to that list, and scaling to a large codebase. And good debugger support.
Nitpick: technically `Gem::Version` is part of `rubygems`, and while `rubygems` is (typically) packaged with Ruby, it's actually entirely optional, so much so that `rubygems` actually monkeypatches† Ruby core's `Kernel` (notably `require`) to inject gem functionality.
MRuby has none of it, and CRuby has a `--disable-rubygems` configure flag.
Back in 1.8 days, you even had to manually require `rubygems`!
† https://github.com/ruby/rubygems/tree/4e4d2b32353c8ded870c14...
* default libraries (these are maintained by the Ruby core team, delivered with Ruby, and upgraded only as part of Ruby version upgrades.)
* default gems (these are maintained by the Ruby core team, delivered with Ruby, not removable, can be required directly just like default libraries, but can be updated separately from Ruby version upgrades.)
* bundled gems (these are gems that are delivered and installed with Ruby, but which can be upgraded separately or removed.)
Rubygems is a default gem. [0] It used to not be part of the standard library, but it has been since Ruby 1.9, released in 2007.
[0] see, https://stdgems.org/
Having done mostly TypeScript and Elixir lately, I had forgotten things could be so succinct yet so clear. The combo of modern (to me) Ruby's lambda syntax (in the .map call), parentheses-less function calls, the fact that arrays implement <=> by comparing each item in order, that there's an overloadable compare operator at all, having multiple value assignments in one go... It all really adds up!
In any other language I can think of real quick (TS, Elixir, C#, Python, PHP, Go) a fair number of these parts would be substantially more wordy or syntaxy at little immediately obvious benefit. Like, this class is super concise but it doesn't trade away any readability at all.
Having learned Ruby before Rails became commonplace, with its love for things that automagically work (until they don't), I had kinda grown to dislike it. But had forgotten how core Ruby is just an excellent programming language, regardless of what I think of the Rails ecosystem.
from dataclasses import dataclass
@dataclass(frozen=True, order=True)
class AppVersion:
major: int = 0
minor: int = 0
patch: int = 0
@classmethod
def from_string(cls, version_string: str):
return cls(*[int(x) for x in version_string.split(".")])
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}"
Before dataclasses you could've used namedtuples, at a loss of attribute typing and default initializer: from collections import namedtuple
class AppVersion(namedtuple("AppVersion", "major minor patch")):
@classmethod
def from_string(cls, version_string: str):
parts = [int(x) for x in version_string.split(".")] + [0, 0]
return cls(*parts[:3])
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}" def <=>(other)
[major, minor, patch] <=> [other.major, other.minor, other.patch]
end
vs: def __lt__(self, other):
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
Then use the `total_ordering` decorator to provide the remaining rich comparison methods.That said, it's a little annoying Python didn't keep __cmp__ around since there's no direct replacement that's just as succinct and what I did above is a slight fib: you still may need to add __eq__() as well.
eq: If true (the default), an __eq__() method will be generated. This method compares the class as if it were a tuple of its fields, in order. Both instances in the comparison must be of the identical type.
The Ruby example should be compared to the implementation of data classes. The Ruby code shows how cleanly the code for parsing, comparing and printing a version string can be. We would need to see the code underlying the data classes implementation to make a meaningful comparison.
@functools.total_ordering
class AppVersion:
def __init__(self, version_string):
parts = [int(x) for x in str(version_string).split('.')]
self.major, self.minor, self.patch = parts[0] or 0, parts[1] or 0, parts[2] or 0
def __lt__(self, other):
return [self.major, self.minor, self.patch] < [other.major, other.minor, other.patch]
def __eq__(self, other):
return [self.major, self.minor, self.patch] == [other.major, other.minor, other.patch]
def __str__(self):
return f'{self.major}.{self.minor}.{self.patch}' >>> from packaging.version import Version
>>> Version("1.2.3") > Version("1.2.2")
True
>>> Version("2.0") > Version("1.2.2")
TrueSome of those languages would have you deal with the problem of allocating multiple arrays in the heap just to compare three numbers. Or give you tools to outlaw passing invalid strings to AppVersion.new (quick: what is the comparison between AppVersions "foo" and "bar"?).
Plus you have very few tools to ensure code remains beautiful. I've worked with Ruby for close to two decades, almost nothing in the real world looks that clean. Take a look at the Gem::Version#<=> implementation that the article talks about: https://github.com/ruby/ruby/blob/master/lib/rubygems/versio...
See the commit that made it complex: https://github.com/ruby/ruby/commit/9b49ba5a68486e42afd83db4...
It claims 20-50% speedups in some cases.
There's churn that comes with that. Ruby will have code that is ever changing to gain 5%, 10% performance every now and then. You gotta put that on balance: in a language like Go this method would've been ugly from the start but no one would've needed to touch it in 100 years.
Caching is also vastly underutilized, most apps are read-heavy and could serve a significant portion of their requests from some form of caching.
> When there are performance issues, 95% of the times they come from the database, not the language.
Eh, statements like these are always too hand wavy. Resource usage has to do with performance, the DB has no fault in it but the runtime does.
Having worked with Rails a ton there’s a very large overhead, much more than 5%. Most apps would see a significant speed up if rewritten in a faster language, with no changes to the DB whatsoever. The amount of memory and CPU expended to the app servers is always significant, often outweighing the DB.
parts = version_string.to_s.split(”.”).map(&:to_i)
Is it to_string? Isn't version_string already a string?I think it's a safety measure in case the argument passed in is not a string, but can be turned into a string. Safe to assume that calling "to_s" on a string just returns the string.
if x.is_a? Array
x = x.first
Or something like that. Could be one line too: x = x.first if x.is_a? ArrayEven if you implement an "interface" (duck typing) with `respond_to?(:to_app_version)` you still can't be sure that the return type of this `:to_app_version` is actually a string you can call `split()` on.
It's also a quite bad practice to my eye.
A problem is that ruby lost many developres; rails too but it is by far the biggest driver in ruby. And this creates problems, because it overshadows the remaining ruby developers.
Why thank you! :D
record AppVersion(int major, int minor, int patch) implements Comparable<AppVersion> {
public static AppVersion of(String version) {
var array = Arrays.copyOf(Arrays.stream(version.split("\\.")).mapToInt(Integer::parseInt).toArray(), 3);
return new AppVersion(array[0], array[1], array[2]);
}
public int compareTo(AppVersion other) {
return Comparator.comparingInt(AppVersion::major)
.thenComparingInt(AppVersion::minor)
.thenComparingInt(AppVersion::patch)
.compare(this, other);
}
public String toString() {
return "%d.%d.%d".formatted(major, minor, patch);
}
} raise "check if monkeypatch in #{__FILE__} is still needed" if Gem::Version.new(Rails.version) >= Gem::Version.new("8.0.0")
This will blow up immediately when the gem gets upgraded, so we can see if we still need it, instead of it laying around in wait to cause a subtle bug in the future.Versions numbers can go to 10!?!
Right. I think I found it on stackoverflow.
The question is: why does the official documentation not mention this, along with guides?
Answer: because documentation is something the ruby core team does not want to think about. It is using scary language after all: English. The bane of most japanese developers. Plus, it is well-documented in Japanese already ... :>
difflib is probably my favorite one to cite.
Go see for yourself: https://docs.python.org/3/library/index.html
The benefit there is that their quality, security, completeness and documentation are all great!
I'd love to see a lot more writing and advocacy around Ruby, and not Ruby/Rails. I don't use Ruby/Rails! I use Ruby. And I suspect a lot of folks who have left Ruby behind over the years might not realize some (many?) of their gripes are not with Ruby in general, but Rails in particular.
$ txr -i version.tl
1> (equal (new (app-ver "1.2.003")) (new (app-ver "1.2.3")))
t
2> (equal (new (app-ver "1.2.003")) (new (app-ver "1.2.4")))
nil
3> (less (new (app-ver "1.2")) (new (app-ver "1.2.3")))
t
4> (greater (new (app-ver "1.2")) (new (app-ver "1.2.3")))
nil
5> (tostringp (new (app-ver "1.2.3.4")))
"1.2.3.4"
6> (tostring (new (app-ver "1.2.3.4")))
"#S(app-ver str \"1.2.3.4\" vec (1 2 3 4))"
Code: (defstruct (app-ver str) ()
str
vec
(:postinit (me)
(set me.vec (flow me.str (spl ".") (mapcar int-str))))
(:method equal (me) me.vec)
(:method print (me stream pretty-p)
(if pretty-p (put-string `@{me.vec "."}` stream) :)))
iagooar•4h ago
I wouldn't be as much in love with programming, if it wasn't for Ruby. And although I use many other programming languages these days, Ruby will forever have a special place in my heart.
matltc•4h ago
Glad to see it's getting love on here recently.
amerine•4h ago
jonah•3h ago
Not _exactly_ the same cut, but might be good enough for you?
netghost•2h ago
Ruby, and Ruby on Rails is a treasure trove of little handy bits you can use if you just know where to look. I really miss some aspects of ruby (I just don't have a chance to use it these days).