"...who firmly believed that the terser your code, the faster it ran. The author crammed multiple side effects into a single expression, used ternary operators like they were going out of style, and generally believed that run time was proportional to the number of semicolons executed, and every variable killed a puppy."
How does one go about countering that mentality in some rule of thumb fashion if at all possible? Could a disassembly illuminate identical push/pop operations on the memory stack as explicit intermediate variables would? Maybe a simple intermediate result expansion timing profile side by side the terse code's profile?
IMO it does not necessarily need an assembler implementation, only if that is more clear.
For performance enhancements where code readability suffers big time, I always want /proof/ by comparing the instructions (and some benchmarks help too).
For some microcomputer basics that was true, as they implemented the 'find that line' part of goto and gosub by a linear search from the start of the program, skipping from line to line.
Having fewer lines, or having the most often called routines at the start of the program would speed up programs, potentially a lot.
Also various constrained environments (calculators and whatnot) where variables were significantly faster to access if they were declared locally than if they were global, or were faster if the name was shorter, etc.
Not to mention the simple fact that, for old interpreted languages, the entire program text had to fit inside memory and so terseness literally saved you working memory.
Similarly, in CPython function-local variables are faster to access than class or module attributes (with the usual bunch of exceptions you always have in Python).
This probably applies to similar languages (like Ruby) as well.
In C64 BASIC, iirc, only the first two characters of the [case-insensitive] variable name mattered - so "vacuum" and "vacation" were the same, since the 'real' variable name was "va"
I once went to a code review in a very, very large (but now defunct) corporation. The author of the code did not allocation the memory they were writing into. There were 8 people at the code review. I could not convince a single person that writing into unallocated memory was a bad idea. "Allocating the memory would be slow", they countered. "And besides, we've tested it and it doesn't crash".
Sometimes you can help people become better programmers. Sometimes you can't. Try not to stick around in positions where you don't have the ability/opportunity to make a positive contribution, is my best advice.
But assuming that the person is willing to listen to you, all of the above seem like good approaches.
What memory were they using? How did they get a pointer to it? Perhaps there was reason to believe it would not be used but they had lost that reason to time. For example on many micro controllers you can have the linker reserve space for various things (mapping structures over memory mapped peripherals is common) so maybe the memory they were using was guaranteed to be available somehow. Or perhaps they really were clueless.
Keep in mind that this is more than 20 years ago, so my recollection of the details may be (probably are!) completely wrong. But this is what I recall.
It was an embedded system with a proprietary language. The compiler didn't clear the variables when they were created (again, for performance reasons). For some crazy reason, the language allowed you to declare a pointer without initialising it. So what would happen is that the pointer would end up being whatever happened to be on the stack previously. The reason that it didn't crash in testing was because most of the code in the system was essentially copy and pasted from other parts of the system. As long as the previously run function had a pointer in the same location that happened to allocate some memory in the heap, and then deallocate it, this function would end up using the same memory and it would be fine. And this is exactly what happened in this case (because most of the functions had similar shapes and pointers were declared at the top of the stack frame). The main reason they were reluctant to change the code was because they had copied it from somewhere else and didn't understand how it worked. Because it didn't crash, they didn't want to touch it.
Introduce them to the concept of static single assignment form, and how it is an intermediate form that enables a tonne of downstream optimisations. Most of those "wasteful extra variables" would be introduced by the compiler anyway, so it's not a problem.
Alternatively, just ask them to prove it. If it's faster, they can benchmark before and after, and prove that it's faster? No proof means you're full of shit. This is the Right Answer in that it's what you should be doing anyhow to avoid performance regressions, but it's way more combative and can cause problems within the team, so must be done tactfully.
> If it's faster, they can benchmark before and after
That goes bad very fast. I've worked with someone who tried winning arguments with one-line microbenchmarks; They even tried to convince me that I should always use undefined over null in JS since one is faster than the other (forgot which).
> Introduce them to the concept of static single assignment form
I failed with this as well; some coders don't trust the compiler/runtime at all. I had people tell me that garbage collectors don't deal with cyclic dependencies or that "simple comparisons" (<, >, ==) are faster than "compound comparisons" (<=, >=).
Craziest one I remember from my Java years (long long ago) was someone claiming that returning values by throwing exceptions and catching them to extract the return value was faster than simply returning the value....
Oh come on, this is obvious. Thrown things are fast. Anyone who's ever rented a VHS tape from Blockbuster knows that returning something can take days. Of course he's right.
To be fair, in R on my machine > and < seem to be faster than ==, at least for integers. But the difference is miniscule compared to the overhead of doing just about anything else.
At best the benchmark results will be answered with "the compiler got lucky that time." Someone like that needs to understand the details of the error in their thinking along with multiple benchmark examples proving it.
It's the programming equivalent of the people who try to put as many complex word in a sentence as possible to look clever.
I met some people who were trying their best to make everything a oneliner because "shorter is better" or whatever reason they had at the time.
The real reason I always saw was that it was making the code "hacker-like" (and completly unreadable) and they could brag about how clever they were.
I've met people who have created entire programs out of nested list comprehensions; "they're faster than for loops!". I don't avoid them myself, but you would be hard pressed to convince me to nest them, or do anything that takes more than one line to express.
Same goes e.g. for Linq. I know of people who would prefer screen-filling methods of multiple nested loops with throwaway datastructures and various checks sprinkled on top to a simple three-liner Linq expression.
Funnily enough, their argument always seems to be "it's easier to read this, since I'm accustomed to
for (x;y;z) if (foo ||(bar && baz)) lorem = ipsum;
But on the other hand I also find that writing code is the same as speaking normal language. We each have our preferences, vocabulary, diction, style etc. I also jumped on the Linq wagon, but cringe when I see for example a Swift etc. reducer. So as long as that person writes his page of code at a decent pace with limited/no bugs I won't hold it against him.
You don't. You reward that developer by allowing them to persue other interests. Or stick them on writing one off tools that will never be customer facing.
Write a small program that uses a well-established compiler, then "optimize it" with all manner of clever tricks. Look at the assembly produced and you'll find nearly identical machine instructions with even more clever optimizations than you could have imagined.
I think the problem is, to some degree, psychological. While the rational me knows there is very rarely any reason to prefer terseness over clarity of expression, the emotional me still gets a little thrill from one-liners and unconventional solutions (it can sometimes last right up until the point where I have to modify or debug code containing the damn things.)
I think it is a pathological corner-case in an otherwise generally useful interest in, and appreciation of, innovative and economical problem solving.
It's because back then V8 didn't save the AST in `SharedFunctionInfo`, so AST was inaccessible, so to avoid re-parsing and generating the AST to use AST-specific heuristics for inlining big function bodies, it used just the source length as part of the inlining strategy. This has been fixed for a while now with TurboFan[0] (also, now you can call `ast_node_count()` and get the AST count directly).
I think it's less noisy and nicer to look at then an if-else, provided the statements aren't too long. God forbid multiple side effects though, even in languages with less complicated sequencing rules than C/C++.
What is your justification for the restriction though?
Personally i dislike the latter form because it requires me to spend extra time reading what is boilerplate that is functionally identical to a ternary.
Edit: Not impressed by the down-voting of an earnest question of someone trying to learn more about how other people see things, HN.
Why are those the only options? I know languages where if/else blocks return values. (Rust, Julia, Python, Perl, R) I also know languages that allow easy ways to embed multi-line blocks into a ternary. (Rust, Perl)
I'm sorry, but i really don't follow your logic, can you try to explain it more clearly?
Does Rust/Julia/Python/Perl/R feature ternary operators?
The classic languages with ternary (C, C++, Java, PHP, JavaScript) all have the deficiency of distinction between statements and expressions. When everything in your language is an expression, the need for ternary disappears.
In Python the if/else block does not return a value.
Python has a ternary operator, which does return a value. It just so happens this operator uses the tokens `if` and `else`. But it's the same thing what you'd write in C or Javascript as `C ? T : F`, would be written `T if C else F` in Python. Different order of arguments and tokens, but it's still the ternary operator and not an if/else block.
You can't get a result from block statements in Python. Just because the ternary operator uses the same tokens as the if/else block, doesn't mean it's the same thing.
My comment, and the comments above it are all about C-style languages (C, C++, C#, Java).
Languages with very different syntax like Python are of course going to have different style preferences. Eg. Python doesn't have a ?: operator at all, just an inline if-else expression, so the entire discussion of "should you use ?: for control flow" is meaningless for Python.
Are you saying this suggests it does something different from what it does? What does it suggest?
boolean_flag
? do_this()
: do_that();
It seems so terse as to be impossible to misinterpret.
The only possibly feasible way i see for it doing something unexpected would be if it were the last statement in a function in a language that implicitly returns the value returned by the last statement in a function; but users of such languages usually are aware that can happen.
Ternaries strongly suggest "side effect-free statement being run for its return value." Perhaps a more apt analogy would be to say it's like writing a method called "getX" with side effects.
As far as i am aware ternaries put no restriction whatsoever on what kind of code you can put in its branches.
I don't buy the comparison with getX either, since ternaries have no name, they're just some punctuation, and even if implemented as if/else word pairs are completely vague.
It might be a convention in some circles. I don't think it's a well-established one, though.
For me, ternaries strongly suggest "expressions being run for its return value", but without any indication whether or not it's side effect-free. In practice, most of the time such expressions end up being side effect-free (getters, finders, etc.), but every now and then you do want to have a stateful expression (e.g. getting something from a map, and storing a default value there if the key is missing in the map). I believe such stateful expressions are fine to use with ternaries as well.
OK, maybe I don't quite literally mean side effect-free. In that case I probably wouldn't think it's bad. What I would not expect in a ternary statement is code that, say, makes a network request, or writes to disk, or changes several different properties on the current object.
> As far as i am aware ternaries put no restriction whatsoever on what kind of code you can put in its branches.
You just called someone out for this:
"If you're restricting what you're saying to a specific language say that bold and as the very first thing (...)"
Are you talking about a specific language now? Because some languages do put restrictions on that. Python makes a clear distinction between statements and expressions, so you don't even get the option to use a ternary in place of an if/else block.
That said, (in general) I disagree that ternaries suggest side-effect free. However, they do suggest that you care about the return value of the ternary expression. Some languages enforce this suggestion, others don't. But even for languages that don't, a ternary that silently discards its return value feels like a "cute hack" to me.
I can't give you a hard reason. I guess when I learned C all examples were like c=yesno?1:2. So I guess I stuck to it and never thought about triggering an action with side effects that way.
So as an example of doing both, consider following Java pseudocode (simples example that comes to my mind now):
Foo value = someCondition()
? map1.computeIfAbsent("key", defaultValueProvider)
: map2.computeIfAbsent("key", defaultValueProvider);
This both gets a value and changes state of the map; which map is queried/altered is determined by the condition. I personally don't mind such code, and prefer it to if/else version.
I have been doing this for a while and I am trying to keep things simple and consistent now. I used to do "elegant" code that used a ton of language features but now my PHP or C# look almost the same.
Seriously though, there's nothing in ternary operator that says it should only be used for side-effect free code. Especially in C++ (or Java), which doesn't even differentiate between stateless and stateful code on the language level.
IMO ternary is a perfect solution for the case where you want to assign or return a different value based on a boolean condition. Whether or not the code in branches is side-effect free is irrelevant.
Personally, where I try to avoid using ternary operator, is places where I don't care about returned value. So "yes" for:
auto foo = [condition] ? possibly_stateful_then() : possibly_stateful_else();
No solid reason, just a feeling. I feel that if-else block communicates intent more clearly when you're dealing with statements - because it can't return a value in languages we're discussing, it's specialized for that job. The same motivation makes ternary better when we care about a value.
To be honest, with that explanation the decision you made has nothing to do with ternaries, and is entirely just the mirroring of the decision the if forced on you.
Also,
> in languages we're discussing
Don't bury that lede. If you're restricting what you're saying to a specific language say that bold and as the very first thing, so people don't end up wasting their time parsing your post in a different context. Rude.
> To be honest, with that explanation the decision you made has nothing to do with ternaries, and is entirely just the mirroring of the decision the if forced on you.
I like to think of it as using the dedicated tool for a job it was designed for. In my mind, it's a good rule of thumb because it enhances readability (compare "the principle of least surprise").
> Don't bury that lede. If you're restricting what you're saying to a specific language say that bold and as the very first thing, so people don't end up wasting their time parsing your post in a different context. Rude.
Why? Come on, at this point in the thread we'll still discussing C++ derivatives. I thought it was obvious.
Also: if a lanuguage has an if/else as an expression and has a ternary operator, then IMO there's a problem with that language being cluttered with duplicate syntax for the same thing.
I agree. If a function isn't side-effect-free, then you've got other problems besides whether it's used with a ternary operator.
My primary language, Object Pascal, even goes to the length of naming functions (return values) and procedures (don't return values) differently. It really helps to focus the mind on the purpose of each: one almost always expects procedures to do something and, most likely, modify something that may introduce side effects. Functions are almost always side-effect-free in "decent" Object Pascal code.
Ternaries are often used as single-usage substitites for a variable, but it's not rare to see the ternary statement simply copied somewhere else if the same value is needed again.
Using ternaries only when it is free of side-effect can both helo avoid a class of unnecessary errors, and add meaningful semantic information that some code is, or should be, side-effect free.
It would be great if the compiler(s) in various languages supported enforcing it, but sometimes you take whatever you get.
What do you mean "block-oriented"? I'd say this is part of Rust's ML heritage, and comes of being expression-oriented: if is just an expression, and evaluates to a value like any other expression.
I tend to like ternary operators and similar operations but the way python handles it really bothers me, I think because it essentially overloads the `if` keyword, and totally violates the if-then-else norm of conditional statements (ternary operator included). then-if-else is stranger than a do-while loop, but at least there's an occasional reason to use a do-while loop.
It bothers me a little, as well. I tend to think of it as a special type of comprehension, since it follows that format as opposed to the standard if statement.
You lost an "if" there :). setf will complain on trying to set 5 to 7.
What you meant is:
(setf z (if (> x y) 5 7))
A nicer thing that I like about Lisp's "everything is an expression" is something that is considered as code smell by some:
(setf foo (or var (compute-default-value) +some-default-constant+))
This works in Common Lisp, where the only value considered logically false is NIL, because or will short-circuit (as expected), returning not T, but the value of the first logically true result.
All of the above. More specifically, correct meaning that it's doing what it's supposed to be doing (and figuring out what it's supposed to be doing will take a few brain cycles too), and nothing more.
My opinion is that code should be written in a way that makes it possible for an intern to read and update. I work with interns and fresh-out-of-college grads, and I need them to be productive too.
To be honest, I think nested conditionals of that form are hard to read whether you write them as if/else blocks or ternary expressions :)
I'd first look for a way to try and avoid the whole situation, if possible.
After that, what I feel is more readable depends on what formatting the language allows, length of identifier names, what the subexpressions look like, etc.
Then, after careful consideration, I'll just follow any style guide and thus prefer if/else blocks, especially if a ternary would silently discard its result.
Legendary troll on comp.lang.c and elsewhere. His catchphrases include "Unix is dying, and the world is better for it." And "I, on the other hand, prefer honesty."
You might be joking, but I know at least one major electronics company where the automated machine tools will send text messages to the plant manager in ever increasing frequency, when scheduled maintenance is coming up. Because otherwise those plant managers wouldn't allow the required downtime.
In all seriousness, management who takes things like static analysis results seriously is a valuable asset. We should consider teams that have management like this as superior to the ones who don't. They clearly value foresight and planning and will probably be less likely to host a chaotic/careless work environment.
At my $dayjob, I have a problem with a cow-orker with a position slightly superior to mine ("first among equals", former team lead of the group of projects to which I'm assigned) taking results of static analysis too seriously - to the level of cargo culting. When bored, he'd introduce "fixes" to reduce number of issues reported by SonarQube - "fixes" I have to revert every now and then, because they reduce quality of the code (sometimes also introducing bugs through carelesness), all to optimize the Sonar metric.
To be clear: tools are fine. It's people that are the problem (as usual).
It also strongly depends on the type of tool you use. If by "static analysis" you mean "Clang screams at me if I'm juggling with razor blades", the results mostly seem to be rather useful.
But I've yet to see one of those code-visualizing tools based on metrics that convinces me. The results I've seen so far are either obvious ("I already knew that, thanks") or inconclusive and hard to interpret. So, to me those tools seem to be more of a way to try and convince your manager that you should be given time for refactorings than actual utilities.
I've been wondering if there could be a benefit in analyzing the commit history of a project instead so that one could somehow at least visualize obvious problem areas ("every third bugfix has to touch this piece of code, so we should really have a proper cleanup there...").
I thought, "gosh, that seems at least marginally useless; why wouldn't that short circuit?" and then just tried it. I tried a few variations, and about the only use I can think of is to signal as a side effect for when the dict is made.
Fun trick to learn exists. Though I would really like to know what led up to it, since it seems ... unhelpful.
Edit: Looking at other comments, I did try popping off that entry in the dict, and no, it didn't have anything in it (it doesn't make the dict anything like defaultdict).
Binary operations will do it, like in `True or foo()`, but there's no real lazy evaluation machinery for parameters to a function. (Yet? I bet someone's working on it... at least I haven't tried any packages like lazypy for it.)
This is why I've started to think about () as the Invoke operator. Seeing it should always make the programmer assume that whatever that operator is to the right of will likely be invoked with possible side effects.
after the author thought "I need to subtract its square from itself, not just square it". I can see how this could occur in numerical code.
I've noticed there are two opposing schools of thought on issues of "insane code" like this; on one side there's "use the language to the fullest if it makes sense to", and on the other is "avoid anything that might be the slightest bit confusing". I think that taking either of those to their logical conclusion is not a good idea --- in the former case, you'll end up with extremely dense and (initially) difficult-to-understand code, but the latter case will cause a gradual degradation of code into something approaching the verbosity of Asm, but with none of the benefits (e.g. "ternary operators are confusing, don't use them; multiple levels of precedence are confusing, so always fully parenthesise expressions; nested parentheses are hard to read, so don't use them either and only do one operation per statement; boolean expressions are confusing, so always use if/else; nested if/else are confusing, so don't nest and always refactor to use a function call, etc. etc.)
It does seem that different languages vary in where they are on this scale, with more "exotic" ones like APL tending highly towards the former (interesting related discussion: https://news.ycombinator.com/item?id=13565743 ) and C#/Java towards the latter. The ideal is probably somewhere in the middle.
If you're not going to use it you're better off using a language without it - otherwise you pay all the costs (because you have to understand it to be able to read code, e.g. libraries you use) but get none of the benefits.
> a gradual degradation of code into something approaching the verbosity of Asm
there's a simple rule of thumb - if you can't immediately and unabmiguously deskcheck the expression as it is written, it's too complicated and should be simplified.
Depends on what kind of pride we are talking about. I know developers who hold a lot of pride in how readable and understandable their code is. It is a pleasure to work with them and said code.
"I've noticed there are two opposing schools of thought on issues of "insane code" like this; on one side there's "use the language to the fullest if it makes sense to", and on the other is "avoid anything that might be the slightest bit confusing".
This is a false dichotomy. How about "use the language to it's fullest, within reason (avoiding things like multiple side effects per source line), but when non-obvious code is written, document it."
I'm of the opinion that you should express your intent as clearly as possible and let the optimizer handle making your code fast. It should be rare that writing "dense" code actually gives you performance wins.
Nope, haven't had to maintain it in years. I did have to port it to Python3 which was a pain & grew it by ~30 bytes. The minifier was myself. It could actually be tighter but I optimized it for gzipability since I put it through a build tool to produce https://github.com/serprex/Crumb/blob/master/Crumb.py
> The people who would benefit from the warning don't have the necessary background to understand it.
In my experience, this is accurate, but underscores a teaching opportunity. If you show Joe Beginner how to get the warnings, explain how they apply to the code and why the warning was good, they may slowly begin to appreciate the warnings. So that even if they don't understand, they won't necessarily dismiss the warning next time -- instead they'll go ask for help.
I used to think I had a pretty good grasp on how the compiler was going to encode things.
After spending months looking at disassembled FreeBSD kernel C code in a profiler, trying to hunt down cache misses, I'm realize that it is hard to count on anything and that my instincts, even after 25+ years of C programming, are often wrong.
Looking through C# docs (which I assume this is, but don't have experience with), it seems A's value is changed to be the previous value of a, minus itself (ie 0) multiplied by a? In other words:
I wonder if, from this: He gave as one example a book from an apparently-successful author (sales of over four million and counting) who firmly believed that the terser your code, the faster it ran. The author crammed multiple side effects into a single expression, used ternary operators like they were going out of style, and generally believed that run time was proportional to the number of semicolons executed, and every variable killed a puppy.
If the described author could be identified? Perhaps not absolutely, but they do have some specific stylistic quirks combined with a particular sales level.
I noticed that books in general have different constraints as well in not having an infinite vertical view of the code unlike a text editor. The act of publishing to physical paper imposes constraints on line length both horizontally and vertically. I wonder if that drove part of it.
First, I am not at all contradicting this. :) I just want to get that out of the way.
I have found it pedagogical to read some more difficult programs, without trying to find ways to make them necessarily "read" better. In particular, generic and/or high performance versions of algorithms.
To your point, this is precisely why I will refuse to generalize some parts of my code on the first pass. First, solve my problem specifically, then try and generalize. (This is not a waterfall style approach, but I do try and keep it somewhat phased.)
IMO don't generalize unless you find out later you need the same algorithm somewhere else.
Otherwise, just leave the specific version in and stick some info in the comments (like a link to the high level description of the algorithm, if it's a common one that has a name, plus some pointers to your specifics).
Completely agreed. I do recommend playing with it (in like a repl or some other place) to see if you can possibly improve the algorithm, but have specific metrics for what an improvement would be.
I'm reminded of a section in Programming Pearls where they explore several versions of a function, each time improving it. Too often I'm impatient and want the final solution first.
> First of all, there will be a lot of false positives. For example, you might write
total_cost = p->base_price + p->calculate_tax();
> This would raise the warning because the compiler observes that the calculate_tax method is not const, so it is worried that executing the method may modify the base_price, in which case it matters whether you add the tax to the original base price or the updated one. Now, you may know (by using knowledge not available to the compiler) that the calculate_tax method updates the tax locale for the object, but does not update the base price, so you know that this is a false alarm.
I think the warning in that case is still fair, and I'd be perfectly happy to get it. calculate_tax() could be modified to affect base_price with this compilation unit being none the wiser.
Two unfashionable lessons that I learned when I was a practicing programmer rather than a hobbyist and loathsome manager.
1. Code is a communication system : between you and the next person to touch it. You are telling the other person how you made this work. Invest in this because the next person will probably be you in six months.
2. Compilers and type systems are your friends. Write code that extracts errors from the compiler by explicitly constraining things as much as you can afford to do. Avoiding typing or checking via long methods or low level datatypes is bad. Short typed, functional methods and high level data types are good.
Two unfashionable lessons that I learned when I was a practicing programmer rather than a hobbyist and loathsome manager.
1. Code is a communication system : between you and the next person to touch it. You are telling the other person how you made this work. Invest in this because the next person will probably be you in six months.
2. Compilers and type systems are your friends. Write code that extracts errors from the compiler by explicitly constraining things as much as you can afford to do. Avoiding typing or checking via long methods or low level datatypes is bad. Short typed, functional methods and high level data types are good.
155 comments
[ 4.7 ms ] story [ 228 ms ] threadHow does one go about countering that mentality in some rule of thumb fashion if at all possible? Could a disassembly illuminate identical push/pop operations on the memory stack as explicit intermediate variables would? Maybe a simple intermediate result expansion timing profile side by side the terse code's profile?
For performance enhancements where code readability suffers big time, I always want /proof/ by comparing the instructions (and some benchmarks help too).
Having fewer lines, or having the most often called routines at the start of the program would speed up programs, potentially a lot.
Not to mention the simple fact that, for old interpreted languages, the entire program text had to fit inside memory and so terseness literally saved you working memory.
This probably applies to similar languages (like Ruby) as well.
Sometimes you can help people become better programmers. Sometimes you can't. Try not to stick around in positions where you don't have the ability/opportunity to make a positive contribution, is my best advice.
But assuming that the person is willing to listen to you, all of the above seem like good approaches.
It was an embedded system with a proprietary language. The compiler didn't clear the variables when they were created (again, for performance reasons). For some crazy reason, the language allowed you to declare a pointer without initialising it. So what would happen is that the pointer would end up being whatever happened to be on the stack previously. The reason that it didn't crash in testing was because most of the code in the system was essentially copy and pasted from other parts of the system. As long as the previously run function had a pointer in the same location that happened to allocate some memory in the heap, and then deallocate it, this function would end up using the same memory and it would be fine. And this is exactly what happened in this case (because most of the functions had similar shapes and pointers were declared at the top of the stack frame). The main reason they were reluctant to change the code was because they had copied it from somewhere else and didn't understand how it worked. Because it didn't crash, they didn't want to touch it.
Alternatively, just ask them to prove it. If it's faster, they can benchmark before and after, and prove that it's faster? No proof means you're full of shit. This is the Right Answer in that it's what you should be doing anyhow to avoid performance regressions, but it's way more combative and can cause problems within the team, so must be done tactfully.
That goes bad very fast. I've worked with someone who tried winning arguments with one-line microbenchmarks; They even tried to convince me that I should always use undefined over null in JS since one is faster than the other (forgot which).
> Introduce them to the concept of static single assignment form
I failed with this as well; some coders don't trust the compiler/runtime at all. I had people tell me that garbage collectors don't deal with cyclic dependencies or that "simple comparisons" (<, >, ==) are faster than "compound comparisons" (<=, >=).
This was wrong on a number of levels.
I met some people who were trying their best to make everything a oneliner because "shorter is better" or whatever reason they had at the time. The real reason I always saw was that it was making the code "hacker-like" (and completly unreadable) and they could brag about how clever they were.
Funnily enough, their argument always seems to be "it's easier to read this, since I'm accustomed to
as a pattern."But on the other hand I also find that writing code is the same as speaking normal language. We each have our preferences, vocabulary, diction, style etc. I also jumped on the Linq wagon, but cringe when I see for example a Swift etc. reducer. So as long as that person writes his page of code at a decent pace with limited/no bugs I won't hold it against him.
I think it is a pathological corner-case in an otherwise generally useful interest in, and appreciation of, innovative and economical problem solving.
[0]: https://github.com/v8/v8/wiki/TurboFan
EDIT: added information about `ast_node_count()`
So:
Personally i dislike the latter form because it requires me to spend extra time reading what is boilerplate that is functionally identical to a ternary.
Edit: Not impressed by the down-voting of an earnest question of someone trying to learn more about how other people see things, HN.
If-else is the only option if you want to have multiple statements in a {} block, instead of only one function call.
So it seems more consistent to use ternary for expressions and if-else for statement control flow.
I'm sorry, but i really don't follow your logic, can you try to explain it more clearly?
The classic languages with ternary (C, C++, Java, PHP, JavaScript) all have the deficiency of distinction between statements and expressions. When everything in your language is an expression, the need for ternary disappears.
Python has a ternary operator, which does return a value. It just so happens this operator uses the tokens `if` and `else`. But it's the same thing what you'd write in C or Javascript as `C ? T : F`, would be written `T if C else F` in Python. Different order of arguments and tokens, but it's still the ternary operator and not an if/else block.
You can't get a result from block statements in Python. Just because the ternary operator uses the same tokens as the if/else block, doesn't mean it's the same thing.
Languages with very different syntax like Python are of course going to have different style preferences. Eg. Python doesn't have a ?: operator at all, just an inline if-else expression, so the entire discussion of "should you use ?: for control flow" is meaningless for Python.
The only possibly feasible way i see for it doing something unexpected would be if it were the last statement in a function in a language that implicitly returns the value returned by the last statement in a function; but users of such languages usually are aware that can happen.
How do they suggest that?
As far as i am aware ternaries put no restriction whatsoever on what kind of code you can put in its branches.
I don't buy the comparison with getX either, since ternaries have no name, they're just some punctuation, and even if implemented as if/else word pairs are completely vague.
It might be a convention in some circles. I don't think it's a well-established one, though.
For me, ternaries strongly suggest "expressions being run for its return value", but without any indication whether or not it's side effect-free. In practice, most of the time such expressions end up being side effect-free (getters, finders, etc.), but every now and then you do want to have a stateful expression (e.g. getting something from a map, and storing a default value there if the key is missing in the map). I believe such stateful expressions are fine to use with ternaries as well.
You just called someone out for this:
"If you're restricting what you're saying to a specific language say that bold and as the very first thing (...)"
Are you talking about a specific language now? Because some languages do put restrictions on that. Python makes a clear distinction between statements and expressions, so you don't even get the option to use a ternary in place of an if/else block.
That said, (in general) I disagree that ternaries suggest side-effect free. However, they do suggest that you care about the return value of the ternary expression. Some languages enforce this suggestion, others don't. But even for languages that don't, a ternary that silently discards its return value feels like a "cute hack" to me.
So as an example of doing both, consider following Java pseudocode (simples example that comes to my mind now):
This both gets a value and changes state of the map; which map is queried/altered is determined by the condition. I personally don't mind such code, and prefer it to if/else version.For clarity, let's rewrite it as:
Seriously though, there's nothing in ternary operator that says it should only be used for side-effect free code. Especially in C++ (or Java), which doesn't even differentiate between stateless and stateful code on the language level.
IMO ternary is a perfect solution for the case where you want to assign or return a different value based on a boolean condition. Whether or not the code in branches is side-effect free is irrelevant.
Personally, where I try to avoid using ternary operator, is places where I don't care about returned value. So "yes" for:
no for:As i asked below: Why though?
Also,
> in languages we're discussing
Don't bury that lede. If you're restricting what you're saying to a specific language say that bold and as the very first thing, so people don't end up wasting their time parsing your post in a different context. Rude.
I like to think of it as using the dedicated tool for a job it was designed for. In my mind, it's a good rule of thumb because it enhances readability (compare "the principle of least surprise").
> Don't bury that lede. If you're restricting what you're saying to a specific language say that bold and as the very first thing, so people don't end up wasting their time parsing your post in a different context. Rude.
Why? Come on, at this point in the thread we'll still discussing C++ derivatives. I thought it was obvious.
Also: if a lanuguage has an if/else as an expression and has a ternary operator, then IMO there's a problem with that language being cluttered with duplicate syntax for the same thing.
My primary language, Object Pascal, even goes to the length of naming functions (return values) and procedures (don't return values) differently. It really helps to focus the mind on the purpose of each: one almost always expects procedures to do something and, most likely, modify something that may introduce side effects. Functions are almost always side-effect-free in "decent" Object Pascal code.
Using ternaries only when it is free of side-effect can both helo avoid a class of unnecessary errors, and add meaningful semantic information that some code is, or should be, side-effect free.
It would be great if the compiler(s) in various languages supported enforcing it, but sometimes you take whatever you get.
What you meant is:
A nicer thing that I like about Lisp's "everything is an expression" is something that is considered as code smell by some: This works in Common Lisp, where the only value considered logically false is NIL, because or will short-circuit (as expected), returning not T, but the value of the first logically true result.So e.g.
returns 2.To me it works in short expressions that do return a value so number_of_things = some_type ? 5 : 1
x = x ? (n ? m : w) : (b ? c : x);
This doesnt seem too bad with variables, but now imagine this.
int killMe = Factory.Omg ? (isFuckedUpped ? Leet.YES : Leet.NO) : (youGetTheIdea ? yep : nope);
My opinion is that code should be written in a way that makes it possible for an intern to read and update. I work with interns and fresh-out-of-college grads, and I need them to be productive too.
I'd first look for a way to try and avoid the whole situation, if possible.
After that, what I feel is more readable depends on what formatting the language allows, length of identifier names, what the subexpressions look like, etc.
Then, after careful consideration, I'll just follow any style guide and thus prefer if/else blocks, especially if a ternary would silently discard its result.
At my $dayjob, I have a problem with a cow-orker with a position slightly superior to mine ("first among equals", former team lead of the group of projects to which I'm assigned) taking results of static analysis too seriously - to the level of cargo culting. When bored, he'd introduce "fixes" to reduce number of issues reported by SonarQube - "fixes" I have to revert every now and then, because they reduce quality of the code (sometimes also introducing bugs through carelesness), all to optimize the Sonar metric.
To be clear: tools are fine. It's people that are the problem (as usual).
But I've yet to see one of those code-visualizing tools based on metrics that convinces me. The results I've seen so far are either obvious ("I already knew that, thanks") or inconclusive and hard to interpret. So, to me those tools seem to be more of a way to try and convince your manager that you should be given time for refactorings than actual utilities.
I've been wondering if there could be a benefit in analyzing the commit history of a project instead so that one could somehow at least visualize obvious problem areas ("every third bugfix has to touch this piece of code, so we should really have a proper cleanup there...").
{"some": "dict"}.setdefault("some", foo())
while expecting foo() to be short-circuited.
What are people expecting with that?
Fun trick to learn exists. Though I would really like to know what led up to it, since it seems ... unhelpful.
Edit: Looking at other comments, I did try popping off that entry in the dict, and no, it didn't have anything in it (it doesn't make the dict anything like defaultdict).
I've noticed there are two opposing schools of thought on issues of "insane code" like this; on one side there's "use the language to the fullest if it makes sense to", and on the other is "avoid anything that might be the slightest bit confusing". I think that taking either of those to their logical conclusion is not a good idea --- in the former case, you'll end up with extremely dense and (initially) difficult-to-understand code, but the latter case will cause a gradual degradation of code into something approaching the verbosity of Asm, but with none of the benefits (e.g. "ternary operators are confusing, don't use them; multiple levels of precedence are confusing, so always fully parenthesise expressions; nested parentheses are hard to read, so don't use them either and only do one operation per statement; boolean expressions are confusing, so always use if/else; nested if/else are confusing, so don't nest and always refactor to use a function call, etc. etc.)
It does seem that different languages vary in where they are on this scale, with more "exotic" ones like APL tending highly towards the former (interesting related discussion: https://news.ycombinator.com/item?id=13565743 ) and C#/Java towards the latter. The ideal is probably somewhere in the middle.
there's a simple rule of thumb - if you can't immediately and unabmiguously deskcheck the expression as it is written, it's too complicated and should be simplified.
This is a false dichotomy. How about "use the language to it's fullest, within reason (avoiding things like multiple side effects per source line), but when non-obvious code is written, document it."
At least, I'd say that describes my approach.
http://rextester.com/FDFE11220
NB it really is faster
edit: nvm, Crumb is way more that kind of thing: https://github.com/serprex/Crumb/blob/master/C3.py I forgot I concocted that little devil
I even did a C version https://github.com/serprex/Crumb/blob/master/Crumb.c which is a pretty boring implementation besides
In my experience, this is accurate, but underscores a teaching opportunity. If you show Joe Beginner how to get the warnings, explain how they apply to the code and why the warning was good, they may slowly begin to appreciate the warnings. So that even if they don't understand, they won't necessarily dismiss the warning next time -- instead they'll go ask for help.
The R package "data.table" does a good job at this. Just scroll through https://github.com/Rdatatable/data.table/blob/master/R/data.... and search for "warning(".
After spending months looking at disassembled FreeBSD kernel C code in a profiler, trying to hunt down cache misses, I'm realize that it is hard to count on anything and that my instincts, even after 25+ years of C programming, are often wrong.
Looking through C# docs (which I assume this is, but don't have experience with), it seems A's value is changed to be the previous value of a, minus itself (ie 0) multiplied by a? In other words:
If the described author could be identified? Perhaps not absolutely, but they do have some specific stylistic quirks combined with a particular sales level.
“Nathan Reed
"July 19, 2017 at 4:34 pm
" Didn't you just write such code with a straight face yourself only yesterday? ;) In your bytecode interpreter example:
"Presumably the NextUnsigned8/16 functions have side effects of advancing a buffer pointer, so this is nearly the equivalent of p[x++] = ++x."If I feel particularly smart and proud after writing a piece of code, I look at it again and remove some smartness in favour of readability.
Unless it's absolutely performance critical, in which case I try to comment the hell out of what's going on.
I have found it pedagogical to read some more difficult programs, without trying to find ways to make them necessarily "read" better. In particular, generic and/or high performance versions of algorithms.
To your point, this is precisely why I will refuse to generalize some parts of my code on the first pass. First, solve my problem specifically, then try and generalize. (This is not a waterfall style approach, but I do try and keep it somewhat phased.)
Otherwise, just leave the specific version in and stick some info in the comments (like a link to the high level description of the algorithm, if it's a common one that has a name, plus some pointers to your specifics).
I'm reminded of a section in Programming Pearls where they explore several versions of a function, each time improving it. Too often I'm impatient and want the final solution first.
I think the warning in that case is still fair, and I'd be perfectly happy to get it. calculate_tax() could be modified to affect base_price with this compilation unit being none the wiser.
1. Code is a communication system : between you and the next person to touch it. You are telling the other person how you made this work. Invest in this because the next person will probably be you in six months.
2. Compilers and type systems are your friends. Write code that extracts errors from the compiler by explicitly constraining things as much as you can afford to do. Avoiding typing or checking via long methods or low level datatypes is bad. Short typed, functional methods and high level data types are good.
1. Code is a communication system : between you and the next person to touch it. You are telling the other person how you made this work. Invest in this because the next person will probably be you in six months.
2. Compilers and type systems are your friends. Write code that extracts errors from the compiler by explicitly constraining things as much as you can afford to do. Avoiding typing or checking via long methods or low level datatypes is bad. Short typed, functional methods and high level data types are good.