5 ms·
Combinator Recipes for Working With Objects in JavaScript
- ville 14y agoIt is stated that JavaScript doesn't have partial function(al) application. Isn't it available through the built in Function#bind method?
- raganwald 14y agoYes, and yet, no. Bind binds this as well as arguments from L2R, so you can't bind arguments while allowing apply or call to set the context. You also can't bind arguments leaving a "hole."
- jrajav 14y agoFunction#bind isn't the greatest solution, since it forces you to set 'this' and only allows left-to-right, flat application. Many people have written partial application libs to make it easier to work with (I even have one of my own on github). A good one is substack's: https://github.com/substack/node-ap https://github.com/substack/node-ap
- raganwald 14y agoThere's no need to rush out and buy the book, but if you're interested, here's a coupon code to get it for $9.99 today: "Hacker_saturday" http://leanpub.com/javascript-allonge http://leanpub.com/javascript-allonge
- jan_g 14y agoThanks. I've bought it, because the article was very informative.
- hermannj314 14y agoThank you for the coupon code, I'm looking forward to reading this.
- jacobr 14y agoThanks! I'm not sure if I missed it, but maybe you could explain immediately invoked functions somewhere, as the examples rely heavily on them. Also, the code example in the introduction showing the format of code examples is CoffeeScript, which is a bit confusing.
- deleted 14y ago[deleted]
- deleted 14y ago[deleted]
- k3n 14y agoIsn't splat() just an implementation of partial application around a specific function (map)?
- raganwald 14y agoVery much so, thanks for pointing that out!
- wonderzombie 14y agoI love these kinds of articles. I know about partial function application, and I'm already sold on the merits of higher-order functions, et al. (A one-two punch of Haskell and Clojure sold me.) Maybe that's why it's nice to see other ways of describing or encoding these concepts— it was a neat little "aha" moment when we got to pluck. Immediately it reminded me of how, in Clojure, keywords are functions. For those of you who aren't familiar, Clojure has symbolic keys like in Ruby (e.g. :foo), called keywords. So given the 'inventories' variable in the pluck() example, you could write: (map #(get % :oranges) inventories) The #(get % :oranges) syntax is an anonymous function, invoking get to retrieve :oranges from each inventory in inventories. But it turns out you don't even need to do that. You can just do this instead: (map :oranges inventories) Anyway, I guess what's funny to me is that I used JS for a little while, and I liked it pretty well. It's only in retrospect that I start to see the extent to which it seeded my brain with these patterns.
- raganwald 14y agoI left "compose" out of this excerpt, but in the book: var pluck = compose(splat, get);
- wonderzombie 14y agoCompose is another piece I wish more languages had. The functional style of small, orthogonal functions has changed the way I write code, but in many imperative languages, function composition as I know it just doesn't exist. And sometimes that disparity is a real bummer. You can get close in dynamic languages, as with JS, Ruby, and Python. But if we're talking about Java or C++, it's just not happening, not without a bunch of work and not within an existing codebase where the introduction of new styles might actually be counterproductive.
- jrajav 14y agoThe functions covered in the article, if anyone wants to play around with them: https://github.com/jrajav/rcombinator https://github.com/jrajav/rcombinator
- raganwald 14y agoEverything I write on my blog is covered by the MIT license, so go wild!
- jrajav 14y agoOh, I do see now that there's a license.txt at the top level of your blog repo. Now I feel silly. :) (Context: I contacted the author before publishing to see if this was alright.)
- ville 14y agoI sent a pull request :)
- wylee 14y agoIn `maybe`, would it make sense to check the args passed to the wrapper against the number of args the original function expects? Something like this: function maybe (fn) { var expected_number_of_args = fn.length; return function () { var i, arg; for (i = 0; i < expected_number_of_args; ++i) { arg = arguments[i]; if (arg === null || typeof arg === 'undefined') { return null; } } return fn.apply(this, arguments) } }
- jrajav 14y agoThis would make it unusable for a function that, itself, uses the 'arguments' variable rather than formal arguments. Function arity in Javascript is a tricky subject when we touch on partial application and other functional programming techniques that act on the arguments passed to a function, but the generally accepted solution is to avoid Function#length and ignore that formal arguments exist.
- wylee 14y agoThe `maybe` in the article allows for this ambiguous situation: var fn = function (a, b) { ... }; var maybe_fn = maybe(fn); var obj = {}; maybe_fn(1); // calls fn maybe_fn(1, null); // doesn't call fn maybe_fn(1, obj.nonexistent_prop); // also doesn't call fn In my mind, all of these calls to `maybe_fn` should do the same thing. Maybe not, though?
- jrajav 14y agoThis is a good point, and may be an argument for having a "formal_maybe" or perhaps having it check at least as many arguments as there are formal arguments. However, I don't think that this situation would be as important in practical use. You would normally use this function to allow for passed arguments to be null or undefined - not to allow for some arguments not to be passed.
- ufo 14y agofunction.length is certainy a PITA dus to the `arguments` issue that was mentioned. In particular, function.length doesn't work on functions returned by our own combinators, a big no-no! In the end I adopted a convention where every function that depends on the number of arguments of something else receives that number sa a first parameter: maybe(2, function(a,b){ ... })
- ufo 14y agoI had some experience using things like these inpractice. Some tips I would give out * Make sure you make you library comprehensive. Remember that main benefit of doing something like this instead of explicit functions is conciseness and consistent patterns. * Partial function application can be tricky. I find it is only really useful if you use it on all most of the standard combinators and you need to pay very good attention to what is the order of arguments you use. I also suggest putting the arguments on the same line if you are doing partial functoins by hand like he did: //This way you don't confuse partial functions //and more general closures with inner state. function get(attr){ return function(object){ return object[attr]; };} * There are lots of way to do partial function application - do you let multiple arguments be passed at once or not? Do you allow for placeholder "hole" arguments or do the arguments have to always be passed in left-to-right order? What do you do with "this"? In the end I ended up having functions for all of the different cases but in general, I'd at least recommend not using null/undefined checks to test if arguments have been passed since that can easily mask dynamic errors. I'd rather check arguments.length explixitly or use a special placeholder object from my library if I need one. * One of the big downsides of using partial function application and these combinators all over the place is that they make the call stack a lot more complicated. I use the JS debugger a lot and this can make things much more annoying to deal with. In particular, your "real" code gets hidden inside opaque "f" variables, its harder to set useful breakpoints and sometimes its harder to look at closed over variables. * Another problem I had was that many of my coworkers were not used to the partial application and functional patterns. In the end we ended up using these combinators less and less, and now they are too rarely used to justify the "abstraction cost".
- KeithMajhor 14y agoThis was a great post. Just a little nitpick. I think this line near the bottom: var something = maybe(doesntCheckForSomething(value)); Should actually be: var something = maybe(doesntCheckForSomething)(value); If I'm wrong then I likely missed something so please let me know. Thanks.
- raganwald 14y agoI think you're right, thanks!!!
- nadaviv 14y agoI find higher-order function to be really beautiful with CoffeeScript: partial = (fn, a...) -> (b...) -> fn.apply this, [a..., b...] compose = (fns...) -> (a) -> a = fn a for fn in fns; a splat = (fn) -> (arr) -> arr.map fn get = (attr) -> (obj) -> obj[attr] maybe = (fn) -> (args...) -> return unless args.length return arg for arg in args when not arg? fn.apply this, args
- wonderzombie 14y agoBeauty is, of course, subjective, but the use of 'this' stands out to me as unusual for these particular patterns. Call it a slightly leaky abstraction, I suppose. That said, at least the function declaration syntax looks a lot like Haskell's type signatures. So maybe they're doing something right. :)
- bonobo 14y agoThe link to Part II seems to be broken.
- raganwald 14y agoPart II: https://github.com/raganwald/homoiconic/blob/master/2012/12/combinators_2.md#combinator-recipes-for-working-with-objects-in-javascript-part-ii https://github.com/raganwald/homoiconic/blob/master/2012/12/...