Read this post ‘unless’ you’re not a Ruby developer
jesseduffield.com
jesseduffield.com
Why? Because it allows you to express yourself more elegantly.
The click-bait title is misleading. It's meant to ridicule "unless", but actually achieves the opposite.
If you were to write the title of the post as code, it would be:
unless !ruby_dev
read article
end
That would be a terrible use of "unless"! That should clearly say "if ruby_dev", not "unless !ruby_dev".But what if you wanted to write an article meant for anyone other than ruby developers?
Which of the following is better?
if !ruby_dev
or unless ruby_dev
Both work, but I consider the second option more elegant. Just as I wouldn't verbally say "if you're not a ruby developer" rather than "unless you're a ruby developer".Honestly, I don't see the issue. It's a style matter. Just use it properly. All language can be abused if you try hard enough.
Therefore, coming from a computer science background I don't like 'unless'.
(For context: I dropped out of comp sci and took a job as a web dev ~18 years ago. Never looked back. But have ended up learning more about copywriting and marketing than computer science along the way.)
The truth is there is a balance in all languages. Nearly every word was invented to prevent having to say a string of other words to mean the same thing.
"Unless" is a single word to mean "If not". I consider that elegant.
However, some languages take this too far, in my opinion. German famously has a word for nearly everything. I'm not sure that's elegant, in that it requires learning a far greater number of words.
English has many words that I wouldn't consider elegant, simply because they're uncommon or convoluted. Never use a 10 dollar word when a 5 cent word will do.
So, if, like me and the ruby community, you prefer to optimize for developer happiness, "unless" is an elegant choice.
If you prefer to optimize for peak computing performance, if may be more computationally efficient use "if not".
But, _unless_ you're saying that you never use the word "unless" in everyday language, I think we can agree it's a good word that has good applications. I don't see why that wouldn't be true in programming.
That's not quite how it works though, right? Compound words are exactly that, combining two or more words to narrow down the meaning, without having to invent a new word. It's almost like "if not" vs "unless"...
Side note: ancient Greek didn't use spaces, and many other languages (Thai and Written Chinese are the ones with which I'm most familiar) either don't use spaces or make spaces optional. The distinction between a phrase and a word gets a bit blurry at times, and I find the distinction is seldom useful, particularly when making comparisons across languages.
Ruby inherits the Perl philosophy of "There's more than one way to do it". Compare with Python, and its "There should be one — and preferably only one — obvious way to do it."
It's true that it makes the compiler less elegant, but Ruby optimizes for the programmer at the expense of the compiler.
"count" is only equivalent to "length" and "size" when called with no argument and no block, and should be slower for that case (it includes a comparison of arity and a call to rb_block_given_p() in MRI before it returns the array length).
But there are enough people on this thread who feel differently that it makes me wonder if maybe we're wrong. If code is meant to be read and understood by all, and if "unless" is confusing to a large number of people, maybe those of us who like it should knock it off.
I do find it more elegant and easier to read, but maybe you and I can process double negatives easier than the anti-unless crowd are able to process "unless" logic.
If that's the case, I'd be willing to sacrifice my preference for "unless" for the greater good.
Could you give an example of a double negative in this case?
I ask because using "if" does not lend itself to double negatives in my experience, but just thinking about "unless" I see double negatives being an issue (since unless is already negating the operand).
I would likely not be a programmer if it weren’t for Ruby and it doing things different.
I had tried to learn programming for years (JavaScript, Python, Java, C#), none of it stuck or progressed beyond following tutorials and one of the things I had serious issues with was negation.
Then I came across Ruby and it just kinda clicked into place like no other language had. With Ruby I was able to do my own things within a month.
Now I don’t really have a problem picking up other languages anymore. But there’s others out there right now struggling to learn programming and there should be as many programming languages doing things different as possible to increase the chance that they come across one that is obvious for the way they think.
This idea of there should only be one way of doing things is poison in my opinion.
Which is why unless is such a hard concept for people to grasp, not just in code, but also in regular English.
At least the "if not" makes the negation explicit, which can then be easily eliminated using several different techniques (early return, swap with the else condition) if the negation is confusing enough.
However, "unless" necessarily includes negation because that's how the word is defined.
This is the first I've heard about this confusion over the word "unless" in English.
In spoken English I do notice that most often the "unless" comes after a statement. Example: "I'll be there on time, unless the bus is late." That is more clear (to me) than "I'll be there on time, if the bus is not late." Maybe not by coincidence, I tend to like "unless" when it follows the action in in Ruby code, too.
IMHO, something ‶elegant″ in programming is not something that can't be built form something else (or we will all end up in writing only CMOVs), but something that conveys the meaning of its author precisely and concisely – which makes it inherently subjective. I'm in the `unless` team, I can perfectly understand if you are in the `no unless` team, but this argument does not make a lot of sense.
The very ergonomic solution to map a computation to every element of a sequence is `map`. In this case, `for` is filled with bookkeeping that does not matter.
Also, the `for` operator definitely predates the `map` function. Some people also prefer `map` to be more or less pure and expect it to return a usable list; `for ` has no such expectation, it e.g. may consist solely in printing elements.
And `CAS/Compare Accumulator with Storage` definitely predates `if`, still you are most probably using the latter.
Go to the store and get milk, if not we already have some.
Let me put it this way: Are there _any_ programming paradigms that don't get poorly used? We need to learn how to use our tools well, not reduce them to banality.
raise(“a long error string”) unless valid
This reads much worse, when the string pushes the line near max width, than: valid or raise(“..
Of course it’s another idiom to learn but it’s not a difficult one when used simply.Everyone's lazy or overwhelmed at some point, and it made mistakes too easy to make (the problem is that it overtightens the lug nuts because the ease of use removes the proper physical feedback).
In situations where you don't have the bandwidth to perfectly educate everyone and enforce rules, sometimes it's easier to limit the toolset. A tire iron can be misused too, but it's a little harder.
I do wonder if it's an issue of "unless" being misused? Programmers using it just for fun rather than considering whether it's the best choice in context?
When I come across an `unless`, I can't use them; I have to come back out into "conscious reading" mode, or something like that. Makes me crazy.
do_something unless already_done?Anything else (at least for me) is more difficult to read, it's probably due to being exposed to all thos other languages that do not have that syntactic construct. Using C syntax conditionals feels more natural to me, but again this is all opinionated.
Problem with so much syntactic sugar is you need to retain/recall the semantics of the vocabulary. As a polyglot programmer who doesn't write much code anymore, it slows me down.
That said, I feel it's really down to double+ negation being hard to handle. `if` with a single negation is fine; `unless` is automatically a negation, so you're just off to a bad start on that front. I feel like the only viable use is in a case like Rust's `let … else { diverge }` where anything that fails the condition is guaranteed to diverge (typically, return) and I can just ignore it when reading the overall codeflow. But Ruby mixes that up and does `diverge unless …` which puts the diverging part "up front" and "in the way" for such readings.
Weirdly, Ruby was one of the first languages I learned early in my career, and at the time I had no problem with `unless`. But after years of experience with other languages, I similarly feel that `if` statements now trigger the fast pattern matching circuits in my brain, while `unless` makes me do a double-take and basically translate it into `if not`
At first I thought I was just becoming dumber with age, but I like your explanation better :P
When I write Elixir, `unless` gets awkward in a functional style of programming, and Elixir has guard clauses and pattern matching. I pretty much never use `unless` in Elixir despite using it in Ruby for years.
Sometimes, I'll add extra methods with a negation in the name itself. So for example
# instead of
return "invalid" unless valid?
return [] if empty?
# I define invalid?() and do
return "invalid" if invalid?
return [] if empty?THING if logical condition
Filter First is far better self-documentation: if condition THING
For example, I can totally see how imperative-first would mess up people with neurodivergant brains.
Tangent -- Ruby takes a lot of inspiration from Perl.
When I started writing these as pipeline execution. Then I’ll use a maybe?() instead if the conditional does not need to depend on the result of a previous operation.
The if !ruby_dev (read as "if not ruby_dev") is so much easier.
The one place I’ll often use unless is things like:
return unless valid
raise unless h.key?(k)
I think it might have something to do with:1. How abundantly clear it is that the condition is a Boolean; and
2. The “false” condition indicating a “no work to do” situation.
I think I only use them for postfix return, break, next, and raise.
I confess at first I thought they were somewhat superfluous, but they are more readable now that I'm used to them. Completely agreed on it being stylistic, mainly.
I always have to stop at an unless because the double-negative is really hard to evaluate in the mind IMHO.
IMHO unless leads to unnecessary nesting. In your example, it reads like _most_ people are ruby dev. I try to keep the most-often excecuted flow as flat as possible (happy path) and just try to nest for additional/special cases. Unless just tries to do... different.
Also note that unless itself contains another negative ( < medieval onlesse)
It's just easy to see if more than one conditions are evaluated:
if (!ruby_dev && cpp_dev)
vs
unless (ruby_dev && !cpp_dev)
Sure, I understand the "elegant" aspect, two symbols are shortened to one (although I'd argue that's not that important when it comes to clarity and legibility).
But every time I read "unless" in code it's quite jarring. I have to consciously translate it to "if not", and even then seeing the "unless" keeps tripping me off, perhaps because it's awkward in English to start a sentence out of the blue with "unless".
The `unless` keyword makes the interpreter slightly more complicated and is another piece of language the human brain needs to recognize. It may seem inconsequential, but grains of sand make a hill, as they say.
My ideal language would leave out pretty much anything that can be achieved with more basic constructs. It would of course have if-else, and leave out `unless`, but there would also be no switch statements or ternaries. Branching must be done with if-else or by looking up a value in a hash. No `for` or `while` loops because a simple `loop` construct with `continue` and `break` statements can do everything that `for` and `while` could do in other languages. No classes or inheritance because they're magical and they can be effectively simulated by the end developer if that's what they really want.
What I think would be really cool is to have a language with a syntax like Ruby but with only the most basic of programming language constructs.
Though, I think it's rare to expand a pattern match/switch into an if-else tree, and much more common to expand an if-else into a 2-way pattern match/switch.
If you're really keen on such a language, you could fairly easily implement such a language as a pre-commit hook source code re-formatter so that all of the source in your repository is in your favorite language subset.
You can simulate the loop behaviour if that's what you really want
Structured programming is, I believe, a good idea for the vast majority of use cases. Though one can technically use structured programming while using goto, encouraging goto can therefore allow programmers to go down a path that is counterproductive. It's basically the opposite end of the spectrum from OOP where encouragement of rampant objectiveness and inheritance makes programmers write code that is way too complicated.
There's absolutely a place for goto. I once made a "language" (actually YAML) specifically for developing for the Amazon Alexa platform, and it used something similar to goto called "go to scene" and "go to random". For that sort of thing, goto can be much more practical and even easier to reason about than "better" constructs in general purpose high-level programming languages. It's just not something I would consider appropriate for the kind of language I am proposing, though I still ponder on it.
This is not a contrived example. I regularly find myself having to read some code in a language in which I am not proficient. Stuff like ’unless’ is such a pointless obstacle in these scenarios.
So ”elegance” doesn’t come free. The cost is a loss of transferable knowledge from other languages. Your intuition is that this cost is low relative to the benefit of ”elegance,” I have the opposite intuition. Why stray from such universal conventions in favour of one person’s subjective aesthetic preference?
A statement that forces you to switch out of inaccurate skiming mode is insanely useful
Practically speaking, that's not how I know Ruby to be written. The convention I follow is to use `unless` when there is no `else` case and only when there is no negation in the expression.
You say there is a difficult time transferring knowledge from other languages when they see the `unless`. That's fair.
My response to that is that, to idiomatically think in Ruby is write code that reads well, and `unless` fits that. Every language has an idiomatic way in which one thinks and reasons with the language. While there are principles that can be transferred over across languages, the way to think in that language does not necessarily transfer. The Ruby community has a heavy emphasis on creating embedded DSLs that seems natural enough to written English, and the Ruby community's style guide is written for that.
In contrast to Ruby, Python has idioms that seem to be the inverse of Ruby. The intuitions on what is good, idiomatic Ruby is counter-intuitive in Python. This even extends to the design of Ruby bundler vs. Python virtualenv/pip. As a long time Rubyist, Python rubs me the wrong way (what is intuitive in Python is counter-intuitive in Ruby) until I realized that I used to write that style of code a long time ago with Pascal.
I want to suggest a slight reframing: instead of saying: "It is not all about cost-benefit," you could say "here is an addition to the 'benefits' column that hasn't been considered yet."
It's a totally reasonable argument; much better than "elegance." This was my main problem: that the critique of `unless` was much more plausible than the defence.
However, we don’t always make decisions purely on cost vs benefit. Some types of decisions, such as strategic thinking, involves unfair advantages. If cost-benefits are reasoned from a finite space in which one can find the optimal choice, unfair advantages cheats that, and operates on a potentially infinite space with imperfect information.
Not that I am saying Ruby or Python are unfair advantages :-) I’m just saying that cost-benefit is not the only way in which one can decide on something, and may not be appropriate for every situation. Being able to think in a language can potentially change how you think, and framing that as a benefit can be problematic.
I explored Ruby because I wanted to explore eloquence, semantics, and pattern languages (in the Christopher Alexander sense). I spent over ten years on it until I had my fill. Now I am exploring concurrency, reliability, uncertainty, self-healing and distributed systems — Elixir and Kubernetes. And while I still use Ruby in some tooling, it doesn’t give me the joy that Elixir does.
I do like `unless` in Ruby, but only when it is the one liner form and the exception happens pretty rarely. The multi line form often takes a lot of effort to parse for me if it is more than a very simple expression.
Python exposes those methods. What you see is what you get. Classes and objects are not considered as autonomous agents so much as data structures bound with functions that can operate on it.
More controversially than “unless” is Ruby’s way of calling an anonymous function. Almost every other language, if “f” is a function, you can call it with f(). In Ruby, an anonymous function is an object, and you call it by sending a message “.call()”; the shorthand for “call” being “f.()”. Everything is an object.
This trips up even polygots who like Ruby. But if your mind can shift in such a way where that is ever seen intuitive, then you’re probably well on your way to being able to think in Ruby. (Assuming someone _wants_ to think that way).
And if you go on that direction, almost the everything on the language is a larger roadblock than an oddly placed conditional.
What do you think is more important: the speed at which C programmers can navigate your code base, or one person’s subjective idea of ”elegance”?
Specifically about Ruby, with it's infinite levels of metaprograming and "you can even redefine the meaning of blank space" philosophy, it's not 'unless' that will stop anybody.
That said, when reading code, I often have to pause and mentally translate "unless" to "if not". This is especially true for more complex logic.
Elegant, yes. Additional mental overhead at times, certainly.
The `unless` is idiomatic to the Ruby way of thinking.
if (true) and if not(true) is simple and elegant, and doesn't require any person on my team to stop their flow to puzzle out what the code is supposed to be doing at that point.
You want a well-justified, inclusive, and consistent yet adaptable style when collaborating with people. It would be selfish to gatekeep a policy without regard for those principles.
“Unless” is a conjunction, and its presence creates a double negative with the end of the sentence making the sentence extra clunky (“unless you’re a Ruby dev, you shouldn’t read this article.”)
A few iterations later, we get to the correct syntax, “read this article if you’re a Ruby dev.”
if !ruby_dev
or unless ruby_dev
As a ruby dev of 5+ years, I still have an easier time with `if !ruby_dev`. I have to translate `unless` to `if !` every single time to grok something. Almost like an extra step in an algebraic expression being simplified.https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/Unle...
https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/Unle...
> there’s this bizarre quirk of human psychology where developers retain the unless against all odds
I have refactored a number of unwieldy `unless` statements written by colleagues so maybe there’s something to that.
I wholeheartedly disagree and believe we should instead grow the developers to understand these different approaches as opposed to labeling half of the language "bad practice".
As I said in another comment. Ruby was built to have multiple ways of doing the same tasks and criticizing it for this is... pointless.
Yes people can create monstrosities with all of these variations, but that's true with any powerful tool. If you dislike Ruby for being Ruby, pick another language.
But there’s such a thing as too much flexibility, expressiveness and flexibility often go hand in hand as flexible language lets you make things as expressive as you want… but things can be expressive without the same level of flexibility if they are well designed.
I find Rust strikes a good balance for low level language work, it feels more expressive than C because of higher level language features but it’s not necessarily as flexible as C with things like the borrow checker nagging you to be safe unless you turn it off.
Lisp… too flexible, too expressive. Lisp code reminds me of the phrase “that depends on what the definition of is, is”.
Scheme and scheme likes including weird step cousins like JavaScript share the unfortunate result of this and your drowning in custom DSLs and no one quite codes the same way unless you have rigorous tooling to enforce a team style.
Ruby falls foul of this for me too. It’s too flexible, it’s very expressive and I enjoyed reading the tutorials and learning the basics. But the moment I discovered real code I was immediately recoiling in horror …. It was a disaster… a spaghetti mess of overrides redefining and meta-programming in general. It made it impossible for me to ever trust the environment I coded it since I had to constantly inspect everything to make sure someone hadn’t done something crazy like enhanced the “+” sign to transform numbers to strings for avoiding something like a sql injection risk in a specific context and whoopsie, leaked the behaviour globally so now any time you added integers you got concatenated strings… but only after the library was dynamically loaded on first query … maddening stuff…
Stuff like this is why ruby is too flexible.
I understand business idea of having monkeys producing code for cheap but I think this approach is misguided.
> As I said in another comment. Ruby was built to have multiple ways of doing the same tasks and criticizing it for this is... pointless.
Agreed 100% with this take. If I only wanted 1 way to accomplish something no matter how inelegant it might be in that context, I would use Python. I choose Ruby specifically because of how expressive it is. It makes it a bit tougher when working in a team with 2 or more opinionated devs, ie. "should we use more functional or more OO approaches to solve a problem" but this is a culture and communication issue, not a language problem. Having a good rubocop setup and staying within the confines of your agreed upon ruleset is a good start.
For example you can use rubocop to enforce dissallowing `unless !condition`, then it's just not an issue anymore - these kinds of issues dissapear before code even makes it to review.
Exactly the reasons I use C++. Well, after crazy performance, efficiency and single executable deployment.
What may start as a simple single condition, may not remain that way a year later.
Since there's always the possibility of a certain conditional becoming more complicated, it makes sense to opt for the equivalent solution that makes adding those additional conditions easier.
The alternative is to change the keyword when you add those additional conditions, but in most workplaces it's very possible that someone with less rigor than you might come in and add those new conditions and not make the change.
And then if you want to add a new condition you need to parse and understand the more complicated new conditional first, before you can change it.
This adds a lot of maintainability risk for what appears to be, in the best case, a very minor benefit.
It's far simpler to ban use of unless.
return unless valid_token
return if expired?
This scales extremely well if you’re concerned about future conditions.Not exactly the same, but over the years I've heard people argue against having super customized shell configurations (e.g. completions, prompts, highlighting) because they won't be available if you have to use a different environment (e.g. sshing into a temporary cloud server to debug something failing in CI). I don't pretend that other people will value tradeoffs the same as me, but it's a mindset I can't really imagine ever having. I'd rather be happy most of the time even if I know I'm going to be unhappy for short periods occasionally in the future, and I don't really think the slight efficiency gain in the uncommon case due to being used to not having nice features is going to outweigh the larger efficiency gain for the much more common case.
At the risk of straying entirely off-topic, I've seen this in non-professional contexts at times as well; I recently had a friend in an online game mention that he doesn't like to utilize a convenience feature that happens for a couple weeks each year as part of a special even because he would miss it too much the rest of the time. While I don't think the tradeoff is quite as obvious to me for this sort of thing, I feel like occasionally having a fun temporary addition is worth it just for the change of pace, and I'm generally able to adapt to the loss of a minor convenience relatively quickly after losing it. It seems sort of like a question of "maximizing peak happiness" versus "minimizing peak unhappiness" or even "maximizing average happiness"; I like having something to get excited about every now and then even if it leads to feeling blase about things for a bit later because I get bored if things stay the same for too long.
I switched from QWERTY to Dvorak a long time ago, a few years after learning to touch type. Does it make me more efficient on my own computer? Probably slightly -- Dvorak really is a well-designed keyboard layout, but it's impossible to know whether my WPM is any higher than it would've been on QWERTY. However, I make way more mistakes and type way more slowly whenever I sit down at a QWERTY keyboard. I had to re-learn how to touchtype QWERTY after switching to Dvorak, and my skill never got back to the same level. It also leads to extra inconvenience every time I lend my computer to someone else, install a new OS, etc.
I think this means my average typing speed (weighted average of the time spent at Dvorak keyboards + QWERTY keyboards) is probably flat or possibly lower than if I had just stuck with QWERTY. When combined with all the effort I spent learning Dvorak, re-learning QWERTY, and making many mistakes in the early years, I think my lifetime productivity is probably even lower. I don't recommend that people switch, even if I think Dvorak really is a better layout.
do_something if boolean_expression
All unless does is allow you to negate it using an expression that's more natural to most people do_something unless boolean_expression
To me the negation of an if with ! is less clear in this context. For example: puts "That's a prime" if is_prime?(variable)
makes sense. So does: puts "That's not a prime!" unless is_prime?(variable)
Far more so than: puts "That's not a prime!" if !is_prime?(variable)
The author decides however to add more conditionals. Fine, don't do that. Or that it's unreadable in some contexts. Fine, don't do that either.The whole point of Ruby - the only reason it really exists - is that it should be fun and easy and not require you to spend too much time stressing over rules. If you read a piece of Ruby and don't like it, change it. It's not Python - there is more than one way. Use the way that makes sense in the context you're using it.
If you can't trust yourself or your team to do that wisely in a project, consider changing languages, because guess what? The language isn't going to change because you don't like that thing.
I would go so far as to suggest jumping into Rubocop "cold turkey", meaning all rules on, is not very helpful on an existing codebase. The way my team approached Rubocop on a particular large legacy codebase was to start with a few hand picked rules that were non-controversially good, and discuss new rules one at a time as we gradually fixed the existing issues.
Were I reading through real-world code that did things like that:
"OK, then we do this, and then we do that, and then-- Oh wait! Backtrack! We didn't do that thing at all! ... Now where were we, on our actual bug, before some very special person's syntactic speed bumps?"
It's an opportunity to get better at something versus banning it from usage because we aren't used to it.
Growing is, for me, almost always the right choice.
Regarding `unless`, that's a different matter. I was actually the person who proposed adding an `unless` syntax to Racket, and later regretted it. I was bikeshedding, before I knew something more useful to do.
This isn't about learning new things to add to our mental toolbox. This is about our difficulty to introspect on our own thought processes and effectiveness, and about bikeshedding.
Software engineering competence is hard, or we wouldn't have all the huge expenditures, slipped project schedules, and rampant security vulnerabilities. Redundant syntax forms that objectively makes it harder to read (see the lookahead and parser theory), solely because someone imagined it was sometimes easier to read this way (see dying languages Perl and Ruby) is just wasting our time, when we could be tackling the real problems.
(Though us pulling theories about programming and process out of our posteriors, to defend to the death, is arguably half our problem in industry right now.)
That said, I only use it in 2 cases:
* as a trailing condition (do_something unless this), mainly for early returns * as the only arm of a multi-line block (unless this ... end, no "else" blocks - Rubocop would yell at me anyway)
And then only if the condition is either a simple value, or a combination of simple values (this && that, this || that).
That's it. Never had a problem with double negatives or accidentally inverting the logic.
Of course one can avoid it at all but of course one cannot avoid to spend time on it when reading somebody's else code.
It's the exact opposite of pythons "There should be one– and preferably only one –obvious way to do it" - they intentionally create solutions that have zero practical benefit - it's just fuels arguments based on preferences and introduces mental overhead due to inconsistency.
unless predicate
do this
else unless impossible_thing
do the impossible
What it did at runtime I have no idea, but it broke my brain for the
rest of the week. if !cond1
do this
elsif !cond2
do that
?it's literally just `!` equivalent. Honestly I haven't seen much code using unless in wild. Especially that it is literally shorter to just !
unless (predicate) {
do_this;
} else {
do_the_impossible unless impossible_thing;
}And Perl literally has the opposite design principle, TIMTOWTDI or "there is more than one way to do it".
I'm not arguing here -- I prefer the "one obvious way" principle.
But you can design a beautiful programming language without regard for the long-term learnings of software engineering.
I always liked Perl's "unless", and I always made sure to not abuse it with double negatives or other contorted conditions that are, in themselves, reasonable. I also promised myself I wouldn't write very big programs in Perl.
- Matz, c.a 2003
die "You may only use port numbers 1024 and higher"
unless $port >= 1024; die "Port numbers below 1024 are forbidden"
if $port < 1024;
is hardly unreadable spaghetti code. die "You may only use port numbers 1024 and higher"
unless $port >= 1024 || is_root(current_uid());
die "Port numbers 1024 are forbidden"
if $port < 1024 && !is_root(current_uid()); return unless User.exists(id=100)Yep that's why I hate ruby - worked on one mature codebase for a year and after seeing various such gems used across the project - from >10 devs - I'm confident I will never touch the language again.
a=1;b=2;c=3;d=a+b==c?4:5;return dI'm not arguing other languages can't produce bad code, just that ruby is particularly suited for it especially as number of developers working on code increases. I've seen people propose a linter to enforce consistency - but at that point I might as well chose a language with better design choices - plenty of alternatives these days.
You're seriously proposing that adding a linter has the same organizational costs as changing languages entirely. Meanwhile, back in the real world...
Ruby has major advantages over many other languages and a linter is basic tooling you should have in every development environment.
if condition { return }
works well enough in other languages and shows actual condition (the important part) to programmer first. if that if and extra brackets is really too long Perl way is also option. condition || returnIn any case, the Ruby community already has good guidelines on the "Unless" usage, there are few scenarios where they are useful but it's not like you find them everywhere in a codebase.
For example, we don't use "Unless" with "Else", or use Unless with negation (like the article), or use Unless in nested If statement, etc. Rubocop will catch many of these and warn you.
My point is that experienced engineers will use the language as it was intended to and not abuse its features.
E.g. "42 == (return true)" is a syntax error while "42 == if false; true; end" is syntactically valid.
You can return in the expression in the comment above because the return is at statement level - nothing above it in the grammar requires a value (but obviously allows it).
Off the top of my head I can't remember which other keywords fall in that category. Obviously "end", "then", "alias" and there'll be a few more, but for most keywords in Ruby you're right they can be treated as expressions.
Er, what?
You understand almost everything in Rust is an expression right?
let x = if yeah_nah() { return 5; } else { "X" };
That return is an expression, obviously we mostly care about the expression's side effect which is a change of control flow to leave the function with some sort of integer 5 as the return value (the function signature tells Rust which kind of integer this 5 is) - but the expression "return 5" itself does have a type, it's ! aka Never, an Empty Type because no values of this type can exist - because the side effect will change the control flow before we need a value.Rust's type inference engine is fine with this because under type arithmetic all the values fit in a single type, the type of "X" - &'static str - there are no values on the left to disrupt that.
Regardless, all of the languages above except C and maybe Object Pascal can still have control stop in the middle of an expression, since an expression can throw an Exception (indirectly). Returning is not significantly different from that.
if !condition
return
end
In other words, the return in question has no argument. The unusual (but not unique) aspect of Ruby here is that if/unless can suffix the "then" block and not just prefix it. return if !User.exists(id=100)
reads just fine and takes less letters. I don't think unless is "bad", it's just unnecessary> It indicates you don't understand Ruby if you find yourself using an "unless" in a complex boolean operation. It's meant for simple cases like an inline return statement
unless `git status -s | grep -v 'RAILS_VERSION\\|CHANGELOG\\|Gemfile.lock\\|package.json\\|version.rb\\|tasks/release.rb'`.strip.empty?
abort "[ABORTING] `git status` reports a dirty tree. Make sure all changes are committed"
end
is not exactly very readable and this unless connection.adapter_name == "Mysql2" && options[:id] == :bigint
if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
options[:default] = nil
end
end
isn't glancable either. Just random snippets from Rails code. You can see how author wanted some bonus points and used unless, if, and ! too. Sure it isn't hard to figure out but it makes it needlesly obtuse unless python.major_version > 1
other zen of python's rules that did not live up the expectations- beautiful is better than ugly => tell that to numpy or pandas
- readability counts => (¬з¬)
- if the implementation is hard to explain, it's a bad idea => then the World is full of very bad ideas
Really curious to see the one and only one obvious way these are handled.
However, a lot of developers love very strict style guidelines. I've never understood why, but it's a discussion I get into very very often.
In a shared codebase, I think there should be as few (equally effective) ways to express an idea as possible. (Obviously there can be more- and less-efficient ways to write a function; I'm referring only to style.) The more arbitrary choices people have, the more time is wasted on choosing one.
If you're coding alone, that's fine - you just pick what you like, and spend as long as you like making that decision. But in a shared codebase, developers writing in different styles is a net negative on quality & readability. Spending time debating which style to prefer is a worthwhile, but unnecessary time sink. Automatically enforcing a pre-defined style is efficient and effective - that's why Prettier exists, is widely used, and has few configuration options.
I highly disagree! In my experience, forcing arbitrary style leads to so much more time wasted as people debate exactly which styles to enforce.
> But in a shared codebase, developers writing in different styles is a net negative on quality & readability. Spending time debating which style to prefer is a worthwhile, but unnecessary time sink
I disagree with this too. I have never found this type of style to be something that actually impacts readability and quality. If you using `if !` and I using `unless` is the problem with the codebase, that's not so bad.
I have to say, this is why I'm a huge fan of Python's black. It lets me write however I want, while you get to read my code in a consistent style.
> forcing arbitrary style leads to so much more time wasted as people debate exactly which styles to enforce
If this is happening often, I would unambiguously call that a problem, though. It should only happen once (and then, when enough has changed in the frameworks, maybe once again). This is exactly the philosophy Prettier takes: it is purposely difficult to change. It works really well as it is, and keeping it constantly optimized for community preference would be an anti-pattern.
> I disagree with this too. I have never found this type of style to be something that actually impacts readability and quality. If you using `if !` and I using `unless` is the problem with the codebase, that's not so bad.
I more or less actually agree with you on this. Small stylistic differences are generally negligible. But variations add up, and some are more egregious than others. I consider this more of a guiding philosophy than a rule; `if!` vs `unless` on its own is not a real problem.
I'll never understand these sorts of articles because they criticize the subject for not being what they explicitly aren't.
"I hate spoons because they can't cut like knives"
Just an example: "if x is not None:" is syntactic sugar for "if not x is None:".
String formatting: there are f-strings, % replacement, .format, probably more methods.
And that's fine, languages evolve over time, add better ways to do things, and don't deprecate the old way.
It's the hypocrisy of flaunting that motto while the language is so full of the exact opposite that always annoys me.
I know boolean logic pretty well, but `unless !something` still trips me up to this day.
E.g. `I can't get no satisfaction` is structurally equivalent to French `ne ... pas`, i.e the second `no` is a reaffirmation, not a negation.
On the other hand `read this unless you're not a developer` is a logical double negation and thus equivalent to `read this if you're a developer`. In practice of course there are usually implied subtleties that make the two not entirely equivalent (the same way synonyms are conceptually interchangeable but may carry different subtext).
EDIT: I can't actually think of any true "double negative" in English that isn't indirect (e.g. `indirect` being synonymous with `not direct` thus `not indirect` being synonymous with `not not direct` and `not not` cancelling itself out). The only direct forms I can think of behave like `not ... no` in my first example, i.e. re-affirmations of the negative rather than double negations.
I think most of the complaints about "double negatives" (where the Rolling Stones line is usually cited as an example) are stylistic preferences or intentional misunderstandings based on arbitrary prescriptions (which are usually based in other languages like Latin that are perceived as "purer" or "more sophisticated").
A: Do you like your hometown?
B: Well, I don't not like my hometown
In boolean logic, not false is true, but in English there's often a middle ground. See also https://en.wikipedia.org/wiki/Law_of_excluded_middle
I'd argue that in "I don't not like my hometown" not-liking acts almost as a compound verb, similar to "disliking" (which is just using a Latin prefix to say "not"). Resolving the double negative results in information loss because it omits the subtext (which in this case is the important part of the answer).
In other words, saying "I don't not like my hometown" makes sense because "I don't like my hometown" implies negative emotions (disliking) rather than an absence of positive emotions (liking). The difference could also be conveyed in emphasis ("I don't like my hometown") but this is more subtle and easier to misunderstand.
"Ain't nobody got time for that" is clear, likewise "I didn't go nowhere" and "He ain't take nothing from nobody" and when something is not clear ("That ain't nothing" could mean it is, or it is not, something) context usually suffices.
Same in Spanish!
I think you're making a very good point here – understanding "unless" (quickly, i.e. without having to think about it) very much depends on one's understanding of the English language and/or one's mother tongue. Sure, one can probably get used to it (like one gets used to what "if/else" means etc.), but it definitely increases the mental effort needed to read & understand code.
If and unless are English language keywords besides being Ruby keywords; and in Spanish, the word for "if" and the word for "yes" only differ by an accent mark.
"No! (Wrong!) There is no ..."
Bringing it back down to 1 level of negation, for it to make logic sense again.
- `if` and `unless` are for guards. The meat of every method, (the happy path) is at the bottom of that method.
- If there are legitimately two options that branch on a conditional, I try to make sure that both branches return the same type. (feature flags, a/b feature testing, etc.)
- If I can refactor code to a case statement, I will 95% of the time
This article screams, "I don't use a linter" or "I work at a company that doesn't have a style guide."And I'll take it one step further: I see a multi-line branch as a code smell in and of itself. If something in a branch takes multiple statements, I am of the opinion that the body of that branch should probably broken out into a method.
So if we're going off the article title, my code might look something like
val isNotRubyDev = !user.isRubyDev
if(isNotRubyDev) return
// ...read articleSince unless has a not built into it, it has a lot of potential to confuse people. In my experience, guard clauses are the only place where they make sense.
def mute_mic
return unless mic_active?
..
end
In this sense, you know that the entirety of the function is an expected (positive) case.I always insist on not using 'unless' if it's not clearly readable as an english phrase or if there's an 'else' as well.
A similar pattern I follow with 'if !'. If there's an 'else' condition always put the positive check first and the negative one in the else, rather than the other way around.
Footguns are equally possible using complex 'if !' statements.
return unless token.present? return unless user.valid? return unless foo = get_foo(user)
Its extremely readable and easy to grok imo.
1. It is a negative, and that is a great thing when there are boolean conditions. Yes, you can wrap the whole thing in brackets, but when doing a visual scan quickly over code, they're easier to miss than the giant `unless` token which guarantees that you won't have an early closing bracket. The more the number of terms go up, the more I have to be fastidious with watching where brackets begin and end.
2. It hints to the expected flow of things. Isn't the first more readable to you?
return :allow_air_travel unless self.nuclear_war_ongoing?
return :allow_air_travel if ! self.nuclear_war_ongoing?
Or I'll put it another way. If you do a search of your own hacker news comments on BigQuery, will you never find the english word "unless" outside of a Ruby discussion there? You could say "if not" why bother using an extra word? Or what about "not true" do you ever say that when you could just say "false" like Dwight from The Office?Basically my argument boils down to this:
Bears. Beats. Battlestar Galactica.
You don't understand Ruby if you think there is something wrong with trying to have more than one option to do things, imperfect shortcuts and more human-readable code.
Unless works really well when there is one named boolean variable after it and it's named correctly or if the condition is simple to understand.
It also works well when used inline and without an else statement. For other cases "if" is better-suited. But then again "unless" is another option in your tool chain that makes for a more elegant programming language if you're not a stickler and not constantly finding yourself pining for a platonic language.
user = User.find(2000)
return unless user
It'd be easier to miss the "!" character in the case of "if !".It also makes code read better in English.
I disliked it as well in CoffeeScript. The use of else blocks is especially annoying, but even otherwise I don't like it. Some might say you shouldn't use it with an else block, but this is not realistic if you work in a team.
Yes, sometimes you have to refactor into a traditional if. Sometimes you make the conditional its own method (which you’d end up doing anyway, assuming it didn’t stop at two or three conditions.) These are light tasks as the project grows.
Starting a line with unless, on the other hand, does not work for me. It is also awkward in short English sentences. And long English sentences, where beginning with unless is more appropriate, don’t have analogies in single-line ruby statements.
That's a problem for those who learn it as a second language and have to translate "unless" to "if not" every single time.
However I find some of the examples / justifications unfortunate e.g.
> I find the second option less readable because it suggests that raising the error would be the normal thing to do, when in fact it’s the exceptional thing to do.
It’s not tho. The normal thing to do is to reject access, being an admin is exceptional. A restricted endpoint should absolutely assume the user is not authorised.
send_email unless user.suspended?
Here we see that we normally send email - except for the exceptional case of the user being suspended. X if Y
X unless Y
syntax. I want to see some language that brings back the "noise words" from COBOL for that matter.(when CONDITION BODY)
(unless CONDITION BODY)
The value of the statement is BODY or NIL if it wasn't executed.
I can't remember a time when it wasn't clear (though you can always build usage that are unclear).
(cond ((massivep building)
(print-mass-warning building)
(install-antigrav-module building)))- Many remarks concern the difficulty of understanding "unless" for those with a first language other than English. It may be interesting to note that Ruby was created in Japan, and that "unless" has been in the language since the very first release. [1]
- Matz has a very interesting and famous quote about the principle of least surprise: "it means the principle of least surprise after you learn Ruby very well." [2]
- Computers don't care how programs are structured. Businesses don't care how systems are architected. The canvas does not care how paint is applied. Software engineering is an art. [3]
---
1. You can verify this yourself - check `parse.y`: https://web.archive.org/web/20071109044522/http://eigenclass...
2. The rest of the quote is also wonderful: https://www.artima.com/articles/the-philosophy-of-ruby#part4
3. The article that contains this line has fundamentally altered how I view our profession: https://www.neversaw.us/2022/01/30/the-canvas-cares-not/
Not unique and not the best, but popular and usable enough to be fascinating for anyone who's given more than passing thought to how difficult it is to communicate in any one realm separately (let alone across realms).
For example, I had a meeting with POs recently where it took an hour to communicate a concept in English and distill a logic diagram from it. I'm not placing blame on English (although some would [1]), POs, or myself. It's just common experience that communication is difficult.
Personally I always enjoy flexibility and expressiveness when writing (English or code), usually when reading (English or code), and sometimes when debugging (English or code). So I like Ruby pretty well. YMMV.
[1] https://news.ycombinator.com/item?id=33513666 & https://news.ycombinator.com/item?id=22689959
Just because of that they are far superior to if/else/unless, unless ;-) you're using the ternaries to execute statements with side effects instead of just returning a value.
if 0
puts 'true'
end
which will print true. I think there is a much greater chance that 0 being true will cause problems with programmers from other languages than using unless which is quite natural to humans. Ruby is first and foremost a language for humanity, and probably dead last a language for ease of implementation.> As a general rule I think that if a language has some feature for which there is already a commonly understood syntax across other languages, it should just use that syntax. If you’re introducing a complete paradigm shift, then that’s fine, but unless is not that: it’s just a different way to write if ! and people jumping back and forth between ruby and, say, javascript, now have one extra idiosyncracy to keep in mind.
Most statically-typed languages don't let you evaluate 0 as a boolean.
IMO, 0 is a value, and values should be truthy. 0 being falsy is only a wart from C.
I don't know off the top of my head, but I would assume that yes, zero is truthy in Erlang as well, since Elixir is fully compatible with it.
% cat words
all
these
worlds
are
yours
Which I believe is one of the reasons Perl allows it: % perl -ne 'print unless /^a/' words
these
worlds
yours
And Ruby does too: % ruby -ne 'print unless /^a/' words
these
worlds
yoursHonestly, I find negative checks like `if !something` ugly, I would much rather check if the result is true than !true. The same with unless, I would prefer to check unless false than unless not false or unless !false
I had actually decided to write them as if statements because that way I didn't have to make one particular logical check (I can't really remember what the logic was, just that it had to do with language and Swedish language legal documents did not have a particular rule that Danish ones did.
So a senior Ruby dev who admittedly was a lot better than me in a lot of things but did have the habit of making incredibly boneheaded bugs sometimes, went through and rewrote all my if statements as unless statements - inadvertently reversing the logic.
A week later bug comes up I get blamed because it was the thing I worked on I was embarrassed, and I went through the code fixing but then I thought - wait I wouldn't have written this as an unless because I need to do one extra statement that way! After digging through I caught our senior dev, the very guy blaming me for causing bugs.
Ah the revenges of youth are very sweet.
!(valid_token && !expired)
This expression is also a double (triple?) negative, and a lot harder to parse. The correct approach is to make the `valid_token` flag take `expired` into account, or add a third variable that represents validity, not append `expired` to the unless clause. This keeps everything readable, if the variable names are descriptive enough: valid_token = token.valid? && !expired
return 'invalid' unless valid_token
It's also damning that the author uses `unless ... else` as his starting point for the critique, while he is aware that the style guide says "Do not use unless with else" (briefly acknowledged in the last section).Although I like 'unless' in Ruby, Perl, CommonLisp (sometimes a condition is just naturally the other way of how 'if' wants it), I acknowledge that people get emotional and really hate it. Maybe a better alternative would be 'ifnot' to avoid the ! operator and the parentheses.
Basically, do_something unless A
Then it's simple now to manipulate the inside of A, as you only take care of the positive ! That's beautiful and simple design.
Example is you want to validate something.
raise Error unless someValidCondition.
Now decompose someValidCondition by just using positive connectives, simple right ?
Do I think Ruby NEEDS unless? No. Does this keep me awake at night? No lol
Use the tools.
(unless foo (error "Foo should not be nil"))
In less functional code, you sometimes see it for default values (though "OR" seems to be more idiomatic for that case): (unless foo (setf foo 3))
Regardless it has few of the downsides that TFA mentions (the sole exception being introducing a double-negative for unplanned compound test statements), but I don't find something like (unless (and foo (not bar)) (error ...)))
To be that unclear; we want both "foo" and "!bar" or error. (or foo (error "Foo should not be nil"))
plus it propagates the value of foo in the non-error case. send_email if !user.suspended?
or send_email if luser.suspended?
The latter could easily be an aggressive dev sending abusive emails to users they dislike enough to call lusers, at first glance at least ... send_email if not user_suspended?But that's actually ideal in this context, a really low precedence operator is exactly what you want because you can swap `unless` for `if not` without worring about extra parentheses:
send_email if not user_suspended? || user_opt_out?
I generally avoid `not`, `and`, `or` keywords.
[0] https://www.google.ru/books/edition/Foundations_of_Mathemati...
I "grew up" in my career as a rails dev myself, but I favor things that are immediately clear to the broadest amount of people possible these days. And if you truly miss these constructs, in any language where you have metaprogramming, which is most these days amongst the broadly used ones, it should be trivially to implement them, should you insist on continuing to use them.
I use Elixir for my "fun" language these days, and they provide unless/2 as a macro, (if/2 is also a macro), but I haven't found myself reaching to use it once.
X = 5 unless invalid_token
Much better than
X = 5 if !invalid_token
Here the humans inability to easily process double negatives, is now an argument for using ‘unless’ in the right circumstances.
Obviously if your specific language doesn't make use of it, or makes use of it in a different way to ruby (e.g. is your natural language treating the second negative as a negation of the negative, or an intensifier of it?) then ruby's grammar will seem unnatural. But that's going to be true for anyone who's trying to map their natural language onto a programming language whose grammar is different to theirs.
return the_obvious unless something_improbable_holds
the_most_astonishing
This also align well with the lake of scalability of the number of condition: I doubt that in usual prosaic English its common to use "unless first-condition or second-condition or … ultimate-condition".Of course it is logically equivalent to `if not`, but it is pragmatically not conveying the same information at human interpretation level.
If I've neglected to address your specific criticism I'm happy to do so here
A long time ago, a friend code reviewed and asked me to change to the most common condition first, as advocated in the Code Complete book (which I hadn't read at the time).
I still think it reads better if/else as true/false, but I am also big on having standards and a coding style, even if I disagree with them.
I mean, unless there's a lack of coding standard I will follow it.
def my_method(required_thing)
raise 'required_thing is required' unless required_thing.present?
...
end
In practice, this hasn't been as issue as a consequence of the tooling and ecosystem around Ruby. def my_method(required_thing)
raise 'required_thing is required' if !required_thing.present?
is a problem ?I find inheriting code riddled with witty logic puzzles distasteful, so prefer my predecessor to have written Python, which constrained them from crafting too many.
Some people want a project to be a series of cryptic crosswords. For them perl/ruby/C.
When I write perl/ruby/C, I restrain myself from filling it with cool puzzles, but I usually don't get to inherit code from myself.
Things like this sound like: "I have this characteristic, therefore everyone has this characteristic."
Some of us ain't got no problem with double negatives, and often find that "unless" tightens up code nicely, especially for single negatives.
That said, I think the author is overselling the need to not use such features. Despite my aversion to using such features, I have no problem reading them.
`return if not valid?` or `return if invalid?`
Both make sense in my brain, whereas
`return unless valid?`
Feels like I need to make another connection to understand it
if not no_unavailable_items:
...
else:
...
unless on it's own is fine, but humans will misuse it if you're not careful.You can address it with code reviews/standards/linters and get the best of both worlds.
(the headline however, is an exceedingly bad example)
if cond
a = 1
end
leaks a to outside and a will be nil if condition does not happenIt means "skip if zero".
The double negative always trips me up.
I had to write "don't skip if not zero" next to it the first dozen or so times.
unless condition
# do A
else
# do B
end unless user.has_errors?
user.save
end
IMO better than `if !user.has_errors?`, at least for me: I will read it as "unless" in my head even in languages that only have `if`. You know what? I will just start using `unless` even harder, I will even make a rust macro for that.The spec says that [t]he functions delete-if-not and remove-if-not are deprecated.
But all that is wrong is their double negative name. remove-if-not is precisely the same thing as keep-if, which is too useful to deprecate. Numerous languages have a function like this, sometimes called filter or similar.
IF some-expression
ELSE Do-something
> Read this post ‘unless’ you’re not a Ruby developer
But:
> Read this post if you’re a Ruby developer
Surely?
Seriously though, the main way Ruby went wrong was an obsession with this entirely superficial kind of expressiveness and simplicity. Rubyists will lard their code with all sorts of "convenient" and "easy" features, to save a few keystrokes here and there, at the expense of bloating their APIs and hiding weird, hard-to-reason about magic.
do not do <statements> unless not <expression>
of some fun programming language whose name I forgot.