We aim for an implementation both clean and elegant, but the API is king. We sacrifice a great deal of implementation elegance for even a slightly cleaner API.
What is the difference between elegance and cleanliness in that context ... can you give me an example so I can understand I am a little confused I am sorry ...
I think "clean and elegant" is mostly a rhetorical flourish. While they are not quite synonyms, there is a great deal of overlap here, especially when talking about the implementations.
But the point is that if we can make a better API for the user, we do so, even at the cost of making the underlying code uglier. Conversely, if a beautiful simplification of the code would lead to even a slightly less comfortable API, we wouldn't do so. In practice, of course, this is tempered by other real-world considerations such as performance and even our own patience. But it's an important goal we mostly stick to.
Here's an example: We could write map as a pure function in an extremely clean manner:
const map = curry(fn, xs) => xs.map(fn))
Instead we have three separate files involving a number of dependencies and these blocks of code:
var map = _curry2(_dispatchable(['fantasy-land/map', 'map'], _xmap, function map(fn, functor) {
switch (Object.prototype.toString.call(functor)) {
case '[object Function]':
return curryN(functor.length, function() {
return fn.call(this, functor.apply(this, arguments));
});
case '[object Object]':
return _reduce(function(acc, key) {
acc[key] = fn(functor[key]);
return acc;
}, {}, keys(functor));
default:
return _map(fn, functor);
}
}));
export default map;
export default function _map(fn, functor) {
var idx = 0;
var len = functor.length;
var result = Array(len);
while (idx < len) {
result[idx] = fn(functor[idx]);
idx += 1;
}
return result;
}
function XMap(f, xf) {
this.xf = xf;
this.f = f;
}
XMap.prototype['@@transducer/init'] = _xfBase.init;
XMap.prototype['@@transducer/result'] = _xfBase.result;
XMap.prototype['@@transducer/step'] = function(result, input) {
return this.xf['@@transducer/step'](result, this.f(input));
};
var _xmap = _curry2(function _xmap(f, xf) { return new XMap(f, xf); });
export default _xmap;
Why do we do this? Because it's a better user experience to be able to write these:
map (square, [1, 2, 3, 4, 5]) //=> [1, 4, 9, 16, 25]
map (square, {a: 1, b: 2, c: 3}) //=> {a: 1, b: 4, c: 9}
map (square, increment) (4) ==> 25
map (square, Just (7)) //=> Just (49)
map (square, Nothing()) //=> Nothing
// ...
than to have separate mapArray, mapObject, mapFunction, mapMaybe, etc,, functions. This version of map supports transducers, avoids the map(parseInt) pitfall, and works on arbitrary Functors.
Here we went a long way from the cleanest, most elegant implementation into a much uglier one in order to make our API more elegant. And that's the point.
My God I love that answer I was unable to understand the difference between the two terms and asked the question naively without knowing what to expect as an answer but I feel that what ever my expectations could have been this answer goes beyond my theoretical expectations and make me understand that your work in this library is taking functional programming in JavaScript at an artistic level (a metaphorical way of describing the perfection of your technical knowledge)
@Lucxium:
I suspect that the library is much more workmanlike than artistic. But thank you for your kind words.
Most helpful comment
I think "clean and elegant" is mostly a rhetorical flourish. While they are not quite synonyms, there is a great deal of overlap here, especially when talking about the implementations.
But the point is that if we can make a better API for the user, we do so, even at the cost of making the underlying code uglier. Conversely, if a beautiful simplification of the code would lead to even a slightly less comfortable API, we wouldn't do so. In practice, of course, this is tempered by other real-world considerations such as performance and even our own patience. But it's an important goal we mostly stick to.
Here's an example: We could write
mapas a pure function in an extremely clean manner:Instead we have three separate files involving a number of dependencies and these blocks of code:
Why do we do this? Because it's a better user experience to be able to write these:
than to have separate
mapArray,mapObject,mapFunction,mapMaybe, etc,, functions. This version ofmapsupports transducers, avoids themap(parseInt)pitfall, and works on arbitrary Functors.Here we went a long way from the cleanest, most elegant implementation into a much uglier one in order to make our API more elegant. And that's the point.