Last active
August 29, 2015 13:57
-
-
Save aesnyder/9403886 to your computer and use it in GitHub Desktop.
Functional Chaining in CoffeeScript and Underscore.js
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
# 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 |
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
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: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 usednot v
, but I assume that's not what you want, because it will remove""
,0
, etc.sum
: it seems to mesum
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 namesum
calls for:Now we have a function we can use as a higher order function:
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:
Feel free to ping me to discuss this, ask questions, etc.