4 ms·
these arguments are bizarre in that either a) they're very easily avoidable or b) they exist in FP languages, just hidden - Immutable data arguments Nobody is
by rtpg 13y ago
these arguments are bizarre in that either a) they're very easily avoidable or b) they exist in FP languages, just hidden
- Immutable data arguments
Nobody is stopping you from just working as if JS is immutable.
- Recursive functions and the stack
Tail call elimination would be nice. But for most common things, instead of writing the recursive call yourself, you'd use other common functions in the FP toolbox like map, reduce. You only need to find a good implementaiton of these base functions and 90% of your headaches are gone.
But the stack is still an issue in other languages (See foldr debate in Scala).
- Partial application and composition
How do you think this is implemented in other FP languages? At one point you do have to store pointers to the functions you are composing to build the composed function , so you'll have at least one new object.
>but if you are doing a lot of Point-free Programming, then your loops have the possibility of generating a ton of garbage.
You shouldn't be doing something like for(..){ compose(f,g)(elt); } obviously, but this is just a specific case of "cache values". If you store the function beforehand, the amount of garbage created is linear to the size of your code, not of your data. (Never mind that you shouldn't be looping in FP).
If you try hard enough and don't mind a more obscure syntax, you can actually reduce this function creation overhead too.
var composeObj = Object.create(null);
composeObj.prototype.invoke = function(x){
return this.g(this.f(x));
}
compose = function(f,g){
var rV=Object.create(composeObj);
rV.f = f; rV.g=g;
return rV;
}
h = compose(function(elt){return 2*elt;},function(elt){return elt+3;});
h.invoke(10);
If you're that crazy about FP garbage problems, this model will create exactly as much objects as any other compose function in any FP language (tag to indicate closure, pointers to composed functions).
- i_s 13y ago> Nobody is stopping you from just working as if JS is immutable. You cant just work "as if" things are immutable when they aren't. You have to be super careful about not mutating objects, for example copying an array with .slice() before messing with it. That is not ideal because 1) it is manual, and 2) copy on write is inefficient compared to the structure sharing that immutable data structures use. If you want the benefits of persistent data structures in Javascript, you have to use something like Mori. And that is ok, but not as nice as it is in functional languages, because you loose the specialized literal syntax to create the data structures and work with them. For example, to create a vector in mori you have to: var vec = mori.vector(1,2,3); And to map a function on it, you have to use Mori's special version of map: var result = mori.map(inc, mori.vector(1,2,3,4,5))
- platz 13y agoThe issue with function composition in javascript, for me, is that it's difficult to reason that the functions you are composing in fact do have all their types "lining up". I think there was some outfit "loop/recur" that was doing heavily composition-style javascript. They often write the types for function in comments in haskell notation above the actual function definitions.
- anaphor 13y agoFWIW if you're doing explicit recursion for something then it's usually either: a) Going to be bounded by a small number or user controlled b) Something you would have to use recursion for in any language, like a recursive descent parser. So like you said call stacks growing is usually not a big issue if you know what you're doing.
- malandrew 13y agoWhile there is no tail call optimization, you can trampoline a recursive function call to eliminate issues with blowing the call stack. function trampoline(fn /*, args */) { var result = fn.apply(fn, _.rest(arguments)); while (typeof result === 'function') { result = result(); } return result; } I learned this wonderful trick from fogus' functional javascript, which I cannot recommend enough.
- kevingadd 13y agoYeah, but that trampoline function is gonna run like garbage because it contains 3 or 4 different things that completely defeat modern JIT optimizations.
- kevingadd 13y agoThe problem with treating JS like a good functional language when it's absolutely a terrible one (not to mention a terrible language in general) is that you get badly-performing code riddled with subtle problems. You build algorithms around assumed immutability, and then it's trivial to accidentally introduce mutation into them - sometimes entirely by accident, since third party JS libraries can introduce mutation into non-mutating operations. For example, I once pulled in a third-party JS 'printStackTrace' library for debugging purposes, then weeks later had to spend half a day troubleshooting to figure out that printStackTrace was mutating arbitrary arrays from the stack, thus permanently corrupting random globals. If you're maintaining a large codebase with multiple people, the risk of accidentally introducing mutability is very, very real. Faking tail calls in JS requires using constructs that introduce unnecessary GC pressure and prevent most JIT optimizations, compared to tail calls in a real functional environment where they deliver great performance without GC pressure. Partial application and composition in JS performs like garbage, as mentioned above with faking tail calls. 'True' FP runtimes can do better here, as do some static runtimes (.NET, for example, has the ability to do a subset of composition and partial application without any GC or call overhead whatsoever.) Your composeObj example is another good one. You've created this special variant of JS callables that isn't actually a function (because JS functions are rigid and hard to build composable primitives out of), and it contains patterns that subdue JIT optimization (Object.create, for one). Again, attempts to pretend that JS is a good functional language just produce ugly, underperforming, hard-to-maintain code.