Skip to content

Instantly share code, notes, and snippets.

@aesnyder
Last active August 29, 2015 14:01
Show Gist options
  • Save aesnyder/9923d5be47cfbc8a1e38 to your computer and use it in GitHub Desktop.
Save aesnyder/9923d5be47cfbc8a1e38 to your computer and use it in GitHub Desktop.
_.chainable
# 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
@aesnyder
Copy link
Author

I guess its a matter of to each his own.

I personally find your composition examples very difficult to read, and I'd say I'm more comfortable with composition than the average developer. For that reason I would have a really hard time allowing that code to make it into my projects.

Maybe for you composition will lead to more flexibility, faster development and less bugs. But I can tell you one thing for sure it will make on-boarding developers to the code base significantly harder. As an agency that ships code for others to maintain we have to be cognizant of that. For that matter we have to have a code base where you can leave the project and another developer can easily jump on and get moving. Your examples would be significantly harder for most developers to jump in and work with. Believe me I've been in projects where one clever developer wrote insanely abstracted and well written code. Something that academics would all drool at. In fact we were all drooling at the code as he was getting the PRs merged. Then when there was a bug found in the code he was the only developer out of 5 who could work on the feature. I'm telling you without a doubt that the code you posted above falls into the same category.

@RocketPuppy
Copy link

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 the Function prototype. The end result being something that could look like:

Function.prototype.c = (g) -> c(this, g)
Function.prototype.$ = (args...) -> _.partial(this, args...)

findCurrentUser = find.$((o) -> o.name == currentUser.name))

#alias sideEffect so it looks more familiar
tap = sideEffect

includeScore.
  c includeRank.
  c maxBy('rank').
  c tap(renderMaxScoreTemplate).
  c tap(renderUserStandingsSidebar).
  c findCurrentUser.
  c tap(renderCurrentUserDetails)

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment