Skip to content

Instantly share code, notes, and snippets.

@aesnyder
Last active August 29, 2015 13:57
Show Gist options
  • Save aesnyder/9403886 to your computer and use it in GitHub Desktop.
Save aesnyder/9403886 to your computer and use it in GitHub Desktop.
Functional Chaining in CoffeeScript and Underscore.js
# ob - a chainable object helper
#
# ob({foo:1, bar: 2, zap: undefined})
# .compact()
# .map((v) -> v + 1)
# .value
#
# // {foo:2, bar: 3}
ob = (obj) ->
value: _.clone obj
map: (cb) ->
@value = _.object _.keys(@value), _.map @value, cb
this
compact: ->
c = _.clone @value
_.each c, (v, k) ->
if not v then delete c[k]
@value = c
this
# sum - chainable adding machine, can add either an array of numbers or array of objects with numerical properties
# sum([1,2,3]).value // 6
# sum({f:1, b: 1},{f:2, b: 2}, {f:3, b: 2}).value // {foo: 6, bar: 5}
# sum({f:1, b: 1},{f:2, b: 2}, {f:3, b: 2}).by('bar').value // 5
sum = (array) ->
reduectionStrategy = (type) ->
if type is 'object'
numbers = _.map array, (obj) ->
ob(obj)
.map (v) -> v if typeof v is 'number'
.compact()
.value
[numbers, ((s, n) -> ob(n).map((v, k) -> s[k] + v).value), ob(numbers[0]).map(-> 0).value]
else if type is 'number'
[array, ((s, n) -> s + n), 0]
value: _.reduce.apply this, reduectionStrategy(typeof array[0])
by: (prop) ->
@value = @value[prop]
this
@david
Copy link

david commented Mar 7, 2014

First of all, I'm impressed, and really happy that more of us are looking at the functional ways. Keep it up!

Regarding the code itself, I'm not really sure about what you had in mind when you wrote this, so I'll just comment about what I see.

map: map is a little surprising to me because you're only mapping over the object's values, but given it's a "chainable object helper" that's probably the intent.

compact: here's another way to write it that doesn't require the initial clone:

@value = _.reduce(@value, ((acc, v, k) -> if v? then acc[k] = v; acc;), {});

This still mutates the accumulator, but I can live with that for now, because the mutation is local.

Also note that I'm using v?. In your code you used not v, but I assume that's not what you want, because it will remove "", 0, etc.

sum: it seems to me sum is trying to do too much and that its behavior is also a bit surprising. It's also somewhat monolithic, in that it doesn't really allow for function composition, except if you change the body of the function itself.

So I'd rework this a bit. For example:

I'd turn sum into the classic number-summing function, because I think that's what the name sum calls for:

sum = (a, b) -> a + b

Now we have a function we can use as a higher order function:

sumAll = (values) -> _.reduce(vals, sum)

sumAll([1, 2, 3]) # => 6
sumAll(foo: 1, bar: 2, baz: 3) # => 6

sumBy = (objs, by) -> sumAll(_.pluck(objs, by)) # or sumBy = compose(sumAll, _.pluck)
sumBy([{foo: 1}, {foo: 2}, {foo: 3}], 'foo') # => 6

sumFields = (objs) ->
  keys = _.keys(_.first(objs))

  _.object(keys, _.map(keys, _.partial(sumBy, objs)))
sumFields([{foo: 1, bar: 0}, {foo: 2, bar: 1}, {foo: 3, bar: 2}]) # => {foo: 6, bar: 3}

Be warned: I didn't test this. :)

So, I think this is a bit different than your original code, but I find that building small functions instead of creating the chainable helper is more flexible. Now, armed with the functions above, you could create a chainable helper, I guess, whose goal is to simply chain everything. However, underscore already has compose which that, more or less. Together with partial, it allows for very powerful compositions.

2 more things:

  1. Javascript is not a functional language, in the sense that most of its data structures are mutable, which is why I obnoxiously keep pushing for people to consider clojure(script). In theory, you can use something like mori to give you the immutable datastructures, maybe together with sweetjs. I know someone did it some time ago but I can't find the link.
  2. If you haven't read it already, I highly recommend Functional Javascript by Michael Fogus.

Feel free to ping me to discuss this, ask questions, etc.

@aesnyder
Copy link
Author

aesnyder commented Mar 7, 2014

Hey thanks,

Yeah functional is pretty awesome. I had read about half of the Functional Javascript book, I just picked it back up and am going to finish it this week.

all of this started with one function

reduceField = (group, field) ->
  _.reduce group, (s, n) ->
    s + n[field]
 , 0

Then I made sum to try to abstract this into a higher level concept and in doing that I ended up abstracting parts of it out int 'ob'. To be honest I felt the same way about the monolithic and surprising nature of sum. I think that in reality this behavior could be moved into ob as reduce as it operates in the same manner as ob's map function.

Thoughts?

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