Id highly recommend an older but wildly relevant book called Javascript Alonge. It totally changed the way I wrote JS and understood functional languages for the better.
What I also noticed was one of the problems the author claims that "imperative" style loops have:
>Off-by-one bugs are more probable. For example, you might write i<22 instead, and then the loop would end at 21, not 22.
Ironically, their implementation of a function called "range" differs from the (likely) best known function by that name (that of Python), causing exactly this problem. (Also, the Pythonic range is probably a better fit for a language with 0-based indexing.)
One of the best tools I found to really dive into functional programming in JavaScript was Lodash's "fp" variant. It will swap the order of the arguments to its functions and curry them all by default. So you're building a function to "add one"
const addOne = map(function(n) { return n + 1 });
Can then be applied against an array of numbers:
addOne([1, 2 ,3])
> [2, 3, 4]
This is fairly basic though. Where it really shines is when you start to use the `flow` function will can be used to compose multiple functions together (and makes the order of execution follow from left to right)
flow(addOne, reverse)([1, 2, 3])
> [4, 3, 2]
I find this is one of the small redeeming features of JavaScript!
I worked on a project that used lodash/fp for a couple of years. It really is game-changing if you have a large object that you need to change in similar ways across different properties.
Theres also Ramda (https://ramdajs.com/), which I slightly prefer because the documentation is significantly better.
Rambda is an excellent library but I've found difficult to use to teach newcomers to FP concepts. Once you start to get into transducers, lenses (which Lodash has, just calls them something else) then it can be a bit of a bridge too far as a tool to start with. Lodash/FP always seemed to me to strike that nice balance between complexity and getting things done.
That said, if I was on a project with a bunch of developers that really grok'd FP concepts then Rambda would be my first pick, absolutely.
date-fns has the same fp variant which can make a lot of sense.
A note from personal experience: never mix fp and normal variants of date-fns and lodash in one project. If you import isAfter from date-fns/fp in one file and date-fns in another (or your editor imports it for your) you‘re gonna have a BAD time.
The article at least paid lip service to `map` and `reduce`. It's a shame the author spent most of their effort describing `forEach`. This is quite a superficial take on FP. It would be better to guide the reader into a different mental model entirely. What is presented here is essentially a syntax-swap.
If you're a JavaScript developer and you want to learn Functional Programming, then learn Elm. It's a small, easy language, and learning it will make you a better JavaScript programmer.
I write a lot of JavaScript and have never decided on using .forEach over something else. I have never used it. Nor have I, or will I, use yield / generators, because they are confusing and silly, which is why nobody uses them. I don't believe these things will make you write better JavaScript, but rather, worse JavaScript.
These things are all just tools in the programmer's tool box. Which one you use will come down to the one you think is most suitable at the time based on your knowledge and experience.
Generators are very useful in more complex scenarios, but when used in simpler environments they'll just add confusion - in fact the article is a good example of adding confusion with a Generator.
Personally, when dealing with arrays I favour using .map, .reduce, .filter and .forEach.
Generators are fundamentally important to have to solve a number of problems. But like me, you probably never need them because you aren’t solving those problems.
FP is about avoiding side-effects; forEach() can only cause side-effects, so it's not really functional at all, and yet it's superficially designed to blend in with the actual functional array methods like map().
IMO it's the worst of both worlds and there's virtually never a good reason to use it. If you're going to write a side-effect-y loop, a classic for-loop (or more often, a for..of loop) helps to emphasize that the code is side-effect-y.
All most programmers will need is a classic for-loop. For of, forEach and any other kind of iterator is really not necessary and adds to the language bloat that just confuses less experienced developers.
I find it really difficult to believe that there are developers out there that never need to be able to break or continue in a loop. What exactly would they be developing?
I believe it was gotten from the ambiguities of English - when you say "all most programmers need is " it could mean all that is needed most of the time but probably the more usual meaning would be that a great number of programmers (most programmers - greater than 50% of programmers), will never need anything but `map` or `reduce`. If the second meaning is taken then this means as a corollary that these developers never need to be able to break or continue in a loop.
Of course if it were assumed they never need anything but the higher level methods: map, reduce, some, every etc. - then they would probably use some or every if they wanted to break.
I thought the original statement was break or continue, I was actually focusing on break? As far as I know you don't break in map, you can return null but not break the loop itself.
on edit: although I almost never break in a loop, I use some other construct instead.
on second edit: maybe I should say iteration instead of loop but will let it stand.
out of the edit window, evidently was typing quickly, but would not use every for implementing a break like functionality on an array - some could do it in some instances or maybe find.
I suppose filter could maybe be hacked to do it in some cases dependent on implementation, if the implementation determined that it was impossible to ever return true for any of the items remaining in an array that implementation might effectively break the array processing however every way I can think of doing that would be artificial, horrible, and probably full of side effects.
Regardless of how you choose to interpret my comment, it is the case that both `break` and `continue` are literally never necessary, as those two constructs can be implemented in terms of `map` and `reduce`.
There is no sensible way to `break` the iteration while mapping. That's not what `map` is for. In fact, there's very little reason for anyone to need `break` ever. The use of `break` implies an effectful procedure rather than a pure function, so at this point you're not really doing Functional Programming.
Let me know what it is you're trying to achieve — at a high level, not in terms of specific implementation details — and I can show you how this might be expressed without using anything as low-level as a `for` loop.
I'm not asking help with programming anything, and it seems somewhat insulting that somehow you have managed to turn multiple people's observations that you can't break in a map and so most programmers probably need something more than map or reduce into a request for tutelage - especially when you've spent a lot of time saying the impossible (implementing break in map) was trivial before switching to observing it was not sensible to do - which I take as meaning an admission of the aforementioned impossibility.
so to clarify - I am not asking for your help in solving my programming problems. You made a response to another poster asserting that
>all most programmers need is either `map` or `reduce`
that poster said
>I find it really difficult to believe that there are developers out there that never need to be able to break or continue in a loop.
you said
>I’m not sure where you got “never” from in my comment.
I, in my normal long-winded way gave you the benefit of the doubt that you meant something else but advised the previous poster had assumed you meant more than 50% of programmers will not ever need other array methods than map or reduce and hence was confused because you cannot break or continue with those (with map and reduce together you can implement something that has the same end effect as continue but it is not 100% the same of course)
but for some reason you just wanted to keep saying you were right and others were wrong even though the clear meaning of everyone's text was - you can't break in a map.
And you said:
>Regardless of how you choose to interpret my comment, it is the case that both `break` and `continue` are literally never necessary, as those two constructs can be implemented in terms of `map` and `reduce`.
frankly exasperated I said
>ok, how do you implement a break of the iteration in map in JavaScript? I believe it cannot be done, you believe it can? Teach me.
now admittedly here you may think I am confused and need help, but really I am saying you are wrong in a nice way when you say break can be implemented in map. But then you say:
>There is no sensible way to `break` the iteration while mapping. That's not what `map` is for.
right, what everyone's been telling you from the beginning.
>Let me know what it is you're trying to achieve — at a high level, not in terms of specific implementation details — and I can show you how this might be expressed
Thank you for your kindness but it should be obvious from the whole conversation nobody is asking you for programming help here, they are just saying your initial statement of most programmers only need map and reduce was overly bold.
As to why someone might want to break a loop, generally you do that when you have a long array - say 10000 items (don't bother telling me that 10000 is not a long array, I know, but a long array is often used as meaning an array requiring a lot of processing and that is partially determined by what you need to do with that array, if that is not good enough for you arbitrarily add 0s to the array length until you feel you have a long array) and have a number of conditions that can cause you to only have to process a number of them which probably in that case you would use find (because often in such a case you are processing an array to find one item in it) or if for some reason you needed to drop down to a lower level I would prefer to use while instead of a for loop because semantically I think while indicates to anyone reading the code without going into the loop - hey Bryan doesn't expect to have to look at every element of this array!
BUT ALL THAT DOESN'T MATTER - because the subject matter of this long discussion was you saying most programmers only need map and reduce and someone asked why do you think most programmers will NEVER need to break. And you wanted to know why they assumed that you thought most programmers would NEVER need to break because evidently you felt you never said an...
ok, maybe I'm too aggressive lately, but the only reason to do a break is to stop iterating over the array. You can easily achieve the output of using break using filter, but you cannot achieve the behavior because filter goes over every item in your array.
Probably the most common reason you want to break is that you found what you were looking for so find is what you would use.
so to make an example that would not be reasonable for find:
you have a long array of objects, you need to display 5 of the 'interesting' objects out of this array. What constitutes 'interesting' is determined by some relatively complicated logic on each item in the array. Since you do not know how many items in this array are interesting you want to process all items until you have 5 items. When you have 5 items you want to stop processing items therefore you break, if your first 5 items have the property of being 'interesting' you just saved a lot of work in your application and things feel zippy so you want to do that (because your application maybe needs the help at this point)
It is obviously, as all examples when one does not need something at the very moment, somewhat artificial - but I would say it is not unthinkable.
However, as I made clear earlier, I am not arguing for the necessity of break - if I wanted to do what I just described I would probably use while unless the syntax became unwieldy - I dislike break.
The only reason I got into this is because someone said they didn't believe most programmers wouldn't ever need break (which can be used to stop iteration of an array) and the guy who said all most programmers needed was map and reduce (both of which iterate over every item of an array) then said they never said most programmers wouldn't need break.
I just assumed it was a new method coming out soon! Although when I was thinking of how I would make a method I just thought an until method that iterated all elements of an array outputting them until it got the return from the callback that was passed as the iteration stopper - default value null.
I don't see where you used map? You made a recursive function? the argument was clearly stated - person says all most programmers need is map and reduce on arrays. I agree that by recursing you also have found another way not to use break but you have evidently had to NOT use map and reduce to do it?
on edit: also how will that recursion perform on the large array we've supposed as being used if it turns out that we don't get our interesting items in early parts of array?
> You can easily achieve the output of using break using filter, but you cannot achieve the behavior because filter goes over every item in your array.
Because JS’s map and filter provide the index and array as arguments, you can, in fact, achieve the behavior of break. If you need the original array after the iteration, you need to apply the map or filter to a copy, though, because the way to achieve the behavior of break involves modifying the array by deleting all the items after the current one, which stops the iteration.
I guess it's a failure of imagination on my part not to have realized that I could modify the array length in that way, or really the failure of imagination was not seeing any good reason for sending the original array in as a parameter to map - when I saw that in documentation I thought what a weird idea!
a better idea is to first use filter() to obtain a collection you can then map() over. So then you dont need to continue or break, the offending elements of the collection were already filtered out.
that's like saying high level languages cannot be unequivocally better then writing assembly, not doing your own memory management is bad, etc...
Functional programming is a higher level abstraction. If the underlying implementation is not well implemented/optimized then you could have bad performance while the opposite is also true.
(if you don’t need the array later, you can just use “array” instead of [...array]; the latter is just used because the break effect is achieved by mutating the array to delete all the elements subsequent to the one you are working, terminating the iteration, and copying the array initially means you don’t stomp the original.)
huh, that's actually interesting and horrifying I guess, I knew the third argument of map was the original array but I had never seen a usage of it - I guess mutating an array while working on it goes against instinct for just about everyone.
so I guess I was wrong, you can get the same functionality of break in map, albeit in a way that, right at this moment, makes me uneasy.
I basically rely on map 80% of the time for iteration. I do have to spend some mental cycles on deciding where to go if I needed to break or skip elements. That was until I discovered the beautiful .flatMap() trick of returning an empty array to skip or exclude an index item.
They don't want to keep the unmapped value in the resulting collection at all. flatMap allows for removal of an element in one traverse, unlike filter+map with eager behaviour.
Sure, there's many ways to skin a cat. Given the option, I wouldn't choose either of these, and instead use something like filterMap[1] which I think conveys intent better than a fold or flatMap.
Btw, I'm assuming that the original map questioner wasn't solely using flatMap for side-effectful iteration, which reading again, I'm a bit suspicious about.
I see people use JavaScript Array.map a lot when they aren't doing anything with the returned array, which I find kind of irritating.
So if you're using map sensibly, no there's nothing wrong. But I don't like the dogma I read a lot which is "use map always" because it leads to inexperience developers using it in the wrong circumstances.
Of course, that wasn't meant seriously. That's why i like `for_each` (or `iter` or whatever it's called) as 'third' function too, because it makes it clear you're doing side-effects.
Classic for-loops create state and ask you to manually manage control-flow using an arbitrary conditional expression. They open the door for a whole lot of subtle mistakes that for..of can protect you from in the cases where it applies (which in my experience, are 95% of the cases where you’d use a for loop at all)
While I agree on the side-effect side of things, you really shouldn't use a classic for loop when forEach suffices.
forEach emphasizes you are operating on each element of the loop in a semi-independent fashion and you explicitly don't have access to any kind of intermediary index/iterator variable.
When you "drop down" to a classic for loop you're telling the reader that you need access to the index/iterator for some reason (and there are plenty of valid reasons for that).
If you only use classic for loops then the reader has to be constantly vigilant you're not doing any index/iterator shenanigans. It's just higher "cognitive load"
The index and the collection iterated over is passed as the 2nd/3rd parameters in JavaScript, so technically you still have access to them, though in the majority of cases people will omit these parameters
> The index and the collection iterated over is passed as the 2nd/3rd parameters in JavaScript,
Yeah, the “map and friends pass extra parameters that are usually ignored” plus the “lots of JS functions optionally take extra parameters that are usually unused” makes it so easy to think of a function as a one-arg function when its really one plus multiple optionals, use it bare in a map, and get stung by it.
>forEach emphasizes you are operating on each element of the loop in a semi-independent fashion and you explicitly don't have access to any kind of intermediary index/iterator variable.
I disagree with this point, specifically for JavaScript the for...of syntax is more pleasant to read and also does not infer that any index access is gonna be done. And as a plus, it is easier to be used with async/await.
This problem isn’t about map(). parseInt takes a second argument - a radix - and map passes a second argument to to the callback: an index
The normal solution is to write an inline function; “shortcutting” it by just passing a named function directly can be nice when it’s appropriate, but it often isn’t what you want to do
Coming from outside the JS world, the problem is exactly about surprising behaviour of map - the function that's probably the oldest and best estabilished element of FP vocabulary. The extra index argument is not present in functions called "map" in other languages.
Also, the silently ignored argument count mismatch errors heavily contribute to the problem. You may have used other functions with map() and never knew it's always passing an extra argument in, because JS silently ignores this error.
Sorry if it’s unfamiliar, but the index argument is critical to many real use-cases.
As far as ignoring errors silently, sure, but I’m not even sure what the error would be in this case. Index is a number and radix is a number; even typescript won’t complain about this because the types line up.
Is it critical in some way that's absent from all the other langugaes that have a map()? There are standard, composable, explicit patterns in Python, Clojure, and I presume other map()-equipped anguages to get you the indices along with the items. (enumerate in Python and map-indexed in Clojure)
The error I was talking about was the argument count mismatch erorr, that would have taught you on all the other times you used map() without knowing about the extra index argument.
> Is it critical in some way that’s absent from all the other langugaes that have a map()?
Yes, but only because of other non-ergonomic design decisions of the API its part of (particuarly, that it is an eager API attached to the Array class returning Arrays; if it were a lazy API of generalized iterable protocol that returned and could be used on generators, then you could just also have an “enumerate” and “with_collection” companion methods, but as it is, even if you made standalone functions for those purposes, for the output to be used with .map() you need them to return Arrays, which means you are accumulating bulky intermediate results.)
Yes, eagerness would use additional space in the case of a Python enumerate() style solution. This would be acceptable in my view and aligned with the general JS design. But an alternative is the map-indexed style solution (aka the current Array.map with a different name).
> Sorry if it’s unfamiliar, but the index argument is critical to many real use-cases.
Only because .map() is an Array method, not a method of a generic iteration protocol applicable to other iterables (including generators.)
In Python, while map is a built-in function instead of a method, and you’ll probably want a comprehension instead, if you want the index for a map (or in a comprehension), you just wrap call enumerate() and go from an iterable of items to an iterable of pairs of (item, index), so:
map(f, enumerate(collection))
In Ruby, if you want the index in a map, you just do:
I ran across this anecdote about how people don't know (or acknowledge?) the existence of the index argument of Array.map. This is a link to the manuscript of the well regarded book on JS FP techniques "Javascript Allonge". The book was linked elsewhere in these comments. The index arg is not mentioned!
Its about .map(), and the broader design of the API around it. Yes, map passes extra args and parseInt takes optional args, and it wouldn’t be a problem if both of those weren’t true. It would be better if .map() was one-arg and had friends .map_with_index(), .map_with_collection(), map_with_index_and_collection().
(It would be better if .map() wasn’t an Array-specific method but part of a general collection protocol that returned [and was applicable to] generators instead of returning Arrays, and then you also had companion methods to like .enumerate(), but that’s…well, farther afield.)
> The normal solution is to write an inline function
Yes, that’s the solution, but that doesn’t say that the design of .map() isn’t a critical component of there being something to solve in the first place.
I think it is important that you brought awareness to this, this is a really good point and I agree 100%. JavaScript does have hygiene issues with its API design.
// Consider:
["1", "2", "3"].map(parseInt);
// While one could expect [1, 2, 3]
// The actual result is [1, NaN, NaN]
// parseInt is often used with one argument, but takes two. The second being the radix
// To the callback function, Array.prototype.map passes 3 arguments:
// the element, the index, the array
// The third argument is ignored by parseInt, but not the second one,
// hence the possible confusion. See the blog post for more details
function returnInt(element){
return parseInt(element,10);
}
["1", "2", "3"].map(returnInt);
// Actual result is an array of numbers (as expected)
Using forEach/map/etc. can often result in worst performance, because some JavaScript JITs (SpiderMonkey in particular has trouble with this) aren't able to monomorphize calls to them effectively and end up falling back to the interpreter due to the megamorphic nature of these higher-order functions.
Even for engines that do, you do end up paying a cost for the JIT to warm up to monomorphize these functions.
As always, benchmark and profile! However, from first principles, this is one of the degenerate cases of certain JITs that often catches people off guard, but has a straightforward transformation to avoid the pessimizing behavior – especially if your loops end up looking like they're particularly hot.
It might be a thing of personal taste, but programming became significantly easier for me when I discovered map and reduce, including editing what I’d previously written.
Same, but I can't shake the feeling that I am missing something, as some people are really insistent that FP is the _only_ way forward.
I've had cursory glances at the ideas of FP and find it very difficult to understand (probably due to many years of OOP style imperative programming).
Now I wonder whether I should have just started with FP and ignored everything else.
Is the idea behind FP that it's just much less likely for your code to be incorrect? Because to me it looks more like the sort of thing code-golfers love, because it allows for cramming even more implied functionality into ever-tighter, ever-harder-to-read spaces. Which, I suppose, is fine if everyone is on the same level, but I'm not sure how I'd apply something like this in a larger organisation / code base.
Again, I am speaking from ignorance and openly displaying this ignorance intentionally so that I may be proven wrong. I am inviting this, as I'd like to know more about FP.
For one, you’re dealing with less state. That’s worth a lot already.
More interestingly, programming in a functional style encapsulates a lot of complexity in concepts. Once you grasp a concept, it all falls in to place. But getting there is hard.
Java-style OOP gives you a loose bag of conceptually simple pieces, but that quickly devolves in to a horde of rabid infants with jetpacks. Then you need to memorise several dozen patterns to contain the chaos.
What you're describing about Java style OOP has made that stuff absolutely impenetrable to me. I have no patience for studying those patterns, and even less for writing all of the boilerplate code to implement them.
I've been loving all of the push towards more functional style programming lately. It makes code so much more intuitive for me.
I think I can relate. I'm a pragmatist (I think anyway), and it was tough to "get" the point of FP just from reading about it, and sometimes I think the proponents can unwittingly make it sound more complicated and wondrous and less accessible since from an outside perspective the terminology seems like a pile of academic buzzwords (it's not, just how anything specialised can look from the outside).
It took a while of actually using some FP patterns for a couple of things to sink in. First, that I'd already been using these patterns in an ad-hoc way, I just didn't have the terminology for it, and second was realising just how repetitive these patterns were in my work. A lot of the code I wrote would either iterate over collections to do stuff to the contents / build a modified collection (map), or iterate a collection and determine something about the contents, like does the user have product x in their cart, or something (reduce).
These days I find code more readable when it uses mapping / reduction and other FP patterns, it's not so much that there's less boilerplate (maybe a couple of lines, nothing drastic), but the grammar itself expresses meaning, I don't have to trace through an entire nested loop section to understand the developer's intentions from the outset.
For instance if I see map, I understand they must be producing a collection based on the input. If they're using reduce, they're figuring out some property of the entire collection.
Also (this is a big plus), the developer would have to go out of their way to modify the original collection, which is always a fun source of unexpected functionality when reading through traditional loops; map and reduce avoid this by design as modified collections are produced as output and don't surreptitiously modify the input. Basically by design you end up with fewer mega-loops that cram in updates, computation, and modify the source data in sneaky ways.
Without reading the contents of the mapping and reduction functions, the grammar of this is very clear to me: "newCollection = (collection.reduce({criteria})) ? collection.map({abc}) : collection.map({def})" - I can see that I'm checking the collection for some criteria, then mapping the collection based on one of two criteria. The grammar itself tells me exactly what's going on before I even have to read the contents of the mapping and reducer functions. Sure that can also be accomplished with a couple of loops, but then I'd have to read through the loops to understand exactly what the broader intention is.
Anyway as I said I consider myself a pragmatist rather than an evangelist for any particular methodology. I don't consider myself a FP programmer but I've found some of the concepts useful tools to have around. Like anyone on a wage, time is money and I use whatever gets the result the client wants in the best way from time, budget, and maintainability perspective. But I hope at least the concept seems a bit less pointless / like code golf with the above context?
'Implied functionality' sounds like another way to say higher-level concepts - something that all languages provide. Just like languages make design decisions on how they manage memory and ownership or how they treat typing, they can also decide to provide more expressive kinds of operations (or, if the language is flexible enough, you can provide them yourself).
That's really all there is to it. Functional programming just means some more expressive, higher-level operations at your disposal. Rather than repeating code to initialize, transform, and iterate over state (tasks you spend relatively more time doing homework on in iterative code), you can spend more energy on your problem domain instead.
FP is best paired with a type system. If you're not using Typescript, then functional Javascript can be hard to follow. The reason is that without types, usage of functions like `map` require you to grok the implementation details of the callback, so that you can understand what the resulting object looks like, and then you have to hold that type in your head going forward.
When you're using a typed language, it's just that: an implementation detail. All that matters is the type you get out, so you can start ignoring all those details and just focus on your program at a higher level, like "first we start with an array of id's, then we map that into an array of employees, then we reduce it into a sum". Much harder to make a mistake compared to "okay, I'm going to maintain a running counter. Then, within a loop, take an id and find the employee, then add the employee's salary to the running sum. After all this I should have a total salary value".
I mostly end up using map, filter, for in and for of. Occasionally you still need a good “old fashioned” for loop but in my mind it’s more clear to use that then forEach.
While loops have their own special powers for recursion so those are still appropriate and not something I would label as “old school” but hell, maybe that makes me old school!
I can’t find a ton of good personal examples where I’ve needed a range function.
This article feels more like the author was “exploring” techniques rather than proposing a standard practice
JavaScript got very tolerable, even good, almost, after ES6. But I’m still reminded on a weekly basis that it doesn’t have a functional way to do something X times. Which is probably why this article spends so much time implementing that.
122 comments
[ 3.0 ms ] story [ 191 ms ] threadThen every for-of loop example does not execute anything because it’s creating an inline arrow function without calling it.
>Off-by-one bugs are more probable. For example, you might write i<22 instead, and then the loop would end at 21, not 22.
Ironically, their implementation of a function called "range" differs from the (likely) best known function by that name (that of Python), causing exactly this problem. (Also, the Pythonic range is probably a better fit for a language with 0-based indexing.)
One of the best tools I found to really dive into functional programming in JavaScript was Lodash's "fp" variant. It will swap the order of the arguments to its functions and curry them all by default. So you're building a function to "add one"
Can then be applied against an array of numbers: This is fairly basic though. Where it really shines is when you start to use the `flow` function will can be used to compose multiple functions together (and makes the order of execution follow from left to right) I find this is one of the small redeeming features of JavaScript!My only excuse was that I was in a hurry due to Minecraft time with my son.
Theres also Ramda (https://ramdajs.com/), which I slightly prefer because the documentation is significantly better.
That said, if I was on a project with a bunch of developers that really grok'd FP concepts then Rambda would be my first pick, absolutely.
A note from personal experience: never mix fp and normal variants of date-fns and lodash in one project. If you import isAfter from date-fns/fp in one file and date-fns in another (or your editor imports it for your) you‘re gonna have a BAD time.
If you're a JavaScript developer and you want to learn Functional Programming, then learn Elm. It's a small, easy language, and learning it will make you a better JavaScript programmer.
Generators are very useful in more complex scenarios, but when used in simpler environments they'll just add confusion - in fact the article is a good example of adding confusion with a Generator.
Personally, when dealing with arrays I favour using .map, .reduce, .filter and .forEach.
IMO it's the worst of both worlds and there's virtually never a good reason to use it. If you're going to write a side-effect-y loop, a classic for-loop (or more often, a for..of loop) helps to emphasize that the code is side-effect-y.
Of course if it were assumed they never need anything but the higher level methods: map, reduce, some, every etc. - then they would probably use some or every if they wanted to break.
No it doesn’t.
If I only need to do X
And X cannot do Y
Then I do not need to do Y
-----------------------------
If I need to do Y
And X cannot do Y
Then I need to do something other than X
-------------------------
Give your answer of no it doesn't please be so kind as to outline the logical reasoning that you are envisioning?
on edit: added line between logical statements.
on edit: although I almost never break in a loop, I use some other construct instead.
on second edit: maybe I should say iteration instead of loop but will let it stand.
I suppose filter could maybe be hacked to do it in some cases dependent on implementation, if the implementation determined that it was impossible to ever return true for any of the items remaining in an array that implementation might effectively break the array processing however every way I can think of doing that would be artificial, horrible, and probably full of side effects.
Let me know what it is you're trying to achieve — at a high level, not in terms of specific implementation details — and I can show you how this might be expressed without using anything as low-level as a `for` loop.
so to clarify - I am not asking for your help in solving my programming problems. You made a response to another poster asserting that >all most programmers need is either `map` or `reduce`
that poster said >I find it really difficult to believe that there are developers out there that never need to be able to break or continue in a loop.
you said >I’m not sure where you got “never” from in my comment.
I, in my normal long-winded way gave you the benefit of the doubt that you meant something else but advised the previous poster had assumed you meant more than 50% of programmers will not ever need other array methods than map or reduce and hence was confused because you cannot break or continue with those (with map and reduce together you can implement something that has the same end effect as continue but it is not 100% the same of course)
but for some reason you just wanted to keep saying you were right and others were wrong even though the clear meaning of everyone's text was - you can't break in a map.
And you said:
>Regardless of how you choose to interpret my comment, it is the case that both `break` and `continue` are literally never necessary, as those two constructs can be implemented in terms of `map` and `reduce`.
frankly exasperated I said
>ok, how do you implement a break of the iteration in map in JavaScript? I believe it cannot be done, you believe it can? Teach me.
now admittedly here you may think I am confused and need help, but really I am saying you are wrong in a nice way when you say break can be implemented in map. But then you say:
>There is no sensible way to `break` the iteration while mapping. That's not what `map` is for.
right, what everyone's been telling you from the beginning.
>Let me know what it is you're trying to achieve — at a high level, not in terms of specific implementation details — and I can show you how this might be expressed
Thank you for your kindness but it should be obvious from the whole conversation nobody is asking you for programming help here, they are just saying your initial statement of most programmers only need map and reduce was overly bold.
As to why someone might want to break a loop, generally you do that when you have a long array - say 10000 items (don't bother telling me that 10000 is not a long array, I know, but a long array is often used as meaning an array requiring a lot of processing and that is partially determined by what you need to do with that array, if that is not good enough for you arbitrarily add 0s to the array length until you feel you have a long array) and have a number of conditions that can cause you to only have to process a number of them which probably in that case you would use find (because often in such a case you are processing an array to find one item in it) or if for some reason you needed to drop down to a lower level I would prefer to use while instead of a for loop because semantically I think while indicates to anyone reading the code without going into the loop - hey Bryan doesn't expect to have to look at every element of this array!
BUT ALL THAT DOESN'T MATTER - because the subject matter of this long discussion was you saying most programmers only need map and reduce and someone asked why do you think most programmers will NEVER need to break. And you wanted to know why they assumed that you thought most programmers would NEVER need to break because evidently you felt you never said an...
i don't think you really listened to the other guy.
i think he's saying that there's no need for break/continue with FP
because you simply approach the problem from a different angle
it's a different paradigm
that's probably why he asked for an example (not to belittle you)
i might do a filter before a map to recreate a lot of the functionality of a continue or break
and when I wrote/write OOP I might avoid break/continue because these things can be leaky
Probably the most common reason you want to break is that you found what you were looking for so find is what you would use.
so to make an example that would not be reasonable for find:
you have a long array of objects, you need to display 5 of the 'interesting' objects out of this array. What constitutes 'interesting' is determined by some relatively complicated logic on each item in the array. Since you do not know how many items in this array are interesting you want to process all items until you have 5 items. When you have 5 items you want to stop processing items therefore you break, if your first 5 items have the property of being 'interesting' you just saved a lot of work in your application and things feel zippy so you want to do that (because your application maybe needs the help at this point)
It is obviously, as all examples when one does not need something at the very moment, somewhat artificial - but I would say it is not unthinkable.
However, as I made clear earlier, I am not arguing for the necessity of break - if I wanted to do what I just described I would probably use while unless the syntax became unwieldy - I dislike break.
The only reason I got into this is because someone said they didn't believe most programmers wouldn't ever need break (which can be used to stop iteration of an array) and the guy who said all most programmers needed was map and reduce (both of which iterate over every item of an array) then said they never said most programmers wouldn't need break.
List.where(…).map(…).take_until(…)
JS doesn't, though.
> then I think take_until would be effectively equivalent to break
JS also doesn't have take_until.
I just assumed it was a new method coming out soon! Although when I was thinking of how I would make a method I just thought an until method that iterated all elements of an array outputting them until it got the return from the callback that was passed as the iteration stopper - default value null.
on edit: also how will that recursion perform on the large array we've supposed as being used if it turns out that we don't get our interesting items in early parts of array?
Because JS’s map and filter provide the index and array as arguments, you can, in fact, achieve the behavior of break. If you need the original array after the iteration, you need to apply the map or filter to a copy, though, because the way to achieve the behavior of break involves modifying the array by deleting all the items after the current one, which stops the iteration.
I guess it's a failure of imagination on my part not to have realized that I could modify the array length in that way, or really the failure of imagination was not seeing any good reason for sending the original array in as a parameter to map - when I saw that in documentation I thought what a weird idea!
FP is a higher level paradigm so you might have to trade off with performance too
Then it can hardly he said be to unequivocally be better, can it.
Functional programming is a higher level abstraction. If the underlying implementation is not well implemented/optimized then you could have bad performance while the opposite is also true.
Yes, it is like saying that. At least, assuming by "cannot be" you mean "is not".
>not doing your own memory management is bad
No, it's not like saying that. Note how differently those claims are phrased.
If you want something that behaves like:
you do this: (if you don’t need the array later, you can just use “array” instead of [...array]; the latter is just used because the break effect is achieved by mutating the array to delete all the elements subsequent to the one you are working, terminating the iteration, and copying the array initially means you don’t stomp the original.)so I guess I was wrong, you can get the same functionality of break in map, albeit in a way that, right at this moment, makes me uneasy.
Is it bad to use map?
> Is it bad to use map?
No.
flatMap takes care of a lot of that
For posterity (and forgive me if this JavaScript syntax is inaccurate; I don't write it so much these days)…
Btw, I'm assuming that the original map questioner wasn't solely using flatMap for side-effectful iteration, which reading again, I'm a bit suspicious about.
1: https://gcanti.github.io/fp-ts/modules/Array.ts.html#filterm...
So if you're using map sensibly, no there's nothing wrong. But I don't like the dogma I read a lot which is "use map always" because it leads to inexperience developers using it in the wrong circumstances.
You actually don't need `map`, you can easily implement that using `reduce` too ;)
There is utility in communicating whether you're working with a catamorphism vs a homomorphism. Code is written for humans, after all.
https://twitter.com/jaffathecake/status/1213077702300852224
forEach emphasizes you are operating on each element of the loop in a semi-independent fashion and you explicitly don't have access to any kind of intermediary index/iterator variable.
When you "drop down" to a classic for loop you're telling the reader that you need access to the index/iterator for some reason (and there are plenty of valid reasons for that).
If you only use classic for loops then the reader has to be constantly vigilant you're not doing any index/iterator shenanigans. It's just higher "cognitive load"
Yeah, the “map and friends pass extra parameters that are usually ignored” plus the “lots of JS functions optionally take extra parameters that are usually unused” makes it so easy to think of a function as a one-arg function when its really one plus multiple optionals, use it bare in a map, and get stung by it.
I disagree with this point, specifically for JavaScript the for...of syntax is more pleasant to read and also does not infer that any index access is gonna be done. And as a plus, it is easier to be used with async/await.
ForEach does get you both.
I don't remember using for loop for at least 3 years previously as a frontend engineer :)
It is longer than:
array.forEach((value, index) => {})
… but forEach has too many limitations (no exits, no await, …)
I'll give you await (gotta use awkward reduce there) but if you want exit, you're better off with something like `find`
Another advantage is that array functions better convey what are you trying to do with this particular operation.
I strongly disagree.
The normal solution is to write an inline function; “shortcutting” it by just passing a named function directly can be nice when it’s appropriate, but it often isn’t what you want to do
Also, the silently ignored argument count mismatch errors heavily contribute to the problem. You may have used other functions with map() and never knew it's always passing an extra argument in, because JS silently ignores this error.
As far as ignoring errors silently, sure, but I’m not even sure what the error would be in this case. Index is a number and radix is a number; even typescript won’t complain about this because the types line up.
The error I was talking about was the argument count mismatch erorr, that would have taught you on all the other times you used map() without knowing about the extra index argument.
Yes, but only because of other non-ergonomic design decisions of the API its part of (particuarly, that it is an eager API attached to the Array class returning Arrays; if it were a lazy API of generalized iterable protocol that returned and could be used on generators, then you could just also have an “enumerate” and “with_collection” companion methods, but as it is, even if you made standalone functions for those purposes, for the output to be used with .map() you need them to return Arrays, which means you are accumulating bulky intermediate results.)
Only because .map() is an Array method, not a method of a generic iteration protocol applicable to other iterables (including generators.)
In Python, while map is a built-in function instead of a method, and you’ll probably want a comprehension instead, if you want the index for a map (or in a comprehension), you just wrap call enumerate() and go from an iterable of items to an iterable of pairs of (item, index), so:
In Ruby, if you want the index in a map, you just do:https://github.com/raganwald/javascript-allonge/blob/db7c435...
Yes, it is.
Its about .map(), and the broader design of the API around it. Yes, map passes extra args and parseInt takes optional args, and it wouldn’t be a problem if both of those weren’t true. It would be better if .map() was one-arg and had friends .map_with_index(), .map_with_collection(), map_with_index_and_collection().
(It would be better if .map() wasn’t an Array-specific method but part of a general collection protocol that returned [and was applicable to] generators instead of returning Arrays, and then you also had companion methods to like .enumerate(), but that’s…well, farther afield.)
> The normal solution is to write an inline function
Yes, that’s the solution, but that doesn’t say that the design of .map() isn’t a critical component of there being something to solve in the first place.
// Consider: ["1", "2", "3"].map(parseInt); // While one could expect [1, 2, 3] // The actual result is [1, NaN, NaN]
// parseInt is often used with one argument, but takes two. The second being the radix // To the callback function, Array.prototype.map passes 3 arguments: // the element, the index, the array // The third argument is ignored by parseInt, but not the second one, // hence the possible confusion. See the blog post for more details
function returnInt(element){ return parseInt(element,10); }
["1", "2", "3"].map(returnInt); // Actual result is an array of numbers (as expected)
Also I think `forEach` is great for cases such as `arr.forEach(f)` where `arr: Array<T>, f: T => void`.
Even for engines that do, you do end up paying a cost for the JIT to warm up to monomorphize these functions.
To just say “there is cost” is not so useful; there is cost to everything.
Not a fan of "map" and its ilk, make code harder to understand and modify over time.
Is the idea behind FP that it's just much less likely for your code to be incorrect? Because to me it looks more like the sort of thing code-golfers love, because it allows for cramming even more implied functionality into ever-tighter, ever-harder-to-read spaces. Which, I suppose, is fine if everyone is on the same level, but I'm not sure how I'd apply something like this in a larger organisation / code base.
Again, I am speaking from ignorance and openly displaying this ignorance intentionally so that I may be proven wrong. I am inviting this, as I'd like to know more about FP.
More interestingly, programming in a functional style encapsulates a lot of complexity in concepts. Once you grasp a concept, it all falls in to place. But getting there is hard.
Java-style OOP gives you a loose bag of conceptually simple pieces, but that quickly devolves in to a horde of rabid infants with jetpacks. Then you need to memorise several dozen patterns to contain the chaos.
Intrinsic vs extrinsic complexity.
I've been loving all of the push towards more functional style programming lately. It makes code so much more intuitive for me.
It took a while of actually using some FP patterns for a couple of things to sink in. First, that I'd already been using these patterns in an ad-hoc way, I just didn't have the terminology for it, and second was realising just how repetitive these patterns were in my work. A lot of the code I wrote would either iterate over collections to do stuff to the contents / build a modified collection (map), or iterate a collection and determine something about the contents, like does the user have product x in their cart, or something (reduce).
These days I find code more readable when it uses mapping / reduction and other FP patterns, it's not so much that there's less boilerplate (maybe a couple of lines, nothing drastic), but the grammar itself expresses meaning, I don't have to trace through an entire nested loop section to understand the developer's intentions from the outset.
For instance if I see map, I understand they must be producing a collection based on the input. If they're using reduce, they're figuring out some property of the entire collection.
Also (this is a big plus), the developer would have to go out of their way to modify the original collection, which is always a fun source of unexpected functionality when reading through traditional loops; map and reduce avoid this by design as modified collections are produced as output and don't surreptitiously modify the input. Basically by design you end up with fewer mega-loops that cram in updates, computation, and modify the source data in sneaky ways.
Without reading the contents of the mapping and reduction functions, the grammar of this is very clear to me: "newCollection = (collection.reduce({criteria})) ? collection.map({abc}) : collection.map({def})" - I can see that I'm checking the collection for some criteria, then mapping the collection based on one of two criteria. The grammar itself tells me exactly what's going on before I even have to read the contents of the mapping and reducer functions. Sure that can also be accomplished with a couple of loops, but then I'd have to read through the loops to understand exactly what the broader intention is.
Anyway as I said I consider myself a pragmatist rather than an evangelist for any particular methodology. I don't consider myself a FP programmer but I've found some of the concepts useful tools to have around. Like anyone on a wage, time is money and I use whatever gets the result the client wants in the best way from time, budget, and maintainability perspective. But I hope at least the concept seems a bit less pointless / like code golf with the above context?
That's really all there is to it. Functional programming just means some more expressive, higher-level operations at your disposal. Rather than repeating code to initialize, transform, and iterate over state (tasks you spend relatively more time doing homework on in iterative code), you can spend more energy on your problem domain instead.
When you're using a typed language, it's just that: an implementation detail. All that matters is the type you get out, so you can start ignoring all those details and just focus on your program at a higher level, like "first we start with an array of id's, then we map that into an array of employees, then we reduce it into a sum". Much harder to make a mistake compared to "okay, I'm going to maintain a running counter. Then, within a loop, take an id and find the employee, then add the employee's salary to the running sum. After all this I should have a total salary value".
While loops have their own special powers for recursion so those are still appropriate and not something I would label as “old school” but hell, maybe that makes me old school!
I can’t find a ton of good personal examples where I’ve needed a range function.
This article feels more like the author was “exploring” techniques rather than proposing a standard practice