Duck-wrapping
Duck-wrapping (verb): If it doesn't quack like a duck, wrap it in a duck.
JavaScript has an problematic idiom that I have come to consider a code-smell: functions that wrap some values (based on their type) in other types. I consider this to be an issue because it impairs the ease at which you can reason about the code in question, and it can complicate some logic with extra boilerplate.
Here are some examples:
[1, 2].concat(3); //=> [1, 2, 3]
[1, 2].concat([3]); //=> [1, 2, 3]
$.map([1,2,3], function (_, a) { return a; }); //=> [0, 1, 2]
$.map([1,2,3], function (_, a) { return [a]; }); //=> [0, 1, 2]
Here, concat
is duck-wrapping its argumenti with an Array
if it is not already an Array
instance.
$.map
is a little more insidious: it is not actually the map function as functional programmers would recognise it. It is instead a duck-wrapping variant of mapcat
/flatMap
/concatMap
!
The downside in both examples is how do you get an Array
that you want as a value in the resulting Array
: You explicitly wrap it in another Array
, or find some other method to do the job. For example:
[1, 2].concat([[3]]); //=> [1,2,[3]]
$.map([1, 2, 3], function (_, a) { return [[a]]; }); //=> [[1], [2], [3]]
This is annoying at the level of Arrays
, but gets more difficult with more complex types, and function interactions. The recent brouhaha around the Promise/A+ highlights one such example: It is difficult to return a Promise
of a Promise
as a value from onFulfilled
because then
duck-wraps the return value as described in the Promise Resolution Procedure
The solution to all these awkwardnesses is to simply provide two methods for the operations: Array
might have concat
and append
for instance. jQuery could have map
and flatMap
ii. These changes both clearer and actually easier to use than the duck-wrapping variants.
- The examples of
concat
above only work with one argument for simplicity, but it is actually takesarguments
of things to concat to the object in order: Each argument is duck-wrapped as needed. E.g.:[1, 2].concat(3, [4]); //=> [1, 2, 3, 4]
. - It just so happens that fantasy-land has formalised interfaces for both the
concat
case and formap
andflatMap
(applicable to bothArray
andPromise
types).