Last active
August 29, 2015 14:01
-
-
Save aesnyder/9923d5be47cfbc8a1e38 to your computer and use it in GitHub Desktop.
_.chainable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# takes an object of methods and makes them optionally chainable, | |
# all lodash methods are accessible in the chain too | |
# | |
# The following methods are added as well: | |
# attr: returns value of attribute on given object | |
# inlcuding: extends either object, or each object in collection | |
# with attrName: callback(object) | |
# if no callback is given then attrName is assumed to be a function | |
# passed to _.chainable: attrName: attrName(object) | |
# | |
# playerData = [ | |
# { name: 'Bobby', kills: 125, deaths: 63, shots: 128 } | |
# { name: 'Annie', kills: 201, deaths: 14, shots: 2432 } | |
# { name: 'Jacob', kills: 101, deaths: 188, shots: 201 } | |
# ] | |
# | |
# players = _.chainable | |
# kdr: (p) -> | |
# p.kills / p.deaths | |
# accuracy: (p) -> | |
# p.kills / p.shots | |
# score: (p) -> @kdr(p) + @accuracy(p) | |
# | |
# players(playerData).including('score').max('score').attr('name').value() // Bobby | |
# players(playerData).find(name: 'Bobby').kdr().value() // 1.9841269841269842 | |
# players(playerData).find(name: 'Bobby').including('kdr').value() // { name: 'Bobby', kills: 125, deaths: 63, shots: 128, kdr: 1.9841269841269842 } | |
_.mixin 'chainable': (obj) -> | |
methods = _.extend _.cloneDeep(obj), _, | |
including: (obj, attrName, value) -> | |
extObj = (o) -> | |
val = if _.isFunction(value) | |
value.call(methods, o) | |
else if _.isString(value) | |
value | |
else | |
methods[attrName](o) | |
_.extend o, _.object([attrName], [val]) | |
if _.isArray(obj) | |
_.map obj, extObj, methods | |
else if _.isObject(obj) | |
extObj(obj) | |
attr: (obj, metric) -> obj[metric] | |
_.extend (arg) -> | |
_.extend | |
__collector: _.cloneDeep(arg) | |
value: -> @__collector | |
, | |
_.mapValues methods, (method) -> | |
-> | |
args = _.toArray arguments | |
args.unshift(@__collector) | |
@__collector = method.apply(methods, args) | |
this | |
, methods |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There's a paradigm shift here, and it's a big one. Like all big changes it will look odd at first. It's a little like learning to read a new language. There's a different grammar and style of thinking required that takes practice to develop. I have shown tangible gains though. Programming with function composition is more flexible. As for readability, there are a number of ways to improve readability. The easiest is to use a language that was designed from the ground up to support this like Haskell. You could also use something like Purescript, but really Javascript can support this fairly easily.
To increase readability in Javascript (well Coffeescript) we can hang the composition operator off of the
Function
prototype so our composition looks more like using the dot operator. We can do the same thing with partial application using$
on theFunction
prototype. The end result being something that could look like:This looks almost the same as your original chain, and it's more modular. We can split it however we want, insert things wherever we want, and manipulate it any way we want. There's no wrapper that we need to go through.
That's all I really have to say. I've shown some tangible gains to using function composition over just
chainable
. Perhaps my last code sample is more readable than the previous ones. I don't really think so, but I'm used to reading this style of code. Let me know if you have any other questions, I'd be happy to answer them.EDIT: Updated code to have proper line continuations.