I need to do some advanced array sorting in CoffeeScript and I came across the thenBy.js micro-library which perfectly fit my needs. It's written in JavaScript, so I translated it to CoffeeScript so that I could use it inline in my .coffee file, and I ran into some problems with the translation. This doesn't work:
firstBy = ->
# mixin for the `thenBy` property
extend = (f) ->
f.thenBy = tb
return f
# adds a secondary compare function to the target function (`this` context)
#which is applied in case the first one returns 0 (equal)
#returns a new compare function, which has a `thenBy` method as well
tb = (y) ->
x = this
return extend((a, b) ->
return x(a, b) or y(a, b)
)
return extend
However, if I wrap with parens and put on trailing parens, it does work:
### Notice the starting paren
firstBy = (->
# mixin for the `thenBy` property
extend = (f) ->
f.thenBy = tb
return f
# adds a secondary compare function to the target function (`this` context)
#which is applied in case the first one returns 0 (equal)
#returns a new compare function, which has a `thenBy` method as well
tb = (y) ->
x = this
return extend((a, b) ->
return x(a, b) or y(a, b)
)
return extend
)() ### <- Notice the ending parens
I'm having a no luck understanding why putting those trailing parens on the thing causes it to work. I understand that I have an anonymous function, and I'm then calling it with those parens (see this answer), but why does that work?
The trailing parens are calling the function larger function (the one defined as firstBy in your first example), effectively setting the variable to the function's return value: the extend function. In other words:
# Let firstBy1 be equivalent to your first definition, and
# firstBy2 equivalent to your second. Then
# firstBy1() is functionally equivalent to firstBy2
console.log firstBy2
### logs:
# function (f) {
# f.thenBy = tb
# return f
### }
# So:
func = (x, y) ->
# ... some comparison of x and y...
foo = firstBy2(func) # or `foo = firstBy1()(func)`
console.log foo.thenBy
### logs:
# function (y) {
# x = this
# return extend(function (a, b) {
# return x(a, b) or y(a, b)
# })
### }
This is probably best illustrated with an example:
# For brevity/clarity...
firstBy = firstBy2
randomNums =
{ attr1: 1, attr2: 2 },
{ attr1: 2, attr2: 8 },
{ attr1: 4, attr2: 2 },
{ attr1: 5, attr2: 2 },
{ attr1: 5, attr2: 3 },
{ attr1: 6, attr2: 1 },
{ attr1: 3, attr2: 1 },
{ attr1: 2, attr2: 4 }
func1 = (x, y) ->
if x.attr1 == y.attr1
return 0
if x.attr1 > y.attr1 then 1 else -1
func2 = (x, y) ->
if x.attr2 == y.attr2
return 0
if x.attr2 > y.attr2 then 1 else -1
When we call...
randomNums.sort(firstBy(func1).thenBy(func2))
...firstBy(func1) is evaluated first. It returns func1 with a thenBy attribute:
func1 = (x, y) ->
if x.attr1 == y.attr1
return 0
if x.attr1 > y.attr1 then 1 else -1
func1.thenBy = (y) ->
x = this
return extend((a, b) ->
return x(a, b) or y(a, b)
)
So then, calling firstBy(func1).thenBy(func2) calls the newly added thenBy parameter attached to func1, yielding:
func1.thenBy(func2) =
extend((a, b) ->
func1(a, b) or func2(a, b)
)
The extend function then applies another (uncalled) thenBy attribute to this anonymous function, yielding the final function that sort uses to order randomNums. It's basically calling func1 on each pair of numbers in the array, and in the event that func1 returns 0 (when the attr1's of each object are equal), then func2 is evaluated on the same pair. This can be extrapolated endlessly with more calls to thenBy. In the end, our function call returns:
[
{ attr1: 1, attr2: 2 },
{ attr1: 2, attr2: 4 },
{ attr1: 2, attr2: 8 },
{ attr1: 3, attr2: 1 },
{ attr1: 4, attr2: 2 },
{ attr1: 5, attr2: 2 },
{ attr1: 5, attr2: 3 },
{ attr1: 6, attr2: 1 }
]
Hope that helps!
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments