Higherorder

Require Export DMFP.Lists.

Higher-Order List Functions

Here is a useful higher-order function, taking a list of Xs and a predicate on X (a function from X to bool) and "filtering" the list, returning a new list containing just those elements for which the predicate returns true.
Fixpoint filter {X:Type} (test: Xbool) (l:list X)
                : (list X) :=
  match l with
  | [][]
  | h :: tif test h then h :: (filter test t)
                        else filter test t
  end.
For example, if we apply filter to the predicate evenb and a list of numbers l, it returns a list containing just the even members of l.
Compute (filter evenb [1;2;3;4]). (* ==> 2;4 *)

Definition length_is_1 {X : Type} (l : list X) : bool :=
  eqb (length l) 1.

Compute (filter length_is_1 [ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]). (* ==>  [3]; [4]; [8]  *)
We can use filter to give a concise version of the countoddmembers function from the Lists chapter.
Definition countoddmembers' (l:list nat) : nat :=
  length (filter oddb l).

Compute (countoddmembers' [1;0;3;1;4;5]). (* ==> 4 *)
Compute (countoddmembers' [0;2;4]). (* ==> 0 *)
Compute (countoddmembers' nil). (* ==> 0 *)

Higher-order Functions and Anonymous Functions: Together Forever

It is arguably a little sad, in the example just above, to be forced to define the function length_is_1 and give it a name just to be able to pass it as an argument to filter, since we will probably never use it again. Moreover, this is not an isolated example: when using higher-order functions, we often want to pass as arguments "one-off" functions that we will never use again; having to give each of these functions a name would be tedious.
Fortunately, we've already seen a better way. We can construct a function "on the fly" without declaring it at the top level or giving it a name.
Compute (filter (fun leqb (length l) 1) [ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]).
(* ==>  [3]; [4]; [8]  *)

Exercise: 2 stars, standard (filter_even_gt7)

Use filter (instead of Fixpoint) to write a Coq function filter_even_gt7 that takes a list of natural numbers as input and returns a list of just those that are even and greater than 7.
Definition filter_even_gt7 (l : list nat) : list nat
  (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Compute (filter_even_gt7 [1;2;6;7;9;10;3;12;8]). (* ==> 10;12;8 *)
Compute (filter_even_gt7 [5;2;6;7;19;129]). (* ==>  *)

Exercise: 3 stars, standard (partition)

Use filter to write a Coq function partition:
      partition : X : Type,
                  (Xbool) → list Xlist X × list X
Given a set X, a predicate of type X bool and a list X, partition should return a pair of lists. The first member of the pair is the sublist of the original list containing the elements that satisfy the test, and the second is the sublist containing those that fail the test. The order of elements in the two sublists should be the same as their order in the original list.
Your definition should not be recursive: just use filter!
Definition partition {X : Type}
                     (test : X bool)
                     (l : list X)
                   : list X × list X
  (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.

Compute (partition oddb [1;2;3;4;5]). (* ==> (1;3;52;4) *)
Compute (partition (fun xfalse) [5;9;0]). (* ==> (5;9;0) *)
enumerate is a handy function for processing a list of items and which item each one is. Here we'll define it using zero_to_n!
Definition enumerate {X : Type} (l : list X) : list (nat × X) :=
  combine (zero_to_n (length l)) l.

Compute enumerate [true; false; true]. (* ==> (1,true); (2, false); (3, true) *)

Map

Another handy higher-order function is called map. Perhaps you've heard of systems like MapReduce or Hadoop---they're massive, distributed systems that implement efficient computations on huge datasets using what are common higher-order functions like map and reduce (similar to fold, which we'll introduce later).
Fixpoint map {X Y: Type} (f:XY) (l:list X) : (list Y) :=
  match l with
  | [][]
  | h :: t(f h) :: (map f t)
  end.
It takes a function f and a list l = [n1, n2, n3, ...] and returns the list [f n1, f n2, f n3,...] , where f has been applied to each element of l in turn. For example:
Compute (map (fun xplus 3 x) [2;0;2]).
(* ==> 5;3;5 *)
The element types of the input and output lists need not be the same, since map takes two type arguments, X and Y; it can thus be applied to a list of numbers and a function from numbers to booleans to yield a list of booleans:
Compute (map oddb [2;1;2;5]).
(* ==> false;true;false;true *)
It can even be applied to a list of numbers and a function from numbers to lists of booleans to yield a list of lists of booleans:
Compute (map (fun n[evenb n;oddb n]) [2;1;2;5]).
(* ==> [true;false];[false;true];[true;false];[false;true] *)

Exercise: 2 stars, standard (complementary')

Define a different version of complementary that doesn't use recursion; you may of course call a recursive function. (Hint: try using map!)
Definition complementary' (dna1 dna2 : strand) : bool
  (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.

Exercise: 2 stars, standard (map_option)

We can also use lists and options together. Define a function map_option that takes a function f : X option Y and a list of Xs and returns a list of those Ys for which f didn't return None. Take X and Y as implicit arguments.
Fixpoint map_option {X Y : Type} (f : X option Y) (l : list X) : list Y
  (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
EX2 (min_by) Remember argmin? What if we wanted it to work for an arbitrary number of objects? We have list_min from last time, but that only works on lists of nats. Can we come up with min_by which takes a cost function and a list and gives the least element from the list according to that function?
It will look very similar to list_min!
Fixpoint min_by {X : Type} (cost : X nat) (l : list X) : option X (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.

Compute min_by (fun xx) [1;2]. (* ===> Some 1 *)
Compute min_by (fun xx) [2;1]. (* ===> Some 1 *)
Compute min_by length [[1; 2; 3]; [1]; []; [1;2;3;4;5]]. (* ===> Some  *)

Exercise: 2 stars, standard (flat_map)

The function map maps a list X to a list Y using a function of type X Y. We can define a similar function, flat_map, which maps a list X to a list Y using a function f of type X list Y. Your definition should work by 'flattening' the results of f, like so:
        flat_map (fun n ⇒ [n;n+1;n+2]) [1;5;10]
      = [1; 2; 3; 5; 6; 7; 10; 11; 12].
Fixpoint flat_map {X Y: Type} (f: X list Y) (l: list X)
                   : (list Y)
  (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.

Compute (flat_map (fun n[n;n;n]) [1;5;4]).
(* ==> 1; 1; 1; 5; 5; 5; 4; 4; 4 *)

Exercise: 2 stars, standard, optional (map_option')

The flat_map function defined above is a little bit more general than map_option. Define a function map_option' that has map_option's type, but uses flat_map to do the work. You'll need to somehow take f : X option Y and produce some other function of type X list Y.
Definition map_option' {X Y : Type} (f: X option Y) (l : list X) : list Y
  (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
One very general higher-order list function is fold_right. Fold takes three arguments: a function f : X Y Y, an initial value of type Y, and a list X. It outputs a Y. Fold will call f with each element of the list in turn (starting from the back and going to the front) along with the result of the previous call to f (or, for the first element of the list, the initial value). It can be used in a variety of fun ways:
Fixpoint fold_right {X Y : Type} (f: X Y Y) (seed : Y) (l : list X) : Y :=
  match l with
  | []seed
  | a :: l' ⇒ (f a (fold_right f seed l'))
  end.

Compute fold_right plus 0 [1; 2; 3; 4; 5]. (* Adds up the numbers in the list. *)
Compute fold_right andb true [true; false; false; true]. (* True if all the elements of the list are true. *)
Compute fold_right (fun (x:bool) (y:nat) ⇒ if x then y + 1 else y) 0 [true; false; false; true]. (* Counts the number of trues in the list *)
fold_left is like fold_right but applies the function starting from the seed and first element:
Fixpoint fold_left {X Y : Type} (f : X Y Y) (seed : Y) (l : list X) : Y :=
  match l with
  | []seed
  | a::l'fold_left f (f a seed) l'
  end.

Compute fold_left plus 0 [1; 2; 3]. (* ===> 6 *)
Compute fold_left minus 10 [1; 2; 3]. (* ===> 3 - (2 - (1 - 10)) = 1 *)
Compute fold_right minus 10 [1; 2; 3]. (* ===> 1 - (2 - (3 - 10)) = 0 *)
What's the first day I like?
Compute fold_left
        (fun x bestmatch best with
                       | Noneif is_weekday x then Some x else None
                       | Some daySome day
                       end)
        None
        [sunday; monday; tuesday; wednesday; thursday; friday; saturday].
(* ===> monday *)
What's the last day I like?
Compute fold_right
        (fun x bestmatch best with
                       | Noneif is_weekday x then Some x else None
                       | Some daySome day
                       end)
        None
        [sunday; monday; tuesday; wednesday; thursday; friday; saturday].
(* ===> friday *)
Can we do it with a friendly combinator?
Definition option_or {X:Type} (o : option X) (or : option X) : option X :=
  match o with
  | Noneor
  | Some _o
  end.
Compute fold_left
        (fun x bestif is_weekday x then option_or best (Some x) else best)
        None
        [sunday; monday; tuesday; wednesday; thursday; friday; saturday].
We can go combinator-crazy:
Definition option_lift {X:Type} (f : X bool) (x : X) : option X :=
  if (f x) then Some x else None.

Compute fold_left
        
Ahh, so concise!
        (fun x bestoption_or best (option_lift is_weekday x))
        None
        [sunday; monday; tuesday; wednesday; thursday; friday; saturday].
We can pack it into a handy helper:
Definition first_satisfying {X : Type} (f : X bool) (l : list X) : option X :=
  fold_left (fun x bestoption_or best (option_lift f x)) None l.

Compute first_satisfying
        is_weekday
        [sunday; monday; tuesday; wednesday; thursday; friday; saturday].
And now we're done with this weekday picking example, we've reached the apotheosis of day selection.
Fold is extremely generic, and many of our list functions can be built on top of it.
map via fold_right:
Definition map' {X Y : Type} (f: X Y) (l : list X) : list Y :=
  fold_right (fun x out(f x)::out) [] l.

Compute map' (mult 2) [1; 2; 3]. (* ===> 2;4;6 *)
length:
Definition length' {X:Type} (l : list X) : nat :=
  fold_right (fun x outout+1) 0 l.

Compute length' [1; 2; 3]. (* ===> 3 *)
How is it so general? Well, look at its definition---fold_right and fold_left are pretty much just our list fixpoint template! Take map as an example, and compare it with fold_right (renaming f in fold_right to g for clarity):

  match l with
  | [] ⇒ []
  | h :: tcons (f h) (map f t)
  end

  match l with
  | [] ⇒ seed
  | a :: l'g a (fold_right g seed l')
  end.
We can rearrange the terms of map like so:

  let g := (fun a restcons (f a) rest) in
  match l with
  | [] ⇒ []
  | h :: tg h (map f t)
  end
fold_right just makes g and the base case output into parameters!

  match l with
  | [] ⇒ acc (* for map, this is  --- the base case *)
  | a::l'g a (fold_right g seed l') (* if g does cons (f a) rest then this is just map'! *)
  end

Exercise: 2 stars, standard (fold_practice)

Can you implement filter, flat_map, and min_by using folds? No fixpoints allowed!
Hints: For filter', look at map'! For flat_map', also look at map' but think about how you'll be adding elements onto the list.
Definition filter' {X : Type} (f : X bool) (l : list X) : list X (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.

Compute filter' evenb [1;2;3;4]. (* ===> 2;4 *)
Compute filter' (flip evenb) [1;2;3;4]. (* ===> 1;3 *)

Definition flat_map' {X : Type} (f : X list X) (l : list X) : list X (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.

Compute flat_map' zero_to_n [1;3;5].
This one is tricky. Check out the weekday example earlier. unwrap_or may be convenient!
Definition min_by' {X : Type} (f : X nat) (l : list X) : option X (* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.

Compute min_by' (fun xx) [1;2]. (* ===> Some 1 *)
Compute min_by' (fun xx) [2;1]. (* ===> Some 1 *)
Compute min_by' length [[1; 2; 3]; [1]; []; [1;2;3;4;5]]. (* ===> Some  *)

(* Do not modify the following line: *)
Definition manual_grade_for_fold_practice : option (nat×string) := None.
(* 2022-01-12 10:20 *)