I appreciate the Ruby love in this post, but it's worth pointing out that the author is clearly new to the language. I say that because he writes Ruby as if he's writing some other language, and not idiomatic Ruby.
This:
dirp = Dir.open(".")
for f in dirp
case f
when /\.rb\z/
print f, "\n"
else
# do not print
end
end
dirp.close
Could be made much more Ruby-native (and simpler!) as:
Dir.open('.').each do |f|
puts f if f.match?(/\.rb\z/)
end
My point isn't to play the "I know the language better than you, so nyah nyah" game, just to suggest that if the author likes Ruby as much as he says, he should learn it better.
So the weirdest part is that the OP is literally just a cut and paste of a bunch of scripts found in the ruby distribution "samples" folder (that I had no idea even existed).
That pretty not great ruby wasn't written by the author -- it's actually included in a "sample" file with the ruby distro?
A file whose commit history shows... it's part of the very first commit recorded in git history, in 1998 by matz, the original author of ruby.
I have no idea what's going on in that "sample" folder, very weird.
The OP is simply regurgitating the weird "sample" folder. The "author" of the OP didn't write any of this code. According to git history... matz did?!?
> A file whose commit history shows... it's part of the very first commit recorded in git history, in 1998 by matz, the original author of ruby.
So not necessarily written by Matz, but part of the body of work that eventually saw a `git init`, and early enough that much of the modern standard Ruby formatting was still emerging. Doesn't seem all that surprising to me in the context of when it was written.
Then the reasoning for this weird code is that it's meant as a base to be extended. E.g. handling more variants of filenames? Anyways, maybe it's worth submitting a pull request with simpler code.
That snippet looks like Python/Lua/C with the for, case and close being used. Ruby is one of those languages that like Lisp/Scheme, Smalltalk or Haskell requires you to know what is going on as well as what is available to come up with a clean/simple solution. I think finding good Ruby resources that are up to date are few but I believe most of the stuff out there still works in recent versions.
> That snippet looks like Python/Lua/C with the for, case and close being used
Not really Python like at all - apart from the one line with for. Python doesn't have a case or when statement, or syntax level regex, doesn't open or close directories, and for files it would idiomatically would use the context manager for file access rather than closing it.
But it also doesn't look like any Ruby I've seen or written either :)
>> I think finding good Ruby resources that are up to date are few...
I just finished a class that was taught with Ruby. It seems like an absolutely delightful language, but I could not find docs pertaining to several projects that I had conceptualized as my 'final project'. Microsoft Graph API has no docs available for Ruby.
Probably worth mentioning that each of those code snippets links out to a core sample in the ruby/ruby repository. There is a source link directly above the code you posted. https://github.com/ruby/ruby/blob/ruby_3_1/sample/dir.rb
I'm not familiar with Ruby, so I have a question: the first snippet has the .close method called while the second doesn't. Does the second example leak resources, or it's automatically closed after a GC in a finalizer, maybe? Does .each close the directory? Or, maybe, .close is a no-op in newer versions?
You’re quite right. OP’s rewrite contains a bug: it leaks one file descriptor per invocation. The correct version passes a block to `open` directly so that it’s automatically closed when the block is done executing. Maybe golfing down to be “idiomatic” isn’t always best :)
Dir.open('.') do |dirp|
dirp.each do |f|
puts f if f.match?(/\.rb\z/)
end
end
There are a few ruby stdlib classes like Tempfile that use the finalizer trick you mention to free resources on GC but Dir isn’t one of them. Here’s it’s implementation:
You couldn't be more wrong, and a simple Google search of the author was all you had to do lol. It's pretty clear here (given his background) that the author is writing in a style to make the code examples more accessible to a wider audience.
Seems to be just a random collection of random ruby snippets, for clicks? Surprised to see it voted up.
Oh wait... they are snippets from the ruby distribution "sample" folder? I didn't even know there was a ruby distribution "sample" folder.
Looking at the git history, it looks like while a few of the "samples" have been touched in the past few years -- others haven't been touched in 10+ years, and I think maybe all of them originate 10+ years ago... maybe all of them originate from ruby original release? I don't think most people even know about this "sample" folder.
So I guess OP is giving us a tour of it, ok. It's... not that interesting.
Some of the samples show you how to use parts of the stdlib, which I guess can be useful. Others, like that second example "biorhythm", are just kind of inexplicable random scripts. (What even is a "biorhythm"?). None of them seem to have any comments (perhaps because they were written by Japanese speakers decades ago?)
These literally decades-old snippets of unclear purpose are probably not actually great examples of how to write current ruby. They may be interesting historically, I wonder how they even got here.
This article is kind of niche... My favourite Ruby delights are among others, methods provided by the Enumerable mixin https://rubyapi.org/3.1/o/enumerable. There's a pile of functionality there that helps with tedious tasks. E.g. here's a solution to this year's AOC challenge #6 using Enumerable's each_cons method:
I struggle with code like this (also, complicated piped shell statements). How do you debug/reason about this? I know this specific example may be an exercise, but still.
Ruby is a surprisingly simple language. Metaprogramming in ruby is a pretty good book that makes it clear what is happening under the hood.
The yield is probably the most complicated part of it, but it is extremely useful for hiding complexity. Once you understand yield, there is very little magic to what ruby is doing.
I prefer python to ruby, but the concept is the same:
As for understanding the command line/reasoning about it, they are usually generated iteratively.
Look at a file to see what you have to work with:
$ cat $file | head -n 5
Decide commas aren't useful and remove them
$ cat $file | sed 's/,//g' | head -n 5
Decide you want a tab between every 4 characters on any given line
$ cat $file | sed 's/,//g' | sed 's/\(....\)/\1\t/g' | head -n 5
Each step of the way you see the output, and every additional pipe modifies the last seen output.
Everything else is just being aware of what tools you can use (sed/awk/grep/xargs/etc) and the limits of the data you work with.
GP may have done something like popping the ruby repl, irb, and then:
First, it's an exercise, you don't get to use much of this functionality day to day, at least if you're a lowly webdev :(.
Second, the solution is optimized for speed of writing (you get more points in AOC the quicker you submit the solution), not for readability. I try to get the chain of calls in my Ruby REPL as soon as the challenge drops.
Third, if you so wish to debug this chain of calls, you can insert a breakpoint and get a REPL anywhere in it with Object#tap:
Debug? You don’t. You have to break your sexy chained-functional-style 1-liner into a “boring” multi-line loop to actually set meaningful breakpoints and work through any problems that may arise. Which is why I dislike this style of code — once something goes wrong, it’s WAY more cumbersome to debug and almost always makes you “unroll” it into its boring, “classic” form. And, of course, once you do that, you’re now debugging something DIFFERENT than what’s shipping in production! And you have to be extra careful to ensure that all of the logic has been kept the same, lest you ship a patch that doesn’t actually fix the bug! This is my prototypical “what programming is NEVER about” example: programming is never about how pretty the code is. Programming is about shipping features, and then being able to diagnose and fix problems with what you shipped.
As you get used to it, it becomes highly readable. The 'tap' method to see what the elements look like at an intermediary step is also really helpful. I usually build these up left to right as well though and won't add on the next processing step unless I'm very certain there period ones are correct
It's important to separate this example from the general concept.
The concept is (generally) called functional composition.
The way one reasons about it, is to mentally split the statement at each function, and think about each step separately. The readability of functional composition comes from the fact there is interrelation exclusively between each adjancent couple of functions - in practice, the reader needs to keep only one result in mind at a time.
Functionally composed statements read like a sequence of statements, rather than a single one. The advantage is that they avoid having to use throwaway temporary variables for each step.
Shell pipes work the same. Even if they're long, assuming that they don't obscure features or use complex intermediate results, they're interpreted the same way - one transformation at a time.
Back to the example. It's not good for a few reasons:
1. it's not properly formatted
2. it uses an uncommon feature (named block variable, `_1`)
3. the sum at the end of the statement breaks the flow.
Writing functional composition in Rust tends to be a bit cleaner, not because of the language, but because of the autoformatting, that indents the statement (but not the sum; that one, I've separated it manually).
Regarding debugging: you can split the statement as convenient, and recompose it once you're done with debugging. Depending on the given statement, one can also put breakpoints inside the blocks.
Code like this should definitely not be written in production code bases and is really just a fun exercise. I think everyone would agree that if this had been written in 2 or 3 lines it'd be much more readable (and therefore maintainable)
Personally I find the Ruby easier to read. Just follow the functions left to right, and the method names are more literate (chars() vs split('') for instance). I’m also not sure at first what slicing on an array with a negative start index will do, whereas I’m sure each_cons does just what it purports to do.
> There are so many Ruby features built in to the language itself. In the very first episode of this series, we dove deep into the internals of one such gem bundled with Ruby's standard library: IRB. Keeping on theme, I'd like to take some time to explore some other areas of Ruby's source.
From that introduction I though this would be about different parts of the standard library, but it's about files from the example folder. In how far are these built in?
Yet there's almost no real discussion of how to use the standard library here, and a lot of talking about example code that's mostly useless (seriously - why is biorhythm pseudoscience #2, and how is displaying a calendar on the CLI going to help me...?)
I agree with the top post - I have a very different idea about what "built-in" means than the author of this article.
I would like to thank everyone for such a lively discussion! It has been a joy reading through and feeling the sentiment ebb and flow in that traditional HN fashion
For those curious and inquisitive minds out there, this article was intended to provoke emotion, fuel your creative soul, and pull you in to the Ruby source code. The precursor to this article was one I wrote on how IRB works and many of it's features. I had accidentally stumbled upon this "samples" directory and like many here wondered _why_ it is there and _what purpose_ was it serving. While many of the examples are a bit dated, they all make use of the standard library in various ways, and many make use of Ruby in ways _I_ hadn't seen before. Even if you don't care for the examples themselves, I hope to leave you with ample reading material to explore those unfamiliar crevices of Ruby.
As many have shouted, there are quite a few pain points in the Ruby language documentation, best practices, discoverability, and onboarding of new comers. This is an incredible time to be a part of this community and make this language as enjoyable to learn as it is to write.
most of the posts here are criticisms of the OPs efforts. they seem misplaced. Ruby lacks accessible, consistent, example-based explorations that cover the breadth of the language in practice-- as a programming language, rather than just a dsl for OO and REST apps.
I'm fairly new to Ruby and to programming in general. I've found so far that Ruby is sorely underdocumented. Coming from JS, where we have MDN, the docs for Ruby are obtuse and don't provide examples.
If you do google search you get wordy, outdated, non-idiomatic code examples that often feel incomplete or are even flat out wrong. There are a lot of gotcha's and hangups in the language* that you can sometimes find in books-- but not consistently and never in blog posts.
My learning process so far has been to look at the docs, which often are EMPTY (see Symbol#to_proc)*. Then I'll read through 5-6 blog posts from 2016, not find the answer, then do searches through multiple (expensive) books that are almost always nothing succinct (not reference style). I eventually have to ask on a chatroom or some other asynch forum, in which case that knowledge has no foward discoverability and the experts in the language find themselves in a samsara of questions/answers.
I could (and often) do github-wide searches of usages of a specific method or idiom, but that is tedious, inconsistent, and overly dependent on one of many features on a 3rd-party service.
TL;DR: what i'd like to see as a newcomer to the language is a canonized and community-driven knowledge base, ala wiki.
*an example of a hangup: some types are immutable and others not. array.each on an array of intergers will not behave the same as an array of strings.. so for ints you have to use map for certain transformations
*the doc entry for Symbol#to_proc is exactly one line and doesn't even use the method! it uses the &: operator.
This almost reads more like a list of cruft which should be removed from the stdlib (obviously not benchmark.rb though). For delegation it is probably better to use forwardable and be explicit about delegated methods--delegate.rb smells like it uses method_missing magic.
You don't see much ruby anymore that uses `DelegateClass`, the primary interface to delegate.rb. It does use metaprogramming but no more than Forwardable does. Also, it's "the good kind" of metaprogramming: active at program boot only, not a whiff of method_missing. This is closer to a lisp or rust macro: code which writes code. Exactly what ruby excels at.
… and ruins the life of future maintainers, there’s a reason few languages tried to borrow features from ruby’s metaprogramming (saying this as a rubyist)
Once things are in the standard library, they're basically impossible to remove, unless you want to break everyone's code and prevent them from updating.
If you're making a new programming language today, the standard library is a place where you should think very carefully about everything you add. Every programmer learning the language will have to be familiar with the standard library, so keeping it small is good. But too small, and programmers will always have to reach for third-party packages without maintenance or stability guarantees. For example, you don't want to have to choose among 5 different libraries to write and run unit tests; the language should define that and it should meet everyone's needs (or be extensible with a small third-party library).
I think the Rubys and the Perls of the world probably choose too much to add to the standard library, and that's where the comment you're replying to comes from. But, while it's easy to overdo the standard library, it's also bad if you underdo the standard library. Tough and sometimes under-considered aspect of language design.
Not the parent, but my usual response to this is always "well someone's going to use it in the codebase, which means I'm going to have to end up using it too".
As a newb, what editor to use for Ruby? It's one of those languages that VS Code does not cover properly and I can't really find good setup for nvim or emacs. RubyMine works wonders but I'm poor.
Also, why is Ruby "editor responsiveness" (not sure what to call it) so much worse than e.g. Python?
If you're looking for IDE-level language assistance, I can't help you, but since you mentioned nvim: I use regular vim with CoC / Conquer of Completion (vim plugin; LSP server, may not strictly be necessary for nvim), Solargraph (Ruby Gem; language server), and Rubocop (also a Gem) for linting. I previously/still use ALE (vim plugin; Asynchronous Lint Engine) because I haven't gotten CoC+Solargraph to play nice with Rubocop, probably due to something silly.
My impression with all of this running under MacVim... it's plenty responsive. It can take a while for Solargraph to index everything on startup if you're working in a big project; once it loads, it's snappy. (There's probably a way to cache that startup scan.)
I recently started using the Solargraph LSP with neovim and it's pretty nice.
Doesn't quite get you to the level of RubyMine in terms of smart-ness, but I find it's close-enough in practice. And I also highly recommend using TreeSitter for syntax highlighting if you're gonna go the Neovim route -- for whatever reason, I've found regex-based highlighters to be unbearably slow on even medium-sized ruby files.
I also work on a project that uses Rubocop for linting which is nice since it gives you some Prettier-like auto-formatting.
There may be some more goodies to be had (e.g. debugging via nvim-dap), but I haven't dug into that yet. As it stands, I'm pretty pleased with my current setup.
This:
Could be made much more Ruby-native (and simpler!) as: My point isn't to play the "I know the language better than you, so nyah nyah" game, just to suggest that if the author likes Ruby as much as he says, he should learn it better.That pretty not great ruby wasn't written by the author -- it's actually included in a "sample" file with the ruby distro?
https://github.com/ruby/ruby/blob/ruby_3_1/sample/dir.rb
A file whose commit history shows... it's part of the very first commit recorded in git history, in 1998 by matz, the original author of ruby.
I have no idea what's going on in that "sample" folder, very weird.
The OP is simply regurgitating the weird "sample" folder. The "author" of the OP didn't write any of this code. According to git history... matz did?!?
In any case, I wonder if the OP's motives were good or if this was just some SEO game... we have enough of the latter on the internet.
So not necessarily written by Matz, but part of the body of work that eventually saw a `git init`, and early enough that much of the modern standard Ruby formatting was still emerging. Doesn't seem all that surprising to me in the context of when it was written.
[0] https://github.com/ruby/ruby/commit/3db12e8b236ac8f88db8eb46...
Not really Python like at all - apart from the one line with for. Python doesn't have a case or when statement, or syntax level regex, doesn't open or close directories, and for files it would idiomatically would use the context manager for file access rather than closing it.
But it also doesn't look like any Ruby I've seen or written either :)
I just finished a class that was taught with Ruby. It seems like an absolutely delightful language, but I could not find docs pertaining to several projects that I had conceptualized as my 'final project'. Microsoft Graph API has no docs available for Ruby.
There are a few ruby stdlib classes like Tempfile that use the finalizer trick you mention to free resources on GC but Dir isn’t one of them. Here’s it’s implementation:
https://github.com/ruby/ruby/blob/1a24442193fe437e761e941d1a...
Deleted Comment
Seems to be just a random collection of random ruby snippets, for clicks? Surprised to see it voted up.
Oh wait... they are snippets from the ruby distribution "sample" folder? I didn't even know there was a ruby distribution "sample" folder.
Looking at the git history, it looks like while a few of the "samples" have been touched in the past few years -- others haven't been touched in 10+ years, and I think maybe all of them originate 10+ years ago... maybe all of them originate from ruby original release? I don't think most people even know about this "sample" folder.
So I guess OP is giving us a tour of it, ok. It's... not that interesting.
https://github.com/ruby/ruby/blob/ruby_3_1/sample/
Some of the samples show you how to use parts of the stdlib, which I guess can be useful. Others, like that second example "biorhythm", are just kind of inexplicable random scripts. (What even is a "biorhythm"?). None of them seem to have any comments (perhaps because they were written by Japanese speakers decades ago?)
These literally decades-old snippets of unclear purpose are probably not actually great examples of how to write current ruby. They may be interesting historically, I wonder how they even got here.
The yield is probably the most complicated part of it, but it is extremely useful for hiding complexity. Once you understand yield, there is very little magic to what ruby is doing.
I prefer python to ruby, but the concept is the same:
https://realpython.com/introduction-to-python-generators/
As for understanding the command line/reasoning about it, they are usually generated iteratively.
Each step of the way you see the output, and every additional pipe modifies the last seen output.Everything else is just being aware of what tools you can use (sed/awk/grep/xargs/etc) and the limits of the data you work with.
GP may have done something like popping the ruby repl, irb, and then:
Second, the solution is optimized for speed of writing (you get more points in AOC the quicker you submit the solution), not for readability. I try to get the chain of calls in my Ruby REPL as soon as the challenge drops.
Third, if you so wish to debug this chain of calls, you can insert a breakpoint and get a REPL anywhere in it with Object#tap:
And lastly, it becomes second nature to read and write these.Debug? You don’t. You have to break your sexy chained-functional-style 1-liner into a “boring” multi-line loop to actually set meaningful breakpoints and work through any problems that may arise. Which is why I dislike this style of code — once something goes wrong, it’s WAY more cumbersome to debug and almost always makes you “unroll” it into its boring, “classic” form. And, of course, once you do that, you’re now debugging something DIFFERENT than what’s shipping in production! And you have to be extra careful to ensure that all of the logic has been kept the same, lest you ship a patch that doesn’t actually fix the bug! This is my prototypical “what programming is NEVER about” example: programming is never about how pretty the code is. Programming is about shipping features, and then being able to diagnose and fix problems with what you shipped.
The concept is (generally) called functional composition.
The way one reasons about it, is to mentally split the statement at each function, and think about each step separately. The readability of functional composition comes from the fact there is interrelation exclusively between each adjancent couple of functions - in practice, the reader needs to keep only one result in mind at a time.
Functionally composed statements read like a sequence of statements, rather than a single one. The advantage is that they avoid having to use throwaway temporary variables for each step.
Shell pipes work the same. Even if they're long, assuming that they don't obscure features or use complex intermediate results, they're interpreted the same way - one transformation at a time.
Back to the example. It's not good for a few reasons:
1. it's not properly formatted
2. it uses an uncommon feature (named block variable, `_1`)
3. the sum at the end of the statement breaks the flow.
One would typically write the example like:
Writing functional composition in Rust tends to be a bit cleaner, not because of the language, but because of the autoformatting, that indents the statement (but not the sum; that one, I've separated it manually).Regarding debugging: you can split the statement as convenient, and recompose it once you're done with debugging. Depending on the given statement, one can also put breakpoints inside the blocks.
I.e. in JS:
From that introduction I though this would be about different parts of the standard library, but it's about files from the example folder. In how far are these built in?
All of the examples given use only the standard lib, afaict.
I agree with the top post - I have a very different idea about what "built-in" means than the author of this article.
I would like to thank everyone for such a lively discussion! It has been a joy reading through and feeling the sentiment ebb and flow in that traditional HN fashion
For those curious and inquisitive minds out there, this article was intended to provoke emotion, fuel your creative soul, and pull you in to the Ruby source code. The precursor to this article was one I wrote on how IRB works and many of it's features. I had accidentally stumbled upon this "samples" directory and like many here wondered _why_ it is there and _what purpose_ was it serving. While many of the examples are a bit dated, they all make use of the standard library in various ways, and many make use of Ruby in ways _I_ hadn't seen before. Even if you don't care for the examples themselves, I hope to leave you with ample reading material to explore those unfamiliar crevices of Ruby.
As many have shouted, there are quite a few pain points in the Ruby language documentation, best practices, discoverability, and onboarding of new comers. This is an incredible time to be a part of this community and make this language as enjoyable to learn as it is to write.
I'm fairly new to Ruby and to programming in general. I've found so far that Ruby is sorely underdocumented. Coming from JS, where we have MDN, the docs for Ruby are obtuse and don't provide examples.
If you do google search you get wordy, outdated, non-idiomatic code examples that often feel incomplete or are even flat out wrong. There are a lot of gotcha's and hangups in the language* that you can sometimes find in books-- but not consistently and never in blog posts.
My learning process so far has been to look at the docs, which often are EMPTY (see Symbol#to_proc)*. Then I'll read through 5-6 blog posts from 2016, not find the answer, then do searches through multiple (expensive) books that are almost always nothing succinct (not reference style). I eventually have to ask on a chatroom or some other asynch forum, in which case that knowledge has no foward discoverability and the experts in the language find themselves in a samsara of questions/answers.
I could (and often) do github-wide searches of usages of a specific method or idiom, but that is tedious, inconsistent, and overly dependent on one of many features on a 3rd-party service.
TL;DR: what i'd like to see as a newcomer to the language is a canonized and community-driven knowledge base, ala wiki.
*an example of a hangup: some types are immutable and others not. array.each on an array of intergers will not behave the same as an array of strings.. so for ints you have to use map for certain transformations
*the doc entry for Symbol#to_proc is exactly one line and doesn't even use the method! it uses the &: operator.
What do you mean by this?
Alas, logs aren't easily accessible in the element android app (which is what i've got).
https://github.com/ruby/ruby/blob/afd46429fcdb83aa9fa7c193ed...
SimpleDelegator is still pretty common though, I wouldn't blink if I saw that while reviewing a PR.
If you're making a new programming language today, the standard library is a place where you should think very carefully about everything you add. Every programmer learning the language will have to be familiar with the standard library, so keeping it small is good. But too small, and programmers will always have to reach for third-party packages without maintenance or stability guarantees. For example, you don't want to have to choose among 5 different libraries to write and run unit tests; the language should define that and it should meet everyone's needs (or be extensible with a small third-party library).
I think the Rubys and the Perls of the world probably choose too much to add to the standard library, and that's where the comment you're replying to comes from. But, while it's easy to overdo the standard library, it's also bad if you underdo the standard library. Tough and sometimes under-considered aspect of language design.
Also, why is Ruby "editor responsiveness" (not sure what to call it) so much worse than e.g. Python?
https://github.com/neoclide/coc.nvim
https://solargraph.org/
https://rubocop.org/
https://github.com/dense-analysis/ale
My impression with all of this running under MacVim... it's plenty responsive. It can take a while for Solargraph to index everything on startup if you're working in a big project; once it loads, it's snappy. (There's probably a way to cache that startup scan.)
Doesn't quite get you to the level of RubyMine in terms of smart-ness, but I find it's close-enough in practice. And I also highly recommend using TreeSitter for syntax highlighting if you're gonna go the Neovim route -- for whatever reason, I've found regex-based highlighters to be unbearably slow on even medium-sized ruby files.
I also work on a project that uses Rubocop for linting which is nice since it gives you some Prettier-like auto-formatting.
There may be some more goodies to be had (e.g. debugging via nvim-dap), but I haven't dug into that yet. As it stands, I'm pretty pleased with my current setup.
I have no idea what you mean by editor responsiveness.