22 ms·
Why most “clever” code ain’t so clever after all
- partisan 10y agoNot the OP of the article. Just wanted to get perspective from Haskell devs on the points raised in the article.
- rev_null 10y agoI find it interesting that he dismisses the use of Maybe. The monadic solution isn't clear, but when used as a Monoid, it's clear and easy to modify: https://gist.github.com/anonymous/7a1829f61cf9302c188f80a7366e9803 https://gist.github.com/anonymous/7a1829f61cf9302c188f80a736...
- curuinor 10y agoC. Alexander in _Notes on the Synthesis of Form_, rather explicitly puts forth the essential architectural problem in a semiformal form - a constraint satisfaction problem. The closest application of CSP-like problems in places close to statistics-land is in neural networks, where Rumelhart published in multiple places that the objective of neural networks, including the backprop net that he reinvented, was soft CSP. So actually, there is probably a path towards formal equivalence. Of course, there's a disgustingly large amount of work done on the analogies between CSP and Ising model at criticality, because you only need to look at the damned things (compare to Hopfield model). The other path is to look at R. Sole's work on munging for power law-ish things in software. So physical methods for software are actually quite old hat (from the 90's). It's just that it's all really fucking hard condensed matter stuff. This is quite close to Huberman's work at PARC about networks and stuff, which led to the different Adamic and Huberman munging for power laws in networks and Internet things.
- stcredzero 10y agoWhether or not something is "clever" as opposed to annoying or obtuse depends heavily on context. In the context of a production programming environment, it's all about cost/benefit. Does it reduce the team's cognitive load? At what cost?
- qwertyuiop924 10y agoThis is a fairly well known thing in all programming communities that matter, but it was good to see it pointed out once again. However, I would argue that the classic guarded function using modulo would actually be clearer. It takes a moment to follow the logic of cycling up an infinite list, and modulo statments are far clearer and easier to understand to most people, I would say. In addition, it's easier to change than either of the above, and equally declarative: modulo is a declarative operation as is a guard: when x % 3 is zero, it IS fizz.
- frank_jaeger 10y agoWhat about when x is 15?
- qwertyuiop924 10y agoWhat about it? You make it a higher precedence guard.
- z0r 10y agoYou can code yourself into a corner no matter what language you use. I've not seen much of this beautiful many-seamed code that could suddenly accommodate unanticipated feature requirements without rearchitecture, but I have enjoyed a certain amount of seamful boilerplate requiring much burnishment as changes are made. I don't think I'm anywhere near as experienced as the author but I still think this article is setting up strawmen and knocking them down.
- iagorodriguez 10y agoI run a small dev company. This is the first lessons we teach our programmers. If your code cant be understood by someone else with minimal efforts it is not good enough.
- TeMPOraL 10y agoDo you maintain a high-enough level within the team though? Most "clever code" I've seen is called clever by people who are not yet proficient enough in a language to understand it. So with unskilled programmers pretty much anything that isn't straight combination of classes, loops and conditionals will be considered clever. There's a tradeoff here - I get the business reasons why one may want to have the codebase at the "lowest common denominator" level for the team. But then again, programming is a craft, so that person who says lambdas in Java are "clever code" (real example from my work) should IMO suck it up and spend few hours learning; then the code will stop being "clever" to them.
- slededit 10y agoNot every company wants highly skilled programmers. Depending on the complexity of the project, and cost of defects unskilled programmers may be the most cost effective.
- TeMPOraL 10y agoI know. Took me a while to realize it and understand why companies think that way, but it boils down to the fact that as programmers, we're not paid to do a good job, we're paid to do a good enough job. Those two goals are very often at odds. It seems to be a typical clash of craftsmanship with market economy.
- jbattle 10y agoAnother articulation is - we aren't paid to write code, we are paid to build stuff. Authors get paid to write prose people can read - the readability of our work only 'matters' insofar as good craftsmanship practices help the team maintain velocity over the long haul.
- johnfn 10y agoCycled lists and the modulo operator are two different abstractions for thinking about the same problem, in the same vein of "blind men grasping at elephant." They are different perspectives on the similar problem. The reason we think that cycling a list is "clever" but using modulo is "boring" is because most of us use modulo often, and cycling rarely. There is nothing inherently "clever" about either of the two approaches. In an alternate universe where we all used Haskell, perhaps the modulo solution would come off as "clever". And perhaps this article would be pointing out the flaws in the modulo approach instead: the fact that numbers divisible by both 3 and 5 have to be treated by a special case, the fact that it doesn't easily generalize to fizz buzz bazz, etc.
- emn13 10y agoThe issue with the first fizzbuzz isn't just cylcling lists, it's also the use of zipWith. At least, that's what causes some of the brittleness in his examples. --- Also, I think you're slightly wrong in that cycled lists and modulo are "just different abstractions" - they may be to a machine, but they are not to people. And in this case, critically, the original problem's phrasing is much closer to the modulo implementation than the cycled list equivalent: "...for multiples of three print Fizz...". By choosing to encode that constant 3, rather than represent it, you're being clever; by failing to use a straightforward analog of "is multiple of", you're being clever. This "encoding" has real risks - for instance, because you're human, you'd immediately notice that 5 is not 6. You don't need to think to do that; don't need to process equivalence or do any kind of algebraic reasoning; a kid can and will notice the identical shapes before they can talk or walk. It's built in. By contrast, even a trained professional might overlook a list with 5 spaces followed by Buzz as opposed to a list of 5 elements consisting of spaces followed by a single Buzz. Sure, it's a really stupid mistake, and one I hope you make very rarely - but people make stupid mistakes. No need to encourage that habit by making them think. So no, it's not that cycled lists are bad and module is good, it's that the problem description is closer to the definition of modulo than it is to list cycling. (This is my interpretation, the article's definition of clever is more about seems and changes and doesn't really touch this).
- sixbrx 10y ago
- sbov 10y agoSome like to say that you should never write clever code. But clever to who? Some people lambdas, map, reduce, and even polymorphism are clever. So in a vacuum the statement is meaningless. Whenever you're writing code you need to know your audience. Is the Haskell solution really that clever to someone who uses Haskell on a daily basis? That said, I always liked the saying: it's easier to write code than read it. Which means, that if you're writing code at "maximum cleverness" you're writing code you won't be able to read.
- Consultant32452 10y agoMy rule of thumb is that I attempt to write code that a junior programmer whose experience is limited to a little more than having taken a course in the language and has done a couple tutorials on the framework(s) can maintain. I see so many developers try to squeeze every new language feature or loads of esoteric library use cases into their code because they learned something and feel like it MUST belong in the production code. Sometimes you have to write complicated code, but 99% of the time it should be possible to write very simple code using only the most commonly used language features.
- jdmichal 10y agoFor all of Java's pitfalls, this is one of the things it really has going for it. The language itself is extremely simple. I think adding properties instead of the weirdness of getters and setters would take it to peak simplicity and straight-forwardness. (Also, maybe eliminate inheritance and force interfaces and composition instead.)
- iamcreasy 10y ago> eliminate inheritance Why would you want that? Wouldn't eliminating inheritance add a lot of boilerplate code?
- jdmichal 10y agoI think default methods take care of most of the places where boilerplate would be required. The abstract collections are a great example of this; most of the methods required of a `Collection` can be implemented from a small subset. Sometimes you'll re-implement other operations anyway for efficiency gains, but that's supported with default methods also. However, I may just be weird. My Java coding style is probably closer to Rust or Haskell. Lots of interfaces defining my type requirements, and static methods to implement compound operations on those interfaces. I like this because it's easily testable and traceable.
- daxfohl 10y agoYour API should strive to be clever (it should allow end users to do countless things straightforwardly without error). Your implementation should not.
- gohrt 10y agoWhat does that mean? How is that clever?
- jheriko 10y agoi've often complained about "clever" code. simple code is easier to maintain, and clever tricks are difficult to read... speaking of difficult to read. please don't set examples with terrible single letter variable names. and terrible two letter variable names. these are even less forgiveable than clever tricks in my book. don't encourage abbreviating things. programming is about thinking about, reading and writing code a hell of a lot more than it is about typing.
- emblem21 10y agofor(var i in list) { (function(element) { someAsyncCallThatNeedsTheRightIndexedElement(element); })(list[i]); } I've seen people grumble that this is "too clever" I've been known to snap back "Bootcamps don't have a monopoly on what technical solutions are valid" Pointers are probably too clever for most people. Admittedly, goto is too clever for me. :(
- atoko 10y agoTry using .call
- Jach 10y agoWhen "too clever" is "I don't understand", yeah, it's bad. I'd snap back with "let's use clojurescript then". Or I'll clarify whether they think 'var i' in the loop is too clever or the scope fixing code that's pretty common to do in JS is too clever.
- olalonde 10y agoI'd just write: for(var i in list) { someAsyncCallThatNeedsTheRightIndexedElement(list[i]) } though I usually try to avoid for loops with `.map` or `.forEach`: list.forEach(someAsyncCallThatNeedsTheRightIndexedElement) The IIFE in your code is unnecessary and I would thus probably side with the too clever camp in this case.
- Strilanc 10y agoNote: this is fixed in ES6. Using `let` instead of `var` will treat each iteration's variable as distinct: a = [] for (let i = 0; i <3; i++) a.push(() => console.log('hi ' + i)) for (let e of a) e() Logs: hi 0 hi 1 hi 2 Actually, it's almost surprising in the opposite direction now.
- ezyang 10y agoI didn't make it past the fizzbuzz example. But I wouldn't call the solution with cycle all that clever. Here's a better one: https://themonadreader.files.wordpress.com/2014/04/fizzbuzz.pdf https://themonadreader.files.wordpress.com/2014/04/fizzbuzz.... the punchline reproduced below: fizzbuzz' :: Int -> String fizzbuzz' n = (test 3 "fizz" . test 5 "buzz") id (show n) where test d s x | n `mod` d == 0 = const (s ++ x "") | otherwise = x fizzbuzz :: [String] fizzbuzz = map fizzbuzz' [1..] The article is very interesting and worth a read. In particular, this implementation doesn't come out of "thin air": it's been golfed from an implementation that defines a simple domain specific language to solve FizzBuzz. And if you understand your problem well enough, DSLs are an extreme force multiplier.
- squeaky-clean 10y agoFeature request: Client should be able to specify either 3, or 1023 for the number needed to print "fizz." Feature request: Client should be able to optionally request "Lizard" as another string whenever the number is a multiple of 13. That's what the article is referring to. Your function may be more clever, but it's more specific, works in only exactly the requested cases and is harder to change.
- mmierz 10y agofizzbuzz' :: Bool -> Bool -> Int -> String fizzbuzz' big doLiz n = (fizz . test 5 "buzz" . lizz) id (show n) where test d s x | n `mod` d == 0 = const (s ++ x "") | otherwise = x fizz = if big then (test 1023 "fizz") else (test 3 "fizz") lizz = if doLiz then (test 13 "Lizard") else id fizzbuzz :: Bool -> Bool -> [String] fizzbuzz big doLiz = map (fizzbuzz' big doLiz) [1..]
- tromp 10y agofizzbuzz' n = (test 1023 "fizz" . test 5 "buzz" . test 13 "Lizard") id (show n) easy as pie...
- wangchow 10y agoSo long as the "cleverness" is well-documented it's not a problem. Authors should explain why it is written the way it is. Problem solved. I've seen some weird-ass calculations in code with no comments. If the author has gone through so much effort to come up with the clever solution why not add some commentary so the next guy can read it quickly. That's why every language has support for comments..
- yoklov 10y agoOTOH comments easily become outdated, and it's painfully easy to make a change that subtly invalidates a comment in another file. They're like lines of code that is never tested or run. For calculations, I'm not really sure I agree either. I used to work in games, specifically on physics and collision, and there's a point at which you have to assume that the person maintaining it has a reasonable level of familiarity with the subject matter, or else you'd need to write a comment that contains all of a linear algebra course.
- bigger_cheese 10y agoI do not see this as an issue if the comment immediately Precedes the "Clever" section of code. Calculations are the most important part of the code to comment I'm an Engineer most of the guys I work with have never been formally taught any programming often I see a bunch of code written in Fortran with notoriously short variable names if it wasn't for comment very easy to get lost. So long as there is a comment saying something like "Ergun's Equation - Calculates Pressure Drop" You don't need to follow all the fluid dynamics to know what the code does - at minimum someone reviewing it can google "Ergun's Equation" You don't need to comment linear Alegbra but at least comment what the code does "//Solve using LU factorization" or similar is sufficient and it beats seeing segments of code like this with no explanation: r[i] = 1/(pb[i]+t[i]-fr1[i-1]-gr2[i-2]); u[i] = a[i]-r1[i-1]u[i-1]-r2[i-2]u[i-2]; f = pc[i]+t1[i]-h*r1[i-1]; Edit: It stripped chunks of the above code but you get the idea.
- wangchow 10y agoTrue--sometimes when translating from a mathematical context to programming causes some readability pains! Especially if it's something that takes advantage of multiprocessing to solve the problem.
- gohrt 10y agoWhy post this draft to HN instead of letting the other make it more refined and readable?
- stites 10y agoI think it's because it hit "1.0"
- junto 10y agoAs a company we use C# and Visual Studio as an IDE. Everyone has Resharper, which is undoubtedly an invaluable tool. However, Resharper had a feature that will convert for/for each loops with conditional logic into LINQ lambdas. For simple cases I find it's conversion useful and readable, but for more complex cases it becomes an unreadable nightmare for anyone bug fixing later on. I find that to be the case even when I personally wrote the original code, so I avoid these conversions whenever they become overly complicated.
- kylemuir 10y agoThankfully, it also has the same "Alt-Enter" to revert said LINQ query :)
- junto 10y agoExactly, but colleagues also have to be encouraged not to always trust what Resharper suggests!
- kylemuir 10y agoDefinitely agree - my pet peeve (although I haven't seen it for a while) is introducting circular references by adding compiled dlls from /bin/debug as references instead of adding a project reference. I use to see it a lot a few years ago, maybe I just got a little better at my job?
- mdpopescu 10y agoWouldn't it be better to simplify the complicated code instead?
- squeaky-clean 10y agoI understand what they're getting at, but I think the final listing is even more "clever" than the first. The first one I could read and understand pretty easily, and I've never written Haskell. The few things it does, and how, are easy to spot. The final one is harder to understand at a glance, even though it's more generalized. Some people take it even further than that, to where the implementation is totally abstracted from it's only uses. If you removed the "fizzbuzz" related names from the code, could you tell that listing 4 is being used for a FizzBuzz? Sometimes a 3&5 FizzBuzz is just a 3&5 FizzBuzz and nothing more, and doing anything further is akin to premature optimization. Planning is hard, haha. I realize FizzBuzz is a toy solution for the sake of example, and the solution with fewer assumptions is more often the better choice when dealing with larger problems, overall this was a very good article. But we also need more programmers to understand that if you're writing an internal backend function which is passed a date-as-integer, you probably don't need to double check that it's actually string representation of the integer, or an ISO formatted date, or YYYY-MM-DD formatted string, or YY-MM-DD formatted string, or....
- nilkn 10y agoI think this is a very insightful article, but I agree with you. I thought the first listing was very clear and immediately comprehensible. Granted, I do have a small amount of experience with Haskell so I didn't have to think about the syntax at all or about `cycle`, etc. Listing 4 was harder for me to understand. In particular, I had to actually look up `foldl1`. Once I understood it, though, I could see the beauty of its generality. I'd say it took me twice as long to understand listing 4 fully compared to listing 1.
- cbrogliato 10y agoThe FizzBuzz example was just to underline how a "considered clever" solution for an academic problem behave when moved into "real life" situations. If we are taught to solve and think problems as if we were in an idealized world we are going to suffer once we enter the battlefield.
- mpweiher 10y agoCode can be clever to allow (significantly more) other code to be simple. For example, we are using Higher Order Messaging[1] to turn this: if ( [delegate respondsToSelector:@selector(doSomethingElse:)]) { [delegate doSomething:self]; } into this: [[delegate ifResponds] doSomething:self]; Yes, the implementation of -ifResponds is slightly clever, though I've gotten it simpler over time, but it certainly is worth it in the simplification of client code. Engineers manage tradeoffs. That's what makes them engineers. [1] https://en.wikipedia.org/wiki/Higher_order_message https://en.wikipedia.org/wiki/Higher_order_message
- ovt 10y agoI wonder how common the use of "ain't" is today. I grew up in the rural US in the 1980s, and it was common. And today I don't hear it, but I live in a more... well, educted area. It throws me off when I see it in writing now.
- azhenley 10y agoI think it is just more common to say it rather than write it (I too have noticed this).
- jorams 10y agoI think the real problem with the fizzbuzz example is that the "clever" solution fails to properly break the problem down. Fizz buzz essentially consists of two parts: 1. We have an increasing sequence of numbers. 2. Every number divisible by 3 is replaced with "fizz", every number divisible by 5 is replaced with "buzz", every number divisible by both is replaced with "fizzbuzz". If you set out to solve these two parts individually, you would never come up with the clever solution. While the desired result of fizzbuzz is a specific sequence of numbers, that fact is almost incidental. The clever solution doesn't really look at the problem definition but instead merely generates the desired sequence.
- belovedeagle 10y agoThis article hinges on a rather silly fallacy. Luckily the author does us the favor of making his mistake very clear in the curve-fitting analogy; but first look at the code. He gives several examples of poorly-written FP code (which we're supposed to believe is "clever" by virtue of being FP), then introduces some new problem constraint which is carefully selected to make the FP solution break but some equally ridiculous imperative-style code not break. In fact, what he really wants to say is, "FP is stupid", but he won't come out and say it. This is illustrated perfectly by the curve-fitting analogy: a perfectly-fit 5th degree polynomial is the "clever" solution, but oh wouldn't it be so much simpler to use an best-fit 3rd degree function? And oh look, the 3rd-degree polynomial has a positive slope at the end whereas the 5th degree polynomial has a negative slope, ergo if we just happen to pick a point greater than the previous one, ta-da! The 3rd-degree polynomial fits the new set better than the 5th-degree. QED. When put that way, the argument is completely idiotic. You could just as easily have picked a new point lying, say, on the 5th-degree polynomial and thus "prove" that it was a better fit for some yet-to-be-defined data than was the "non-clever" 3rd-degree polynomial. The analogy to the code is that it would be trivial to come up with new requirements which could be easily solved by the "clever" FP code and very difficult by the imperative-style code; and in fact the author admits as much in the second case. By the end I can't help but wonder if this was satire, designed to make anyone agreeing with the article look silly when the author later comes out pointing out all the above...
- rkalfane 10y agoI honestly didn't see the same thing as you, my understanding is that using the Lagrange Interpolation is the "clever" choice, but when you add more points, you'll end up with a higher order polynomial and widely different curve which is probably not what you wanted in the beginning. I read the piece more as a reminder that most specs are usually incomplete/too specific/not conveying what is actually needed, and a warning against cornering oneself with a too clever solution. I also didn't see any big issues with their stance on FP, except maybe that they dismissed the Monoid solution that someone posted down in the comments that looks both robust and readable.
- deleted 10y ago
- pdkl95 10y agoSoftware design is often about finding the correct balance between the available implementation methods. Trading memory for time (low-mem/slow recalculation vs higher-mem/fast precomputed-table) is the usual example. Software design involves a similar trade-off where simple (but repetitive) code can be traded for a smaller (and maybe more complex abstraction). It's not about "cleverness" (though that can be a strong warning sign), but instead if the abstraction involved is correct, useful, and appropriate. > My code in Listing 4 is not based on those assumptions. Yes. It's based on the new assumption that it might be necessary to "add that pair to the list of cases" in the future. > I choose not to read into requirements more than needed. Except for the new abstraction that attempts to allow for other word replacements not mentioned in the original specification. While I agree that hard-coding assumptions is usually a bad idea, soft-coding[1] can also be a problem. > FP > OO That's the Expression Problem[2], which always involves yet another trade-off. Some types of modifications are easier under FP, others are easier under OO. [1] http://thedailywtf.com/articles/Soft_Coding http://thedailywtf.com/articles/Soft_Coding [2] http://c2.com/cgi/wiki?ExpressionProblem http://c2.com/cgi/wiki?ExpressionProblem
- hinkley 10y ago"There's a fine line between being clever and being a smartass."
- cwbrandsma 10y agoI remember the days of seeing inline assembly in code (delphi). We griped about that code quite a bit, but this was in the days before "you can't outsmart the compiler". Was it clever: yes. Was it maintainable: no. Was it necessary: YES! This was drawing code, doing it via the normal language routines was just not a performant solution.
- mannykannot 10y agoThere is an interesting genre of complexity that is justified (by its creators) on the grounds of simplicity. One sub-genre makes reducing the line count the primary measure of simplicity. Another sub-genre aims for the most generic and flexible code in order to simplify reuse and modification, regardless of the chances those capabilities will ever be used, or whether several different specific programs would be the simpler, faster and cheaper solution.
- echaozh 10y agoHmm, it may not be very helpful, but there's a replicate function[1] right in the Prelude. I always thought that being clever in Haskell is to be able to find all the strange but powerful packages and functions and weave them in as tight a program as possible. Having to implement replicate by hand is definitely not clever from the start. Hmm, it's also what drove me away from Haskell and kept me on and off with the language, as most of the powerful stuff are too powerful and so hard to tame... [1] http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude.html#v:replicate http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude...
- stites 10y agoThis article/paper/draft was a bit of a strange read. Just looking at Experiment 1: it seems like he is taking weird / baseline-acceptable code for fizzbuzz (perhaps warranting a PR to frege) and refactoring to make it more flexible/production-friendly (overkill for fizzbuzz). He then argues that the latter isn’t clever? Honestly, it seems too clever for fizzbuzz! If I asked fizzbuzz in an interview and the candidate told me the final solution, I'd think it was very clever and very overkill. I guess I don’t understand what he means by clever! I like that he made an attempt to make this language-agnostic, the final example, but now he's introducing the idea of "elegance," (missing a definition) and kind of saying that "cleverness == elegance == overfitting?" I think the problem here is that he's _too_ generic (might I say, he's trying to be too clever : P) and pulls in a third field to make his abstract "cleverness" case. But, from my understanding, I'm under the assumption that cross-validate is a given for models: overfitting is for chumps and students. I'd give that a "to be improved" for draft 1.1. --- Overall, from the recap: > “Clever” code tends to exploit explicit and implicit symmetries in the problem structure, building highly-symmetrical solutions by exploiting highly-symmetrical library functions / types. and from the abstract: > Unfortunately, people tend to assume that the style of such (supposedly) clever code is representative of “good” functional programming. The main issue here is that clearly the first part is correct and makes for fragile code (given context), but he’s completely opinion based on the latter. What he needs to do is find “production-level code" or “education-level practices” that are handed down to new FP developers and taken as “truths." After reviewing the citations, I think that his points on [6] and [7] are weak: he needs to show more than just comments and one-offs, and needs to hammer in the "why this is bad" more. Instead, it feels like he's just trying to show his contempt for haskell. I'm also thinking that, if he wasn't just trying to run haskell through a ringer, you'd think he'd use other functional languages / language-features (frege is designed as "a Haskell for the JVM"). His opening line of 'there is a deluge of “clever” code constantly pushed down from gurus to hipsters to innocent readers' also primes me to think that this is more of a cathartic article than a useful one. I'd love to agree with him, say that "stupid code, everywhere" is a problem in educating new devs for pure-functional languages, then spider the internet, comment/fix all these code examples, and finally make all my friends haskell developers : P (disclaimer: I'm just an enthusiast). However, I remain unconvinced — I think the problems in haskell education is elsewhere. Perhaps I have simply been lucky in finding sane, good resources, and all the production code I study is uncommonly blessed. Looking forward a 2.0 so that I can convert all my friends. (I redacted some more thoughts since this is my first real post to HN; be nice!).