Understanding array.prototype.map
Watch Part 1. Transform your thinking!
Watch Part 2. Don't get Lost!
Hello and welcome to ElmForReactDevs. Im super excited to kick things off with one of if not the most common misconception I noticed in training and pairing sessions with React engineers coming to Elm Fulltime.
Every React engineer has used Array.prototype.map and they think they know how to use it and what it’s doing under the hood. It’s for Looping over arrays.
But that’s not the whole story. We need to shift our thinking here.
map()
is simply a box
—-
I’ve seen folks confuse Array.prototype.map
as an almost drop in replacement to forEach
and forEach
is sold as an alternative to for loops
. A more declarative approach for the imperative for loop
Array.prototype.forEach() - JavaScript | MDN
You will rely on JavaScript features like [for loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for) and the [array map() function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) to render lists of components.
Quick Start – React
So where does this confusion come from?
It comes from the official react docs and google searches for articles and tutorials like this one.
FreeCodeCamp JavaScript Array.map() Tutorial
Array.prototype.map() is a built-in array method for iterating through the elements inside an array collection in JavaScript. Think of looping as a way to progress from one element to another in a list, while still maintaining the order and position of each element.
Ill say it again.
map() is NOT about iteration or looping
It’s about type safety.
I’ll show you how 1 pattern, using map()
, can be used to safely solve different problems in Ui development. Problems include Rendering collections of data, and handling conditional rendering.
Array.prototype.map Array.prototype.map() - JavaScript | MDN is often overlooked in its power.
From the mdn docs we see phrases like:
The map() method creates a new array
Which is easy to confuse with iteration, and conflating map
solely with Arrays/Lists/Collections.
For javascript and React engineers, their first experience with map is with lists. And we see verbiage like “for each thing in this list of things” it’s easy to confuse.
Array.prototype.map() - JavaScript | MDN And Array.prototype.forEach() - JavaScript | MDN Side by side. They have the same arguments, which is also confusing.
The starter example in the official react docs, Thinking in React 2023, has a code example with the following Thinking in React – React
I see this pattern a lot. Initialize an empty array, loop over another array, pushing items from it into the empty one.
function ProductTable({ products, filterText, inStockOnly }) {
const rows = [];
let lastCategory = null;
products.forEach((product) => {
...
rows.push(
<ProductCategoryRow
category={product.category}
key={product.category} />
);
...
}
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
...
Don’t get me started on the side-effects that are happening here. But we see an example of forEach
being used to transform products
into a list of <ProductCategoryRow>
components that are then rendered in our <tbody>
Then students are presented with map()
on the next page about rendering lists of Rendering Lists – React
map()
is mentioned as one of the ways to render lists of components.
On this page, you’ll use filter() and map() with React to filter and transform your array of data into an array of components.
The keyword here is ”transform” 💩
In these situations, you can store that data in JavaScript objects and arrays and use methods like map() and filter() to render lists of components from them.
From there, newcomers are presented with map()
as just another array method and a way to iterate over the array, rendering components.
Then finally, when newcomers try to pickup Elm for the first time, and checkout examples, like - Beginning Elm, students see List.map
for the first time, and think “iteration” like forEach
and map()
from JS.
List.map applies the given function to each element in the original list and puts the result in a new list. Very similar phrase to mdn docs The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
map()
is not about iteration or looping, like forEach
but rather about transformation. We have a safe way transform our data.
No null
.
No type coercion.
No undefined
.
So how do we safely update our data?
[ "help im in a box" ].map(str => str.concat("!"))
-- outputs:[ 'help im in a box!' ]
I really like the “box metaphor” I learned from Professor Frisby.
map() is like when you have a box such as [“im in a box help”]
It’s safe to handle boxes. So we don’t cut ourselves on null
s or undefined
s.
I want to open up the box and transform its contents. Then it goes right back in the box.
map() is simply a box.
We just have different types of boxes. Or boxes with different names.
Much like our React example, we map over a collection of data. In elm they’re called Lists usually.
In Elm we use functions like List.map
to turn data, like a collection of user records, into Html
so that it can be rendered.
List.map
isn’t a “method” like Array.prototype.map, it’s a standalone function that works on Lists. That’s why it’s called functional programming.
But in Elm there are other types that have a map()
function.
We need ways of transforming the data contained inside them.
Maybe the most common example is the Maybe a
type.
type Maybe a = Just a | Nothing
We either have it or we don’t.
Say we have our box
x: Maybe String
x = Maybe.map (\str -> String.append str "!") (Just "Help im in a box called a Maybe")
-- "Help im in a box called a Maybe!
z: List String
z = List.map (\str -> String.append str "!") ["Im also in a box called a List"]
-- "Help im also in a box called a List!
nopeNada = Maybe.map (\str -> String.append str "!") (Nothing)
nopeEmtpy = List.map (\str -> String.append "!") []
-- Nothing still, the box is empty
-- js
[ "help im in a box" ].map(str => str.concat("!!!"))
[ 'help im in a box!!!' ]
But if the box is empty, we get Nothing
so nothing happens.
The idea is to “open” this box. Or a container, or a context, whatever you want to call it.
Open this box safely with map
then and run a function on its contents a -> b
and then put it in that box.
map : (a -> b) -> Maybe a -> Maybe b
map fromAtoB maybeA =
case maybeA of
Nothing ->
Nothing
Just ourA ->
Just <| fromAtoB ourA
Now we can apply our knowledge of Maybes to rendering them in our Ui. [
Theres some helpers in Html.Extra.
In Html.Extra
theres viewMaybe (a -> Html msg) -> Maybe a -> Html msg
Which is super useful
html-extra/src/Html/Extra.elm at 3.4.0 · elm-community/html-extra · GitHub
user.maybeAge |> Html.viewMaybe viewAge
will handle the Nothing
case for your and render nothing
Compared to Conditional Rendering – React
count && {count}
is hard to read
So here we have 2 problems solved with 1 pattern Conditional rendering with Maybe.map and Rendering a Collection of Data with List.map
Pretty cool!
So the more you can generalize your thinking, and simplify your understanding, the easier your FP journey will be.
And then theres Result.map
and take it and run with it
Generalize and think of them as boxes
https://package.elm-lang.org/packages/elm/core/latest/Task#map
https://package.elm-lang.org/packages/elm/core/latest/Result#map
https://package.elm-lang.org/packages/elm/core/latest/Dict#map
https://package.elm-lang.org/packages/elm/core/latest/String#map
And more
But once you crack that then you have map2
and map3
Which let you combine 2 Maybes or 2 Lists
Run 1 function on 2 boxes with map2
You can have 1 function that expects 2 arguments you can give it 2 boxes, and it’ll unwrap both boxes and run that function with them, and put it box But we can draw it out
Resources : Mostly Adequate book
Create linear data flow with container style types (Box) | egghead.io