(* SICP Chapter #03 Examples in F# *)
#light
let rec superexp = fun (b:float) (l:int) -> if l = 0 then b else b**(superexp b (l-1)) ;;
superexp 1.1 16 ;;
superexp 1.3 36 ;;
superexp 1.444 622 ;;
superexp 1.4446678 46598 ;;
(* Functions defined in previous chapters *)
let rec gcd a = function
| 0 -> a
| b -> gcd b (a % b)
let square_real x = x * x
let average x y = (x + y) / 2.0
let rec has_no_divisors n c =
c = 1 || (n % c <> 0 && has_no_divisors n (c-1))
let isPrime n = has_no_divisors n (n-1)
let rec enumerate_interval low high =
if low > high
then []
else low :: enumerate_interval (low+1) high
let compose f g x = f(g x)
let isOdd n = (n % 2 = 1)
let isEven = compose not isOdd
(* 3.1.1 - Assignment and Local State - Local State Variables *)
let balance = ref 100
exception InsufficientFunds of int
let withdraw amount =
if !balance >= amount
then begin balance := (!balance - amount); !balance end
else raise (InsufficientFunds (!balance))
let _ = withdraw 25
let _ = withdraw 25
let _ = try withdraw 60 with InsufficientFunds b -> b
let _ = withdraw 15
let new_withdraw =
let balance = ref 100
in fun amount ->
if !balance >= amount
then begin balance := (!balance - amount); !balance end
else raise (InsufficientFunds (!balance))
let make_withdraw balance =
let balance = ref balance
in fun amount ->
if !balance >= amount
then begin balance := (!balance - amount); !balance end
else raise (InsufficientFunds (!balance))
let w1 = make_withdraw 100
let w2 = make_withdraw 100
let _ = w1 50
let _ = w2 70
let _ = try w2 40 with InsufficientFunds b -> b
let _ = w1 40
type ('a, 'b, 'c) account_rec = { withdraw:'a; deposit:'b; balance:'c }
let make_account balance =
let balance = ref balance in
let withdraw amount =
if !balance >= amount
then begin balance := (!balance - amount); !balance end
else raise (InsufficientFunds (!balance))
and deposit amount =
balance := (!balance + amount); !balance
and getbalance = !balance
in { withdraw=withdraw; deposit=deposit; balance=getbalance }
let acc = make_account 100
let _ = acc.withdraw 50
let _ = try acc.withdraw 60 with InsufficientFunds b -> b
let _ = acc.deposit 40
let _ = acc.withdraw 60
let acc2 = make_account 100
(* Exercise 3.1 *)
(* exercise left to reader to define appropriate functions
let a = make_accumulator 5
let _ = a.f 10
let _ = a.f 10 *)
(* Exercise 3.2 *)
(* exercise left to reader to define appropriate functions
let s = make_monitored sqrt
let _ = s.f 100
let _ = s.how_many_calls() *)
(* Exercise 3.3 *)
(* exercise left to reader to define appropriate functions
let acc = make_account 100 "secret-password"
let _ = acc.withdraw 40 "secret-password"
let _ = acc.withdraw 50 "some-other-password" *)
(* 3.1.2 - Assignment and Local State - The Benefits of Introducing Assignment *)
let random_init = ref 7
let rand_update x =
let a = 27
and b = 26
and m = 127
in (a * x + b) % m
let rand =
let x = random_init
in fun () -> x := (rand_update !x); !x
let cesaro_test () = gcd (rand()) (rand()) = 1
let monte_carlo trials experiment =
let rec iter trials_remaining trials_passed =
match trials_remaining with
| 0 -> float_of_int trials_passed / float_of_int trials
| _ ->
if experiment()
then iter (trials_remaining - 1) (trials_passed + 1)
else iter (trials_remaining - 1) trials_passed
in iter trials 0
let estimate_pi trials = sqrt (6. / (monte_carlo trials cesaro_test))
(* second version (no assignment) *)
let random_gcd_test trials initial_x =
let rec iter x trials_passed =
let x1 = rand_update x in
let x2 = rand_update x1
in function
| 0 -> (float trials_passed) / (float trials)
| trials_remaining ->
iter x2
(if gcd x1 x2 = 1 then (trials_passed + 1) else trials_passed)
(trials_remaining - 1)
in iter initial_x 0 trials
(* alternate translation *)
let random_gcd_test' trials initial_x =
let rec iter trials_remaining trials_passed x =
let x1 = rand_update x in
let x2 = rand_update x1
in
if trials_remaining = 0
then float_of_int trials_passed / float_of_int trials
else
if gcd x1 x2 = 1
then iter (trials_remaining - 1) (trials_passed + 1) x2
else iter (trials_remaining - 1) trials_passed x2
in iter trials 0 initial_x
let estimate_pi' trials = sqrt (6. / (random_gcd_test' trials !random_init))
(* Exercise 3.6 *)
(* exercise left to reader to define appropriate functions
fun random_in_range low high =
let range = high - low
in low + random range
end *)
(* 3.1.3 - Assignment and Local State - The Cost of Introducing Assignment *)
let make_simplified_withdraw balance =
let balance = ref balance in
fun amount ->
balance := !balance - amount;
!balance
let w = make_simplified_withdraw 25
let _ = w 20
let _ = w 10
let make_decrimenter balance =
fun amount -> balance - amount
let d = make_decrimenter 25
let _ = d 20
let _ = d 10
let _ = (make_decrimenter 25) 20
let _ = (fun amount -> 25 - amount) 20
let _ = 25 - 20
let _ = (make_simplified_withdraw 25) 20
(* we add an additional step here to handle the introduction of the balance ref *)
let _ = (let balance = ref 25 in fun amount -> balance := !balance - amount) 20
let _ = (fun amount -> balance := 25 - amount) 20
let _ = (balance := 25 - 20)
(* Sameness and change *)
let d2 = make_decrimenter 25
let d2' = make_decrimenter 25
let w1' = make_simplified_withdraw 25
let w2' = make_simplified_withdraw 25
let _ = w1' 20
let _ = w1' 20
let _ = w2' 20
let peter_acc = make_account 100
let paul_acc = make_account 100
let peter_acc' = make_account 100
let paul_acc' = peter_acc
(* Pitfalls of imperative programming *)
let factorial n =
let rec iter product counter =
if counter > n
then product
else iter (counter * product) (counter + 1)
in iter 1 1
let factorial' n =
let product = ref 1
and counter = ref 1 in
let rec iter () =
if !counter > n
then !product
else
begin
product := !product * !counter;
counter := !counter + 1;
iter ()
end
in iter ()
(* Exercise 3.7 * )
( * exercise left to reader to define appropriate functions
let paul_acc = make_joint peter_acc "open_sesame" "rosebud" *)
(* 3.2.1 - The Environment Model of Evaluation - The Rules for Evaluation *)
let square x = x * x
let square' = fun x -> x * x
(* 3.2.2 - The Environment Model of Evaluation - Applying Simple Procedures *)
let square'' x = x * x
let sum_of_squares x y =
square x + square y
let f a =
sum_of_squares (a + 1) (a * 2)
(* exercise 3.9 *)
let rec factorial_2 = function
| 0 -> 1
| n -> n * factorial_2 (n-1)
let rec fact_iter product counter max_count =
if counter > max_count
then product
else fact_iter (counter * product) (counter + 1) max_count
let factorial_3 n = fact_iter 1 1 n
module sicp_localstate_translation =
(* 3.2.3 - The Environment Model of Evaluation - Frames as Repository of Local State *)
let make_withdraw balance =
let balance = ref balance
in fun amount ->
if !balance >= amount
then (balance := !balance - amount; !balance)
else raise (InsufficientFunds (!balance))
let w1 = make_withdraw 100
let _ = w1 50
let w2 = make_withdraw 100
(* Exercise 3.10 *)
let make_withdraw' initial_amount =
let balance = ref initial_amount
in fun amount ->
if !balance >= amount
then (balance := !balance - amount; !balance)
else raise (InsufficientFunds (!balance))
let w1' = make_withdraw' 100
let _ = w1' 50
let w2' = make_withdraw' 100
module sicp_internaldef_translation =
(* 3.2.4 - The Environment Model of Evaluation - Internal Definitions *)
(* redefine square to work on floats *)
let square_real' x = x * x
(* same as in section 1.1.8 *)
let sqrt' x =
let good_enough guess =
abs_float(square_real guess - x) < 0.001
and improve guess =
average guess (x / guess) in
let rec sqrt_iter guess =
if good_enough guess
then guess
else sqrt_iter (improve guess)
in sqrt_iter 1.0
let make_account balance =
let balance = ref balance in
let withdraw amount =
if !balance >= amount
then (balance := !balance - amount; !balance)
else failwith "Insufficient funds"
and deposit amount = balance := !balance + amount; !balance
and getbalance () = !balance
in { withdraw=withdraw; deposit=deposit; balance=getbalance }
let acc = make_account 50
let _ = acc.deposit 40
let _ = acc.withdraw 60
let acc2 = make_account 100
(* 3.3.1 - Modeling with Mutable Data - Mutable List Structure *)
(* Note: ML lists can handle types of 'a ref, but this
can't be used to set the tail of the list. To
do this trick, we need to define a list that
has a ref for the head and tail. Means extra work for ML.
A better solution for F# is to use purely functional
data structures. *)
exception NotFound
(*
type MyWidget =
{ GetLength : unit -> int;
Get : unit -> string;
Update : string -> unit }
let MakeWidget(s1:string) =
let state = ref (s1, s1.Length) in
let get() = fst(!state) in
let getLength() = snd(!state) in
let update(s:string) = state := (s,s.Length) in
{ GetLength = getLength;
Get = get;
Update = update }
*)
type 'a mlist = MNil | MCons of 'a ref * 'a mlist ref
type 'a MLIST = { cons : 'a -> 'a mlist -> 'a mlist;
car : 'a mlist -> 'a;
cdr : 'a mlist -> 'a mlist;
set_car : 'a mlist -> 'a -> unit;
set_cdr : 'a mlist -> 'a mlist -> unit;
make_list : 'a list -> 'a mlist;
append : 'a mlist -> 'a mlist -> 'a mlist }
let MList =
let cons x y = MCons(ref x, ref y)
let car = function
| MCons(x, xs) -> !x
| MNil -> raise NotFound
let cdr = function
| MCons(x, xs) -> !xs
| MNil -> raise NotFound
let set_car mlist y =
match mlist with
| MCons(x, xs) -> (x := y)
| MNil -> raise NotFound
let set_cdr mlist ys =
match mlist with
| MCons(x, xs) -> (xs := ys)
| MNil -> raise NotFound
let make_list xs = List.fold_right (fun v b -> cons v b) xs MNil
let rec append mlist ys =
match mlist with
| MNil -> ys
| MCons(x, xs) -> cons (!x) (append (!xs) ys)
in
{ cons = cons;
car = car;
cdr = cdr;
set_car = set_car;
set_cdr = set_cdr;
make_list = make_list;
append = append }
(* Sharing and identity *)
let x = MList.make_list ["a"; "b"]
let z1 = MList.make_list [x; x]
let z2 = MList.make_list [MList.make_list ["A"; "B"]; MList.make_list ["A"; "B"]]
let set_to_wow x =
let _ = MList.set_car (MList.car x) "Wow"
in x
let _ = z1
let _ = set_to_wow z1
let _ = z2
let _ = set_to_wow z2
(* Mutation as assignment *)
type dispatch = Car | Cdr
type ('a,'b) pair' = Left of 'a | Right of 'b
type ('a,'b) PAIR = { cons : 'a -> 'b -> dispatch -> ('a,'b) pair';
car : (dispatch -> ('a,'b) pair') -> 'a;
cdr : (dispatch -> ('a,'b) pair') -> 'b }
module Pair' =
let cons x y =
let pdispatch = function
| Car -> Left x
| Cdr -> Right y
in pdispatch
let car z =
match z Car with
| Left c -> c
| _ -> raise NotFound
let cdr z =
match z Cdr with
| Right c -> c
| _ -> raise NotFound
in
{ cons = cons;
car = car;
cdr = car }
type mdispatch = Car | Cdr | SetCar | SetCdr
type ('a,'b) mpair = Left of 'a
| Right of 'b
| LSet of ('a -> unit)
| RSet of ('b -> unit)
type ('a,'b) MPAIR = { cons : 'a -> 'b -> mdispatch -> ('a, 'b) mpair;
car : (mdispatch -> ('a, 'b) mpair) -> 'a;
cdr : (mdispatch -> ('a, 'b) mpair) -> 'b;
set_car : (mdispatch -> ('a, 'b) mpair) -> 'a -> unit;
set_cdr : (mdispatch -> ('a, 'b) mpair) -> 'b -> unit }
let MPair =
let cons x y =
let a = ref x
and b = ref y in
let setx v = (a := v)
and sety v = (b := v) in
let pdispatch = function
| Car -> Left (!a)
| Cdr -> Right (!b)
| SetCar -> LSet setx
| SetCdr -> RSet sety
in pdispatch
let car z =
match z Car with
| Left c -> c
| _ -> raise NotFound
let cdr z =
match z Cdr with
| Right c -> c
| _ -> raise NotFound
let set_car z x =
match z SetCar with
| LSet f -> f x
| _ -> raise NotFound
let set_cdr z y =
match z SetCar with
| RSet f -> f y
| _ -> raise NotFound
in
{ cons = cons;
car = car;
cdr = cdr;
set_car = set_car;
set_cdr = set_cdr }
(* This example does not require dynamic dispatch and run-time errors.
Indeed, there is nothing dynamic about this example. It was implemented
that way in Scheme because this is all that Scheme can do. In F#,
you can use static checking to eliminate all run-time errors: *)
type ('a, 'b) cell =
{ car: unit -> 'a;
cdr: unit -> 'b;
set_car: 'a -> unit;
set_cdr: 'b -> unit }
let cons x y =
let x = ref x
and y = ref y
in
{ car = (fun () -> !x);
cdr = (fun () -> !y);
set_car = (fun x' -> x := x');
set_cdr = (fun y' -> y := y') }
let xa = cons 1 2
let za = cons xa xa
(za.cdr()).set_car 17
xa.car()
(* Exercise 3.12 *)
let rec last_pair xs =
match MList.cdr xs with
| MNil -> xs
| tail -> last_pair tail
let rec mappend xs ys =
let _ = MList.set_cdr (last_pair xs) ys
in xs
let xb = MList.make_list ['a'; 'b']
let yb = MList.make_list ['c'; 'd']
let zb = mappend xb yb
let _ = zb
let wb = mappend xb yb
let _ = wb
let _ = xb
(* Exercise 3.13 *)
let make_cycle xs =
let _ = MList.set_cdr (last_pair xs) xs
in xs
let zc = make_cycle (MList.make_list ['a'; 'b'; 'c'])
(* Exercise 3.14 *)
let mystery x =
let rec loop x y =
match x with
| MNil -> y
| _ ->
let temp = MList.cdr x
in ( MList.set_cdr x y; loop temp x )
in loop x MNil
let v = MList.make_list ['a'; 'b'; 'c'; 'd']
let wd = mystery v
(* Exercise 3.16 *)
(* Write a function to count the number of cons cells in a binary tree. In
F#, a cons cell is a Node: *)
type t = CEmpty | CNode of t * t
(* The following incorrect code from the book fails to take cyclic trees into account: *)
let rec count = function
| CNode(a, b) -> 1 + count a + count b
| CEmpty -> 0
(* Exercise 3.20 *)
let xe = MPair.cons 1 2
let ze = MPair.cons xe xe
MPair.set_car (MPair.cdr ze) 17
MPair.car xe
|