I’m one of those weirdos who can inline a double negative instantly so, I didn’t read it yet but I probably will because I’m curious about the title anyway
Oh my dear lord I didn’t think I’d actually disagree with the f’ing article but I had like 8 cases today that made me wish I had a built in if-not keyword! I mean, every language with the billion dollar mistake probably needs a language feature designed to pay back some of that.
I don’t disagree with the essay, and I do generally dislike “unless” as it’s a cutesy statement which does not pull its weight (it would probably be better if there was no “else” clause at all).
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.
Fair enough argument for that particular example, but I think the author's point still stands for the other example (if !user.suspended do send_email end)
The worst thing about `unless` and double negatives, is having been raised in a culture with different double negative rules than English. In Italian a double negative is still negative.
I know boolean logic pretty well, but `unless !something` still trips me up to this day.
There are two different kinds of double negatives and English actually has both.
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").
True! But that still falls into what I said about true double negations usually having subtext that justifies their existence. Compare "I don't hate it".
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.
The phrase for what's going on in that Rolling Stones lyric is "Negative concord" and yes, it's normal in many languages and that includes a lot of non-prestige English variants.
"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.
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.
I think we're going to have problems if we try to make all languages consistent for users of all languages.
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.
Just to help people out. If you can't write out the title as a conditional statement in Ruby, you're not the intended audience. Though it still may be interesting as a discussion in conditionals in general.
I don't have a problem with double negation but I hate ruby for this kind of design - pointless aliases for everything.
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.
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.
> Everyone has an individual background. Someone may come from Python, someone else may come from Perl, and they may be surprised by different aspects of the language. Then they come up to me and say, 'I was surprised by this feature of the language, so Ruby violates the principle of least surprise.' Wait. Wait. The principle of least surprise is not for you only. The principle of least surprise means principle of least my surprise. And it means the principle of least surprise after you learn Ruby very well. For example, I was a C++ programmer before I started designing Ruby. I programmed in C++ exclusively for two or three years. And after two years of C++ programming, it still surprises me.
I came to Ruby from Perl, and I love `unless`. One of the first things I do in any Lisp is build my own unless macro unless the dialect already has it :)
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());
It's not pointless, it makes code read better. It's another option to have. It's not meant for every use case. 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
God that's such a landmine when reading code. Seeing a return without an explicit change of scope... Why ? To save one line ?
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.
If you're returning who cares about the scope? Are you saying visually you'd like to have indentation inside the if body? If that's the case there is also nothing stopping you in most languages from doing something as nasty as:
I do because return means end of scope. Except in your example it hides the fact that it's creating it's own scope.
I'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.
> 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.
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.
Those are called Guard clauses and they are implement in plenty of systems.. this paradigm has nothing to do with "Ruby".
In 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.
I'll admit I'm not a Ruby developer, but wow it allows return conditionally inside an expression? What were the language designers thinking?! I can't think of any other language that allows return inside an expression (or break/continue). Let's see: C, C++, C#, Python, Javascript, Object Pascal, Java, Rust; all nope. That is indefensible.
This isn't exactly true. It's a syntax error to put a return anywhere where the grammar expects a value expression, because it's one of few constructs in Ruby that doesn't return a value (contrast with e.g. "if" and "def" and "class", all of which return a value).
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.
> I can't think of any other language that allows return inside an expression ... Rust
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.
You've listed languages that separate statements from expressions in their syntax. There is a whole other family of languages where choose consists entirely of expressions - starting with LISP in the 50s, and including virtually all FP languages today (F#, Ocaml, Haskell, SML, Clojure). Ruby happens to be one a member of the latter group. To be fair, most of the languages in that group don't have an equivalent of return/break/continue either - I think only Ruby and some Lisps do.
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.
It's not doing "return conditionally inside an expression". The above statement is equivalent to:
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.
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
And I love it for exactly this kind of design. It's what makes well-written Ruby read well. And while it's one more way of making badly-written Ruby read badly, in my opinion at least with Ruby you have the option of writing code that reads well. I've yet to see Python code that looks readable to me.
Wow, I didn't realize Python did this. So, for example, does it enforce functions with single returns or ones with multiple return sites? Also, does it prefer shallowly nested functions with guard clauses or highly nested if / else clauses?
Really curious to see the one and only one obvious way these are handled.
I love Ruby for this kind of stuff. You don't have to have arguments about which syntax you prefer, you can just use the one you prefer and let your coworkers use theirs.
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.
Hm, I have the exact opposite interpretation. This is exactly the kind of feature that leads to arguments. If `unless` didn't exist, what argument could people be having about using `if !`?
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.
> In a shared codebase, I think there should be as few (equally effective) ways to express an idea as possible...The more arbitrary choices people have, the more time is wasted on choosing one.
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.
Fair enough, I understand every team is different.
> 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.
People can prefer the "one obvious way" and you can use a language that has that as its motto. Ruby was built with "developer happiness" as an objective and I don't see it changing any time soon.
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"
The problem is that what is obvious depends upon your way of thinking. To me very little in Python makes sense, but I understand that it does to someone and that’s cool. There’s no reason to hate on a language just because it doesn’t click with your way of thinking.
Well I really showed myself up as commenting on a topic that I know nothing about! :-)
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 wouldn't use this. Relying on tricky operator precedence can trip up your readers. It's better to just wrap everything in parentheses.
I also use parentheses when mixing && and ||, even though their precedence relationship is (probably) more widely known.
The "logical negation" sign used to have the vertical line as long as "|", so e.g. "¬A" looked more like "‾|A" but fully vertically aligned with the letter. Sadly, this glyph fell out of usage in fonts in favour of "minus with cedilla/descender".
I'd always understood it came from Frege's Begriffsschrift, a small descender from a horizontal line (part of a larger diagram), so like a short wide T, then lost its right arm later. Would you have some examples? (a quick google images shows nothing like that ...)
It was definitely a thing in Soviet works on mathematical/symbolic logic. But here's an example from Curry [0]. As I said, it was mostly a stylistic choice.
Funny, when I read your point, I was sure and ready counter with an argument that code editors must highlight symbols and unary operators differently (colours, maybe spacing as well). But I just checked this on a couple of Ruby and JS highlighters and they are actually not! :scream:
In Perl you can write postfix conditionals: `thing() unless $condition`, which at least is more natural usage of "unless" to a native English speaker than the other way around. That's the best way to use it IMO.
I've written ruby everyday for the past 5 years. I still cannot read an unless statement and understand it first time. Most of the time i'm translating it to `if !` anyway.
Without syntactic sugar that differentiates a language from another, I see no way for a new language to become popular. So while some decisions may seem confusing, they helped gathering interest. IMO ruby and python became so popular because of their quirks.
"Unless" should only be used when the imperative condition is simple.
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 !".
statement unless condition is a wonderful bit of syntactic sugar. It reads like natural English if the variable or method it is evaluating is named well.
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.
Don’t you know the word after awhile? Ruby core and standard libraries have many English words in them that are harder to learn than basic one-syllable words on the level of “if not.”
The real problem is that all negatives make it harder to understand things. They open the door for double (and triple) negatives to find their way into the code, and then bang: The only person who can read it is the person who wrote it.
Since 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.
`unless` is generally used for guard clauses (eg `return unless authorized?`) or when there’s only one branch. You don’t tend to see `unless` used with multiple branches.
I take issue with the word unless. "Un" means opposite and "Less" means, well less, so unless means more. So `unless` should just be a straight alias for `if`.
The "un" in unless isn't the negating prefix "un-"; it's the word "on" with a change of vowel. The etymology is "on less[er condition than]," e.g., "Abort the launch on less than the rocket is deemed safe to fly."
It’s a bit awkward to grasp initially but personally I like it. I don’t wish it in all languages but it works in ruby. It especially works well in templates and checking collections.
Honestly, 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
Reminds me of my pet hate in user interfaces - having a check box for a negative condition such as "disabled" rather than having "enabled" and changing the default state of it.
I've been writing Ruby for 20+ years and "unless" (the whole language, actually) immediately clicked for me when I first learned it.
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.
This is basically how I use unless too. Complex conditions are complicated enough to grasp without inflicting an unless on me. But for simple cases it is a nice to have feature.
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.
FWIW HN does not support markdown lists (or markdown anything other than emphasis and code blocks really), you need to add an empty line between your “list items” so the comment parser treats them as separate paragraphs instead of munging them together as just one.
I really don’t understand the author’s point about adding extra conditions. They admit that many people will find a single condition with `unless` more readable. They then complain that it becomes unreadable when adding another condition. OK, so swap it out for an `if` at that point. No one is forcing you to keep using `unless` if the requirements change. “You should use a suboptimal solution to cater for unknown future requirements” is a terrible argument.
That’s exactly how I use ‘unless’. Simple on condition (no else branch) use cases in one liners, anything more complex, I move to if. Best of both world I’d say!
That’s the generally recommended style! I hate this post. It’s arguing against something that is well known to be a bad practice (that’s fine), but it seems to put the fault for this on Ruby itself.
I believe this is somewhat prevalent in the Ruby community as of late where people try as hard as possible to "lock down" the language and limit the ways you can get to a solution in the name of the proverbial "ease of understanding".
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.
I love an expressive programming language, it’s why Go makes me feel like I’m brushing my teeth with sandpaper…
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…
I do not program in Ruby as there was no business need but I have rough idea on what it can do. My view on the horror you've encountered is that the so called "programmers" that produce such abominations should be sent back to school.
I understand business idea of having monkeys producing code for cheap but I think this approach is misguided.
> 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.
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.
>"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."
Exactly the reasons I use C++. Well, after crazy performance, efficiency and single executable deployment.
Indeed. I personally never fail to avoid not using "unless", unless there isn't a lack of extra conditions such that not refraining from instead using "if" wouldn't avoid not being less clear.
The author's point is that conditions in code tend to add up.
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.
That strikes me as a premature optimization. If you have a single condition, and unless makes it more readable, use unless. If the conditions do pile up in the future, change it to an if.
Great, now you've got this weird logic on your style guide that your code reviewers will have to point to, and hopefully the new engineers on your team will remember. But maybe they're in a hurry and will quickly add another expression just this one time, multiplied by many engineers.
While I agree with the other comment re premature optimization, when using guard clauses you can also break up your conditions into multiple statements which is arguably the most readable solution. The author’s example could be written as:
return unless valid_token
return if expired?
This scales extremely well if you’re concerned about future conditions.
> “You should use a suboptimal solution to cater for unknown future requirements” is a terrible argument
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.
I used to write my if/else like the author advocates. The "true" condition first, followed by the "false".
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.
325 comments
[ 3.7 ms ] story [ 298 ms ] threadHowever 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.
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.
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.
it's literally just `!` equivalent. Honestly I haven't seen much code using unless in wild. Especially that it is literally shorter to just !
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
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.
I'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.
In 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?
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.
> 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
is not exactly very readable and this 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- 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.
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...
It 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.
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.
It'd be easier to miss the "!" character in the case of "if !".It also makes code read better in English.
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.
Since 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.
In this sense, you know that the entirety of the function is an expected (positive) case.IF some-expression
ELSE Do-something
(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).
Honestly, 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
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 ?
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.
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.
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 not do <statements> unless not <expression>
of some fun programming language whose name I forgot.
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.