10 ms·
Explicit vs. Clever
- ante_annum 14y agoIt seems like there's a learning spectrum that defines what's explicit and what's clever. If you're not familiar with HOF, then functional programming is always going to appear clever. But as you learn, things lose a bit of magic. I think the key to a team being explicit vs clever together is that everyone has about the same understanding of explicit vs clever.
- akavi 14y agoI feel like more than anything, this example demonstrates the value of lightweight lambdas. var totals = splat(get('total'))(orders); is definitely nicer than var totals = orders.map(function(o){return o.total;}); But there really is not much difference between the coffeescript versions (actually, I'd argue the 'map' version is much clearer in intent): totals = splat(get('total'))(orders); totals = orders.map (o)->o.total
- masklinn 14y ago> I feel like more than anything, this example demonstrates the value of lightweight lambdas. Meh. totals = map (! "total") orders
- ricardobeat 14y agoThere is a JS library that implements `orders.map('total')`, can't remember it's name.
- raganwald 14y agoThe allong.es library quoted actually allows: splat('.total') But you didn't hear it from me.
- deleted 14y ago[deleted]
- raganwald 14y agoI was deliberately avoiding the map method to keep everything functional.
- akavi 14y agoHow is map not functional? Do you mean you wanted to emphasize HOFs?
- raganwald 14y agoYes. The map method is not functional in the sense that the map function is functional. In actual code, I'd use the method too.
- akavi 14y agoI don't really see the method as being any less functional than the function version (ie, they're both without side effects, the method version just has 'this' as an implicit parameter). Though I guess that's just a difference in perspective. Edit: Having stumbled upon something else you wrote[1](Viz. "WHy the crazy idea of using a splatter instead of a mapping method or function?"), I now understand the reason you're making the distinction. Though (speaking as someone who certainly doesn't have the level of expertise to be making this semantic argument) I really don't think it's the distinction between "functional" and "not functional" so much as... "composability"? [1] https://github.com/raganwald/homoiconic/blob/master/2013/01/madness.md https://github.com/raganwald/homoiconic/blob/master/2013/01/...
- chas 14y agoI think a lot of people would argue that composability is what "functional" code is really interested and many of the other things that are normally associated with it are actually side-effects of the emphasis on composition.
- tome 14y agoExactly. There's no point being "functional" for the sake of functions. It's for the benefits this style brings, an important one being composability.
- GeZe 14y agoLiveScript allows partially applied operators, including property access. You can do: totals = map (.total), orders http://livescript.net/ http://livescript.net/
- aurelianito 14y agoI find var totals = orders.map(function(o){return o.total;}); far easier to understand than var totals = splat(get('total'))(orders); And, I would had written var totals = orders.map(function(o){ return o.total }) (note the lack of semicolons and indentation) cheers!
- jiaaro 14y agoCleverness for it's own sake may be a bad thing, but cleverness in general isn't bad. The really important thing is that when you do something clever, you ensure that people who use your clever function/module/library/webservice don't have to also understand your cleverness. Leaky abstractions can be worse than no abstraction. This is a bit of a tangent though. With respect to the micro level cleverness mentioned in the article: Simply naming the output of those helper functions would go a long way...
- ricardobeat 14y agoObscure jargon and custom methods are always going to be perceived as clever. Personally I don't think this var totals = splat(get('total'))(orders); is an improvement over var totals = _.pluck(orders, 'total'); Why? Because the latter has a single layer of abstraction - what does `pluck` do? - while for the former you have to learn about `splat`, `get` and their internal behaviour/return values, specially if my jargon is not exactly the same as this library's jargon. We've had the exact same discussion over Promises: people favor a slightly less convenient syntax over too many layers of abstraction. If you have a large codebase where the functional methods are really going to help, great, go on and put a note on the readme to guide other developers before they encounter it; but unless a specific implementation/library becomes hugely popular it's not going to fare well in public or small projects, and will be seen as clever, opaque code. edit: sorry, I misread the 'is a small incremental improvement' as referring to the underscore method. Keeping the post for discussion anyway.
- raganwald 14y agoThe post doesn't say it is an improvement, it says that Underscore is deservedly popular and also that both are jargon. UPDATE: I will probably clarify that in the code. But if you want to open the door into my madness, the use case for "splat" is that it's a combinator, a function that takes a function and returns a function. So if you like "pluck" as I do, you write: var pluck = compose(splat, get); // ... var totals = pluck('total')(orders); And you wouldn't really write `splat(get('total'))` either. Having building blocks that are combinators rather than general-purpose functions is itself a family of jargon. It's a win if you do a lot of method decoration and function composition, otherwise it's... Clever.
- deleted 14y ago[deleted]
- dasil003 14y agoRe: Promises: According to the last article that was posted here, the point of Promises is to provide greater semantic information that assists in organizing the dependencies and prevents subtle race conditions, it is not simply syntactic sugar on callbacks. I'm not prepared to defend that position, but the article made a compelling case.
- leke 14y agoIs is just me, or has JavaScript got really fucking weird lately?
- raganwald 14y agoIt's not the language, it's the reverse-eternal-september caused by people who are actual programmers with an actual background in computer science writing node applications on the server and client applications in the browser. As long as it was just DHTML for doing some form validation or whatever, it was treated like a "scripting" language. Either that, or the fumes of smugness are getting to people.
- anonymoushn 14y agosplat does not seem to be a function that produces a flatMap. In the contexts google is familiar with, a flatMap is either something like this flatMap fn xs = foldr (++) [] (map fn xs) or a map of the earth or a map in Minecraft.
- raganwald 14y agoThat was a typo, I started out writing about "soak," which is to flatMap as "splat" is to map. Thanks.
- ajross 14y agoAnd yet: var totals = [] for(var i=0; i<orders.length; i++) totals.push(orders[i].total); Now... maybe "splat" and "get" are "jargon", or maybe they're "abstractions" or even "explicit" (frankly I didn't understand that stuff)... But I'll bet good money that bugs in or near this version get fixed faster in the real world. And I'll even bet that truth holds for people who happen to know what "splat" and "get" are supposed to mean.
- raganwald 14y agoI remember this exact argument when Ruby first became popular and the PickAxe book was telling people how to write .each and .map instead of loops. Come to think of it, I remember this exact argument when SmallTalk was introduced. My elders probably remember having this argument when discussing LISP vs. Fortran. I'm not sure what I can tell you that hasn't already been said in the sixty years that programmers have been having the whole "It's easier to understand when you're directly manipulating the metal" argument.
- ajross 14y agoIf you're sick of having the argument, then why did you troll it? :) I mean, I'm perfectly willing to be your imperative curmudgeon strawman if you really want. But honestly, if you're going to blog about the spectrum of "explicit" programming, you're sort of morally bound to accept arguments on sides of the spectrum you'd rather not talk about.
- raganwald 14y ago1. I'm not sick of it, I just can't tell you anything you haven't already heard and rejected. 2. I'm not trolling, just pointing out that this has been going on for a while, which implies that there are reasonable people on both sides of the discussion. 3. I never argued that explicit is bad or that jargon is good, I just explained that sometimes, jargon accomplishes a purpose. 4. I have written for loops and liked them. So, 5. Don't assume enmity where there is none.
- ww520 14y agoClever is fine as long as things are somewhat self-explanatory. At least in this example, the get() method name is confusing. Get is a customary well known method name to extract a property of a map or object, and making the call now. The usage context in the example is that it is a parameterizes getter function that's passed in to splat to be called later. Perhaps it would be clearer if it's called getter('total') or accessor('total'). That shows it's building a parameterized actor function, and it can be passed around and called later. In working with functional code, I prefer naming high order functions with an actor connotation.
- raganwald 14y agoThat's really interesting. It's especially interesting if you think of this: function get (object, propertyName) { return object[propertyName]; }; So now, what is: rcurry(get)?
- ww520 14y agoGood question. Let's re-arrange the get method a bit to make it easier for currying demonstration. function get (propertyName, object) { return object[propertyName]; } The method 'get' does what it's named, to get the property now in the call. Curry(get('age')) creates a separate entity that parameterizes get with the 'age' property. It's a new function object different from get. But at least the term curry(get('age')) gives some visual cue to the code readers as what it does vs just get('age'). For languages (e.g. Haskell) that implicitly curry everything, it's understood that any missing parameter means a curried function has been created and the actual call is deferred. Javascript is not such language. Mixing in the naming convention for implicit curry just causes confusion for the readers. Javascript's currying is explicit with pattern such as, getter(get, 'age'), which would wrap a function around the get function with a closure {propertyName: 'age'}. I think it's best to consider the audience and the convention of a language when naming things.
- sufian_rhazi 14y agoCode needs to read like good copywriting: obvious to the reader as if they were the author. Good copywriting needs to be aware of the target audience and the idioms they use. Abstractions and jargon come along with the audience. It's critical to write for your target audience. If you work with people who think computers are proof verifiers, splat is probably best for you. Then again, you should probably be writing it in Haskell/Fay/ML/... If you work with people who think computers are shufflers of bits, then maybe a for loop is best. And if you work with people who think computers are consumers of over-specified utility libraries, perhaps there's a iterator subclass implementation to be made.
- gbog 14y ago> Code needs to read like good copywriting Yes. > write for your target audience [and the rest of your comment] No. Good writers write good copies that are good for (almost) any target audience. This applies to code too. For me archetypal "good python code" examples can be found in http://norvig.com/ http://norvig.com/, eg http://norvig.com/lispy.html http://norvig.com/lispy.html and it doesn't care about abstractions, jargon, splat, and other things.
- walrus 14y agoHow pointless! ;)
- raganwald 14y agoBEST COMMENT EVAR.
- spullara 14y agoThis article touches on it but I do find that there are different kinds of programmers. Generally, I find that an individual either likes to solve problems, i.e. write code that as obviously as possible gets the result they need OR they like to solve puzzles, i.e. write code that is the "best" way to do something and may not be obvious at all. They generally have two different definitions of readability as well. The former thinks code is readable if it is obvious how it works and how to change it, that latter thinks it is readable if it happens to match how you would talk about it in english with no regard as to whether or not it is editable. Often the inability to edit (or even debug) stems from implicit magic vs explicit code.
- tome 14y agoI happen to agree with you, but I have probably interpreted your comment in exactly the opposite way to which it was intended! I find explicit "do this to that element, then do the other, then put it here" style very hard to read and change.
- spullara 14y agoThat isn't really what I mean. I'm definitely OK with functional style collections and futures and the like. I'm annoyed with implicits, macros and other action at a distance language features that it make it hard to reason about something locally.
- akkartik 14y agoHow 'explicit' something is has more to do with the social structures around a new technology. Does rails make it easy to understand how its conventions are applied? Does it assume that all rails programmers understand implementation details? It does not, and so it is fair to criticize it for magic. In principle you could solve the 'magic' problem without changing a line of code, purely by changing how the mechanisms provided are packaged to new programmers. I spent some time last year trying to distinguish between abstractions and services. According to me, abstractions are things that you are expected to understand the workings of, and services are things you are not. By my definitions, rails is a service, but the lisp programming model is an abstraction, because it is impossible to gain a few months of experience with lisp without understanding in great detail how macros are expanded and so on. Many programming libraries are services because in practice nobody bothers to learn how they work, and we as a field, as a species, tolerate this cargo culting. Unfortunately I had trouble articulating this distinction. Perhaps I still do. Here, take a look: http://akkartik.name/blog/libraries http://akkartik.name/blog/libraries.
- sanderjd 14y agoThis is an interesting perspective if I'm understanding you correctly. Are you saying that Rails is "magical" because it is often presented to new people that way, but if it were more often presented as "a bunch of Ruby code that does a bunch of stuff, please do go look and see how it does that stuff", it would not be "magical" without being implemented or documented at all differently?
- akkartik 14y agoKinda, if I'm making sense of your question :) Did my article make sense? I'd love to hear what you thought of it. There's basically two halves: 1. Under-promising. I'll quibble with the word 'documented'. Part of the problem is that frameworks market and position themselves with their prose to be hermetically sealed containers offering wondrous features. That's part of their documentation. That we do need to change, IMO. 2. Over-delivering. It's not enough to be open source, and it's not enough to just say, "please do go look and see how it does that stuff." Software is in the stone ages because we aren't able to actively help newcomers get quickly up to speed on our code. We need to think about the big picture of a codebase, and how it's presented to others.
- AnIrishDuck 14y agoIf I'm understanding get() correctly here, why not just use: var totals = orders.map(get("total")); Why needlessly use specialized jargon when common terms work just fine?
- raganwald 14y agoThat's fine, as is _.map(orders, get('total')) or just plain map(orders, get('total')). As I explained in another comment, splat is a combinator, which makes it particularly easy to compose. Compare: function pluck (list, propertyName) { return _.map(list, get(propertyName)); }; to: var pluck = compose(splat, get); So yes, for that one line you should use map, but when making things out of the mapping construct, it is sometimes handy to use splat. Other folks have introduced the idea that splat is really nothing more than flip(curry(map)). There's a good argument for better naming once you realize the underlying symmetry.
- tunesmith 14y agoIt's all relative. One persons old-hat jargon is another person's "needlessly clever" is another person's "too explicit". Reminds me of when I was dating, when girls were telling me they wanted someone who was "nice, but not TOO nice." Ok thanks lady, you apparently feel like you just communicated something to me, but you didn't really at all.
- deleted 14y ago[deleted]
- klochner 14y agoIt's a bit confusing that splat is being used in an atypical manner - I've usually seen it used to designate an argument aggregator. [1] http://stackoverflow.com/questions/6201657/what-does-splats-mean-in-the-coffeescript-tutorial [2] http://endofline.wordpress.com/2011/01/21/the-strange-ruby-splat/ [3] http://stackoverflow.com/questions/5917522/unzipping-and-the-operator
- raganwald 14y agoBased on feedback, expect a major naming revision to the allong.es library soon.
- scotty79 14y agoDo you know any notation that makes var splat = applyFirst(applyLast, map); understandable? I wrote implementations of applyFirst and applyLast as I thought they should work and then merged them according to applyFirst(applyLast, map) and I really got the splat function as described in the next code block. My problem is I still don't get why applyFirst(applyLast, map) works. I'm feeling that all this return function(param1, param2) notation obscures actual flow. Is there a notation for this that could make me understand it intuitively?
- anonymous 14y agoI'm not aware of specific notation, but let's first see the difference between map and splat: map is a function that takes a list and a function and returns a list map :: (function, list) -> list splat is a function that takes a function and returns a function that takes a list and returns a list splat :: (function)->((list)->list) That is sort of effectively the reverse order of parameters for map. What applyFirst does is, it takes a function and a parameter and returns a function that takes more params and prepends the given one. applyLast is similar, but appends the given one. Let's walk through the application of applyFirst(applyLast, map)(fn)(list) applyFirst(applyLast, map)(fn)(list) applyLast(map, fn)(list) -- here applyFirst has supplied the map param to applyLast and fn is the given param map(list, fn) -- and here applyLast as supplied the fn param to map, putting list first If you supply more parameters, you'd get: applyFirst(applyLast, map)(a, b)(c, d) ... map(c, d, a, b) Which wouldn't work with map per se, but shows how the applyFirst(applyLast, map) construction works.
- PySlice 14y agoAfter your excelent suggestions for names (makeMapper and makeGetter, they make everything easier, thank you), I'll try answering this, with a F#-like syntax using redundant parentheses around arguments to make them more explicit to those who don't know F# Let's suppose map is defined as: > let map (aList) (aFunction) = ...something here... (this is according to https://github.com/raganwald/allong.es/blob/master/README.md https://github.com/raganwald/allong.es/blob/master/README.md which seems to have the parameters reversed when compared to F#'s Seq.map, Lis.map, etc.) And let's suppose applyLast is defined as: > let applyLast (aFunction) (anArgument) = (fun (x) -> aFunction (x) (anArgument)) So makeMapper is: > let makeMapper = applyLast (map) Read the above as: makeMapper is a function just like applyLast, but the first argument (to applyLast) is already provided, being "map". The functionality of applyFirst is implicit when the remanining parameters are missing, so we only need applyLast. So makeMapper is called as > let mapperWithAGetter = makeMapper (aGetter) This completes the call to applyLast, so now we have a (fun (x) -> map (x) (aGetter)) Now we only need to bind x. The result is called like this > mapperWithAGetter (aList) IMO, all of this could've been written as: > let makeMapper (aGetter) = fun (aList) -> map (aList) (aGetter) Or: > let makeMapper (aGetter) (aList) = map (aList) (aGetter) Read both as: makeMapper takes aGetter and returns a function which takes aList Notice that everything would be much easiear if map took aFunction as its first parameter: > let map (aFunction) (aList) = ... > let makeMapper = map // duh Remember that partial application is implicit! It would also be easier if applyLast had better syntax, using _ for the missing parameter (the first), as in this Scala-like snippet: > def makeMapper(aGetter: Any => Any): List[Any] => List[Any] = { _.map(aGetter) } (I guess Haskell has some nice syntax for this too, but I don't think F# has anything like this) All of this was not easy to get right, I hope I didn't make any mistake regarding the position and number of parameters.
- sly010 14y agoEvery time I have to choose between "clever" and "explicit", I just choose "consistent".
- scotty79 14y agoI think that lots of confusion is brought by strangeness of jargon names makeMapper(makeGetter('total'))(orders) is much more readable for me than splat(get('total'))('orders')
- zzzcpan 14y agoRandom piece of code cannot be explicit or implicit. You have to specify exact thing you are talking about, like creating a function. You do create functions explicitly if you use map() and implicitly if you use splat(). But please don't describe the whole piece of code as explicit or implicit because of that.
- worldsayshi 14y ago> You have correctly predicted in advance that the implementation will need to change and can do so without breaking the rest of the application. Some abstractions allow you to avoid predicting things. 'map' is less restrictive than a for-loop if you don't need the powers of a for loop (store state between iterations etc..). So with some abstractions you can postpone restrictions that you don't need and get code that is easier to change. This is probably all obvious but I thought it was important. It can probably be stated more succinctly.
- carlosantelo 14y agoI will think that code has an audience and it's audience should be explicit. As long as the expectations are correct, it will just be 'sufficiently analyzed magic' and not only sorcery.
- tjholowaychuk 14y agoWith names like pluck and splat... enough said haha
- qu4z-2 14y agoI'm afraid you did not, in fact, say enough. Care to elaborate?
- rickmode 14y agoThe ActiveRecord example, to me, is an example of "clever". Any code that exhibits "spooky action at a distance" is too clever, in my opinion. When I can't hand trace a method or function and see what's going on, it is too "clever" - or at least it isn't "explicit". Spring in the Java world does this with it's XML configuration and code injection based on annotations. You can't decipher what's going on by code examination. To code in Spring you have to fully understand the Spring framework. Rails (to me at least) is similar. The steep learning curve makes them less hackable. With Clojure, by contrast, you can always look at a function or macro and (eventually) grok what's happening.
- lmm 14y agoHah. I was with you up until the last line. Macros do exactly the same thing as spring-aop - they transform your function into some different code, so that when it's called it does something different to what was written. /scala guy, very worried by the introduction of macros, despite (perhaps even because of) all the cool things they can do
- rickmode 14y agoCertainly macros can be hard to decipher, however they don't set up this spooky action at a distance, for example a annotation signaling bytecode injection. There's no way to hand-trace through that; the only solution is to learn the library / framework in-depth.
- lmm 14y agoHonestly I don't see the difference. If you're stepping through in a debugger, you see the "real" code. If you're just reading the code, you read something that looks like a function and then have to jump far away to the annotation or macro to find out what actually happens. Assuming IDE integration it's really no easier or harder to jump to a spring aspect than it is to a macro.
- rickmode 14y ago
- ioquatix 14y agoHow about: orders.collect(&:total) Isn't this all a bit of a storm in a teacup? It seems to me that we should minimise the amount of code because #(code) ~= #(bugs).
- ScottBurson 14y agoYou have an unnecessary lambda^W anonymous function expression in 'mapWith': function mapWith (fn) { return function (list) { return map(list, function (something) { return fn(something) }); }; }; could be simplified to: function mapWith (fn) { return function (list) { return map(list, fn); }; };
- raganwald 14y agohey Scott, I just saw this. It depends on whether your map implementation expects a unary function or whether it injects optional additional arguments like the index, and whether your function is unary or accepts optional other arguments.
- andreyf 14y ago> mapWith is the self-curried right partial application of the map function. If that seems like gobbledegook to you, it's because I'm using jargon. Reginald say "jargon" as if he's proud of it, but for this example, the more pejorative meaning seems appropriate. Why not just say "curried map"? I was under the impression that's why the lambda is the first argument.