(* 15-150, Spring 2024 *)
(* Michael Erdmann & Dilsun Kaynar *)
(* Code for Lecture 10: Higher-Order Functions *)
(************************************************************************)
(* Below, add is a function that takes two integers and computes their sum.
plus is the curried version of add.
*)
(* add : int * int -> int *)
fun add (x:int,y:int):int = x + y
(* plus : int -> int -> int *)
fun plus (x:int) : int -> int =
fn (y:int) => x + y
val incr3 : int->int = plus 3
val 7 = incr3 4
val 13 = plus 7 6
(* Could also write plus in the following more compact form: *)
fun plus (x:int) (y:int) : int = x + y
val incr3 : int->int = plus 3
val 7 = incr3 4
val 13 = plus 7 6
(* Both of the previous declarations ultimately amount to the following: *)
val plus : int -> int -> int = fn (x:int) => fn (y:int) => x + y
val incr3 : int->int = plus 3
val 7 = incr3 4
val 13 = plus 7 6
(* Suppose we define
val incr4 = plus 4
right after defining incr3 in the code above.
QUESTION: Is incr3 visible in the environment of incr4?
The answer is "no" because the environment in effect at the time of
definition of incr4 is the environment created by calling plus on 4:
plus 4
==> [environment at time of def of plus][4/x] "body of plus"
so [environment at time of def of plus][4/x] (fn y => x + y)
At the point that (fn y => x + y) is created, the environment is:
[environment at time of def of plus][4/x]
And that environment does *not* include incr3.
Also: make sure you understand that (fn y => x + y) is a value,
so no further reduction.
*)
(*
Here is an example where this difference in environments comes out clearly:
*)
fun adjust (n:int):int = 7*n
fun sillyplus (x:int) (y:int) : int = x + (adjust y)
fun adjust (n:int):int = 100*n
val incr4 = sillyplus 4
(* So incr4 is a closure of the form
[prior environment][4/x](fn y => x + (adjust y))
If the "prior environment" were to include the 2nd declaration of adjust,
then we would have
incr4 10
==> [(fn n => 100*n)/adjust, 4/x] (fn y => x + (adjust y)) 10
==> [(fn n => 100*n)/adjust, 4/x, 10/y] x + (adjust y)
==> 4 + 1000
==> 1004
However, only the first declaration of adjust is visible in the
"prior environment", giving us:
incr4 10
==> [(fn n => 7*n)/adjust, 4/x] (fn y => x + (adjust y)) 10
==> [(fn n => 7*n)/adjust, 4/x, 10/y] x + (adjust y)
==> 4 + 70
== 74
*)
(* Test: *)
val 74 = incr4 10
(************************************************************************)
(* filter example *)
(* filter : ('a -> bool) -> 'a list -> 'a list
REQUIRES: p is total
ENSURES: filter p L evaluates to a list consisting of
the elements of L that satisfy p,
with elements appearing in same order as in L.
*)
(* Note: this function is predefined in SML as List.filter *)
fun filter (p : 'a -> bool) (nil : 'a list) : 'a list = nil
| filter p (x::xs) = if p(x) then x::(filter p xs)
else filter p xs
val keepevens = filter (fn n => n mod 2 = 0)
val [2,~6, 10] = keepevens [1,2,~5,~6,11,10,13]
val [2,~6, 10] = filter (fn n => n mod 2 = 0) [1,2,~5,~6,11,10,13]
(************************************************************************)
(* A mapreduce example for stock purchases: *)
(* pairup : int list -> (int * int) list
REQUIRES: L contains at least two elements
ENSURES: pairup(L) computes a nonempty list one shorter than L,
pairing each element of L except the last one
with the max value in L to the right of the element.
*)
fun pairup [x,y] = [(x,y)]
| pairup (x::xs) =
let
val (y,m)::rest = pairup xs (* this is provably exhaustive *)
in
(x, Int.max(y,m))::(y,m)::rest
end
| pairup _ = raise Fail "input list too short"
val prices = [20,25,24,30,20]
val [(20,30), (25, 30), (24, 30), (30, 20)] = pairup prices
(* Here is an alternative implementation, with different behavior
when the input doesn't meet the REQUIRES: *)
fun pairup [] = []
| pairup [_] = []
| pairup [x,y] = [(x,y)]
| pairup (x::xs) =
let
val (y,m)::rest = pairup xs (* this is provably exhaustive *)
in
(x, Int.max(y,m))::(y,m)::rest
end
val prices = [20,25,24,30,20]
val [(20,30), (25, 30), (24, 30), (30, 20)] = pairup prices
(* Finally, here is a version without potentially nonexhaustive bindings: *)
fun pairup [] = []
| pairup [_] = []
| pairup [x,y] = [(x,y)]
| pairup (x::xs) =
case pairup xs of
(y,m)::rest => (x, Int.max(y,m))::(y,m)::rest
| _ => raise Fail "supposedly unreachable code reached!"
val prices = [20,25,24,30,20]
val [(20,30), (25, 30), (24, 30), (30, 20)] = pairup prices
(* gain : int * int -> int
REQUIRES: true
ENSURES: gain(buy, sell) ==> sell - buy
*)
fun gain (buy, sell) = sell - buy
val SOME(neginf) = Int.minInt
(* bestgain : int list -> int
REQUIRES: L contains at least two elements,
neginf is bound to a very negative integer.
(Yes, it would be better to use the extended integers from lecture 5.
How would you rewrite this function to use that datatype?)
ENSURES: The best possible gain obtainable by buying at a price in L
and selling at a price strictly to the right of the buy price.
*)
fun bestgain L = foldr Int.max neginf (map gain (pairup L))
val [10,5,6,~10] = map gain (pairup prices)
val 10 = bestgain prices
(************************************************************************)