Explainstuff.mebeta
All concepts
Functional Programmingbeginner7 min

Transform, Keep, Combine

Three tiny tools that replace almost every loop you'll ever write: change every item, keep the ones you want, or boil the whole list down to one answer.

Almost every program ends up doing the same three things to lists, over and over: changing every item into something else, throwing away the items you don't want, and squeezing a pile of items down to one answer. You could hand-write a loop for each of these every single time. But there's a friendlier way — three little tools with friendly names: Transform, Keep, and Combine.

Here's a nice way to picture it. Think of your list like a tiny database table, and these three tools as the queries you run over it — you select new columns, you keep the rows that match a where clause, and you aggregate everything into a grand total. Same idea, but on any list you've got, right there in your code.

Note

Each of these tools takes a function as a value — you hand it a little rule, and it does the looping for you. That trick has its own name: a higher-order function. You describe what you want done; the tool figures out how to walk the list.

Transform

Transform (you'll also hear it called map or select) takes a function and applies it to every single item, handing you back a brand-new list of the same length. Nothing is added, nothing is dropped — each item just gets made over. Want to double every number? Give Transform the rule "multiply by two" and it does the rest.

In F# the tool is called List.map. You pass it the rule and the list, and out comes the made-over list:

// Double every number
List.map (fun x -> x * 2) [1; 2; 3]
// => [2; 4; 6]

Three numbers go in, three numbers come out — each one transformed by the rule you supplied.

Watch it happen below. Every item in the list flows through the transform and lands as a new item on the other side. Notice that the count never changes — Transform is a one-to-one makeover.

Transform every item
map
1
2
3
× 2
2
4
6
map / List.map: each item passes through the function, giving a new list of the same length.

Keep

Keep (the classic names are filter or where) is the bouncer at the door. You give it a yes-or-no test, and it walks the list letting only the items that pass through. The ones that fail are simply left behind, so unlike Transform, the list can come out smaller than it went in.

In F# it's List.filter. Hand it a test that returns true or false, and it keeps the trues:

// Keep only the even numbers
List.filter (fun x -> x % 2 = 0) [1..6]
// => [2; 4; 6]

Six numbers go in, but only the three that pass the "is it even?" test make it out. This is your where clause: keep the rows that match.

In the animation, every item walks up to the test. The ones that match slip through to the result; the rest are turned away at the door. Same idea as picking out just the rows you care about from a table.

Keep only what passes
keep
1
2
3
4
even?
2
4
filter / List.filter: items that pass the test go through; the rest are dropped.

Combine

Combine (known as fold, reduce, or aggregate) is the one that boils a whole list down into a single value. Think of a running total: you start with a number, then walk the list adding each item to what you've got so far, until one final answer is all that's left.

In F# the tool is List.fold. You give it a combining rule, a starting value, and the list:

// Add everything up, starting from 0
List.fold (+) 0 [1; 2; 3; 4]
// => 10

It grabs the start (0), folds in 1, then 2, then 3, then 4, and the four numbers collapse into the grand total 10. That's your aggregate: one number that summarizes the whole list.

The scene shows the list folding inward, item by item, the running total growing as each one is absorbed — until the entire list has shrunk down to a single result.

Combine into one
fold
1
2
3
4
+
10
fold / reduce: run through the list accumulating a single result — here, the sum.

Better than a loop

Why bother, when a plain loop can do all of this? Because these three tools say what you mean instead of how to do it. "Keep the even ones, then double them, then add them up" reads almost like plain English — no counters to manage, no off-by-one mistakes, no temporary list to remember to clear out. The intent is right there on the surface.

And the real magic is that they snap together. Because each one takes a list and (mostly) gives back a list, you can line them up so the output of one flows straight into the next. Stringing them together this way is called a pipeline, and it's where these little tools really sing:

[1..10]
|> List.filter (fun x -> x % 2 = 0)  // Keep the evens
|> List.map (fun x -> x * x)         // Transform: square them
|> List.fold (+) 0                    // Combine: sum them up
// => 220
Keep, transform, combine — in one pipeline
[2;4] → [4;16] → 20
1
2
3
4
Keep · even?
Transform · square
Combine · sum
20
The three tools snap together: keep the evens, square them, then sum — the whole list flows down to a single answer.
Tip

When you catch yourself writing a loop over a list, pause and ask which of the three you're really doing. Changing every item? That's Transform. Picking some out? That's Keep. Reducing to one answer? That's Combine. Most loops are just one of these three wearing a disguise.

Key takeaways

  • Transform (map / select) runs a function over every item and gives you a new list of the same length.
  • Keep (filter / where) checks each item against a test and lets only the matches through, so the list can shrink.
  • Combine (fold / reduce / aggregate) walks the list and squashes it down into a single value, like a running total.
  • All three take a function as an argument, which is what makes them so flexible — you supply the rule, they handle the looping.
  • They read like a sentence and snap together in a pipeline, which is far clearer than hand-writing the same loops over and over.

Keep going