The map Function
Understand the higher-order map function and see its applications on lists, trees, options, abstract domains, and contexts.
The map
function is one of the most popular abstractions in the functional programming paradigm. It is best known for mapping the elements of a list to new elements. Due to its usefulness, map
has found its way into most mainstream programming languages, such as JavaScript, Java, and Python.
The map
function on lists
One way of looking at map
on lists is that it represents a general method of computation on lists.
First, let’s write a square_list
function that squares all elements in a list to see what that pattern is.
(** [print_int_list] prints an integer list [l] to the console. *)let print_int_list l = print_string ("[" ^ (String.concat "; " (List.map string_of_int l)) ^ "]")(** [square_list] returns a list whose elements are the square of the elements of an input list [l]. *)let rec square_list l = match l with| [] -> []| hd :: tl -> (hd * hd) :: square_list tl(** Printing the result of 'square_list [1; 2; 3]' to the console. *)let _ = print_int_list (square_list [1; 2; 3])
Let’s write another function called cube_list
that turns the elements of a list into their cube.
(** [print_int_list] prints an integer list [l] to the console. *)let print_int_list l = print_string ("[" ^ (String.concat "; " (List.map string_of_int l)) ^ "]")(** [cube_list] returns a list whose elements are the cube the of elements of an input list [l]. *)let rec cube_list l = match l with| [] -> []| hd :: tl -> (hd * hd * hd) :: cube_list tl(** Printing the result of 'cube_list [1; 2; 3]' to the console. *)let _ = print_int_list (cube_list [1; 2; 3])
The implementations of square_list
and cube_list
are strikingly similar. The differences between these functions lie in their last lines, which compute the head of the output list from the head of the input list, hd * hd
vs. hd * hd * hd
.
Other than that, the shape of the functions looks identical.
As we have seen multiple times in the course, this kind of common shape is almost always an opportunity to climb up the abstraction hierarchy. We can factor out the common pattern into a higher-order map
function that takes an f
function and a list as input. It then applies f
uniformly to all list elements and produces a new list containing the resulting elements. The two functions, square_list
and cube_list
, become particular cases of map
.
(** [print_int_list] prints an integer list [l] to the console. *)let print_int_list l = print_string ("[" ^ (String.concat "; " (List.map string_of_int l)) ^ "]")(** [map f l] applies a function [f] to all elements in a list [l]. *)let rec map f l = match l with| [] -> []| hd :: tl -> (f hd) :: map f tl;;(** [square_list] is a function that squares elements of a list. *)let square_list = map (fun x -> x * x)(** [cube_list] is a function that turns elements of a list into their cube. *)let cube_list = map (fun x -> x * x * x)(** Printing the result of 'square_list [1; 2; 3]' and 'cube_list [1; 2; 3]' separated by a new line to the console. *)let _ = print_int_list (square_list [1; 2; 3]);print_newline ();print_int_list (cube_list [1; 2; 3]);
The following diagram illustrates this abstraction process:
What makes map
such a powerful abstraction is that it allows us to treat the transformation of a sequence of data as a high-level concept. With map
, we focus on what transformation is rather than what the transformation step looks like.
Another excellent way to understand ...