DEV Community

Kristian Pedersen
Kristian Pedersen

Posted on

#30daysofelm Day 29: Basic map, filter and reduce/foldl

This is day 29 of my 30 day Elm challenge

Yesterday was fun, but I'm really exhausted today. Maybe it's because I'm almost done with the 30 days, and I need a break.

I sure am glad I didn't commit to 100 days. :D

This is the most unmotivated I've felt since starting this challenge, so today I decided to write about map, filter and reduce.

Going from for loops to these functions really improved my JavaScript code and my experience, and I was happy to see them in Elm too.

1. In depth documentation

I'm only going to explain some simple use cases. Afterwards, make yourself some hot beverage, and read these:

JavaScript array methods:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#instance_methods

Elm list functions:
https://package.elm-lang.org/packages/elm/core/latest/List

2. Array.map / List.map

Applies a function to each element in a list.

2.1. JavaScript

[1, 2, 3].map(number => number + 1) // [2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

2.2. Elm

In Elm, the list usually comes last.

List.map (\number -> number + 1) [1, 2, 3] -- [2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

However, there's a nicer way to do this.

Using the pipeline |>, the result to its left gets passed as the last argument to the function that comes after it:

[1, 2, 3] |> List.map (\number -> number + 1) -- [2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

I think this syntax is intuitive, and very nice to look at.

3. Array.map / List.indexedMap

This is just how the standard JavaScript map function works.

Elm has two map functions: List.map with no index, and List.indexedMap.

3.1. JavaScript

const languages = ["JavaScript", "Elm", "Scratch"]

// Using parentheses to get implicit return on its own line
const withIndex = languages.map((language, index) => (
    `${index}: ${language}`
))

console.log(withIndex) 
// ["0: JavaScript", "1: Elm", "2: Scratch"]
Enter fullscreen mode Exit fullscreen mode

3.2. Elm

languages = ["JavaScript", "Elm", "Scratch"]

withIndex = languages
    |> List.indexedMap (\index language -> 
        (index |> Debug.toString) ++ ": " ++ language
    )
Enter fullscreen mode Exit fullscreen mode

As you can see, the order of index and element are the other way around in Elm.

Also, Elm doesn't have very nice string interpolation, and you have to explicitly convert other types to a string.

4. Array.filter / List.filter

Keeps the items that return true.

The name "filter" is kind of ambiguous to me, so think of it as keepIfTrue instead.

4.1. JavaScript

The => number <= part looks weird, so I added a second option that looks nicer to me.

[1, 2, 3, 4, 5].filter(number => number <= 3) // [1, 2, 3]

[1, 2, 3, 4, 5].filter(number => (
    number <= 3
))
Enter fullscreen mode Exit fullscreen mode

4.2. Elm

[1, 2, 3, 4, 5] |> List.filter (\number -> number <= 3)
Enter fullscreen mode Exit fullscreen mode

Note that Elm's filter function doesn't provide an index.

5. Array.reduce / List.foldl

Reduces an array into one single variable.

JavaScript has reduce and reduceRight, while Elm has foldl and foldr. Left and right just specify if we're looping from the beginning or the end of the list.

In the beginning, I just thought of reduce as the way you get a sum, but now I want to use it for everything.

It's not a replacement for more readable standard functions, although that can be very fun. Anyway:

5.1. JavaScript

We have a variable total, which will be updated from inside a loop that's going through an array.

Many people use the term accumulator, which sounds more fancy than total.

Here's an example without reduce:

let array = [1, 2, 3, 4, 5]
let total = 0 // Could also be a string, array, object, etc.

for (const item of array) {
    total = total + item
}

console.log(total) // 15
Enter fullscreen mode Exit fullscreen mode

However, other parts of the code might use the accumulator, so it's safer to let reduce handle it:

const sumOf1to5 = [1, 2, 3, 4, 5].reduce((total, item) => {
    return total + item
}, 0)

console.log(sumOf1to5) // 15
Enter fullscreen mode Exit fullscreen mode

total refers to 0, which is the initial value, and item is each separate value in the array.

Also, the really cool people don't use variable names that make sense:

const sumOf1to5 = [1, 2, 3, 4, 5].reduce((a, b) => a + b) // 15
Enter fullscreen mode Exit fullscreen mode

5.2. Elm

Firstly, Elm has its own sum and product functions, so you don't need to implement these with List.foldl:

List.sum [1, 2, 3, 4, 5] -- 15

List.sum (List.range 1 100) -- 5050

List.product (List.range 1 5) -- 120
Enter fullscreen mode Exit fullscreen mode

List.foldl works the same way if you want:

[1, 2, 3, 4, 5] |> List.foldl (\item total -> total + item) 0
Enter fullscreen mode Exit fullscreen mode

Beyond that, you can do a bunch of crazy stuff, but I'm not in the mood for that now.

6. Conclusion

I hope this helped, or maybe even steered you away from for loops.

map, filter and reduce are a lot of fun, and they really improved my JavaScript code and experience when I learned them.

See you tomorrow!

Oldest comments (0)