#utop_prompt_dummy
let _ = UTop.set_show_box false 

(* Local Bindings *)

let silly1 (z : int) =
  let x = if z > 0 then z else 34 in
  let y = x + 9 in
  if x > y then x * 2 else y * y

let silly2 () = (* poor style, just showing let expressions are expressions *)
  let x = 1 in
  (let x = 2 in x+1) + (let y = x+2 in y+1)

let celsius_of_fahrenheit (tempF : float) =
  (tempF -. 32.0) *. 5.0 /. 9.0

(* example where let expressions probably make code more readable even though
   no computations are repeated *)
(* will have better ways to do this with variant types *)
let is_first_warmer ((temp1 : float), (is_celsius1 : bool),
                     (temp2 : float), (is_celsius2 : bool)) =
  let temp1C = if is_celsius1 then temp1 else celsius_of_fahrenheit temp1 in
  let temp2C = if is_celsius2 then temp2 else celsius_of_fahrenheit temp2 in
  temp1C > temp2C

(* example where we avoid repeating a computation, but it would not matter much *)
let rec squaring_sequence ((x:float), (n:int)) =
  if n=0 then
    []
  else
    let sq = x *. x in sq :: squaring_sequence(sq,n-1)

let rec range ((lo : int),(hi : int)) =
  if lo < hi then
    lo :: range (lo + 1, hi)
  else
    []

let nats_upto1 (x : int) =
  range (0,x)
let nats_upto2 (x : int) =
  let rec range ((lo : int),(hi : int)) =
    if lo < hi then
      lo :: range (lo + 1,hi)
    else
      []
  in
  range (0,x)

let nats_upto3 (x : int) =
  let rec loop (lo : int) = (* loop is NOT a special word, could call it fred *)
    if lo < x then
        lo :: loop (lo + 1)
      else
        []
  in
  loop 0

(* badly named: evaluates to 0 on empty list *)
let rec bad_max (xs : int list) =
  if xs = [] then
    0 (* bad semantic style *)
  else if List.tl xs = [] then
    List.hd xs
  else if List.hd xs > bad_max (List.tl xs) then
    List.hd xs
  else
    bad_max (List.tl xs)

let rec better_max (xs : int list) =
  if xs = [] then
    0 (* bad semantic style *)
  else if List.tl xs = [] then
    List.hd xs
  else
    let tl_max = better_max (List.tl xs) in
    if List.hd xs > tl_max  then
      List.hd xs
    else
      tl_max

let rec good_max1 (xs : int list) =
  if xs = [] then
    None
  else
    let tl_ans = good_max1 (List.tl xs) in
    if not (tl_ans = None) && Option.get tl_ans > List.hd xs then
      tl_ans
    else
      Some (List.hd xs)

let good_max2 (xs : int list) =
  if xs = [] then
    None
  else
    let rec max_nonempty (xs : int list) =
      if List.tl xs = [] (* xs better not be [] *) then
        List.hd xs
      else
        let tl_ans = max_nonempty (List.tl xs) in
        if List.hd xs > tl_ans then
          List.hd xs
        else
          tl_ans
    in
    Some (max_nonempty xs)

(*let does_not_typecheck = (good_max1 [3;4;2]) + 4*)
(* A t option is *not* an int *)

let risky_but_works = (Option.get (good_max1 [3;4;2])) + 2

(* type-checks, raises exception *)
(* let risky_and_fails = (Option.get (good_max1 [])) + 2*)
let propagate_option =
  let x = good_max1 [3;4;2] in
  if Option.is_some x then Some ((Option.get x) + 2) else None

(* benefits of no mutation *)

(* does not matter if returns an alias *)
let sort_pair (pr : int * int) : int * int =
  if fst pr < snd pr then
    pr (* (fst pr, snd pr) *)
  else
    (snd pr, fst pr)

(* naturally and efficiently introduces sharing and we don't care *)
let rec append ((xs : 'a list), (ys : 'a list)) =
  if xs = [] then
    ys
  else
    List.hd xs :: append (List.tl xs, ys)