Iteration and Search
Implement the two fundamental iteration patterns from scratch, then use partial application to build a family of specialized utilities on top of them. This exercise ties together currying, higher-order functions, and tail recursion.
Start by implementing the two core iteration functions. Then use them as building blocks to solve concrete problems. The goal is to get comfortable using partial application to specialize a general function into a specific one.
- 01Implement
iter f n start: appliesfexactlyntimes tostart. Must be tail recursive. - 02Use
iterto definedouble_n_times: doubles a number exactlyntimes. Use partial application. - 03Use
iterto defineadd_stepandcount_by_3: add a fixed amountntimes. - 04Implement
first pred start: returns the smallest integer at or abovestartfor whichpredholds. - 05Use
firstto definefirst_divisible_by n start. - 06Use
firstto definefirst_perfect_square start. A numberxis a perfect square iffloor(sqrt(x))^2 = x. - 07Use
firstto definenatural_sqrt n: computefloor(sqrt(n))without using any floating-point square root.
Starter code
(* Definite iteration *) let rec iter (f : int -> int) (n : int) (start : int) = (* TODO *) start let double_n_times n = (* TODO: partial application of iter *) iter (fun x -> x) n let add_step step = (* TODO: partial application of iter *) iter (fun x -> x) step let count_by_3 = (* TODO: partial application of add_step *) add_step 0 (* Indefinite iteration *) let rec first (pred : int -> bool) (start : int) = (* TODO *) start let first_divisible_by n start = (* TODO *) 0 let first_perfect_square start = (* TODO: use int_of_float (sqrt (float_of_int x)) *) 0 let natural_sqrt n = (* TODO: use first to find the first k where k*k > n *) 0
Hints
For iter, the base case is when n < 1: return start as is. For the recursive case, call iter again with n - 1 and f start as the new starting value. This keeps the recursion in tail position.
For double_n_times, notice that iter (fun x -> x * 2) is a function of type int -> int -> int. Supplying just iter (fun x -> x * 2) gives you a function that still expects n and start. That is exactly double_n_times.
For natural_sqrt, use first to find the first k where k * k > n, starting from 1. Then subtract 1 from the result.
Every derived function is just iter or first with one or more arguments already fixed. Recognize this pattern and the implementations become one-liners.
first_perfect_square uses OCaml's built-in sqrt to check whether a number is a perfect square, but natural_sqrt must not. The whole point of natural_sqrt is to compute the integer square root using only integer arithmetic through first.
(* Definite iteration *) let rec iter (f : int -> int) (n : int) (start : int) = if n < 1 then start else iter f (n - 1) (f start) let double_n_times = iter (fun x -> x * 2) (* double_n_times : int -> int -> int *) let add_step step = iter (fun x -> x + step) let count_by_3 = add_step 3 (* Indefinite iteration *) let rec first (pred : int -> bool) (start : int) = if pred start then start else first pred (start + 1) let first_divisible_by n = first (fun x -> x mod n = 0) let first_perfect_square start = let is_square x = let s = int_of_float (sqrt (float_of_int x)) in s * s = x in first is_square start let natural_sqrt n = (first (fun k -> k * k > n) 1) - 1 let () = Printf.printf "1 doubled 4 times: %d\n" (double_n_times 4 1); Printf.printf "count_by_3, 5 steps from 0: %d\n" (count_by_3 5 0); Printf.printf "First multiple of 7 from 50: %d\n" (first_divisible_by 7 50); Printf.printf "First perfect square from 20: %d\n" (first_perfect_square 20); Printf.printf "floor(sqrt(50)): %d\n" (natural_sqrt 50)
Walkthrough
iter. The function simply decrements n and applies f to start on each recursive call. Both updated values are passed forward as arguments, so there is nothing pending after the recursive call. This makes it tail recursive.
double_n_times. Supplying iter (fun x -> x * 2) produces a function waiting for n and start. That is exactly the type of double_n_times. No wrapper function is needed.
add_step and count_by_3. add_step takes step and returns a partially applied iter. Then count_by_3 partially applies add_step with 3, giving a function of type int -> int -> int. Two levels of partial application, both one-liners.
natural_sqrt. The key insight is that the first k where k * k > n overshoots by exactly one step. Subtracting 1 gives the floor of the square root. No floating-point arithmetic involved.
Going further
Once everything works, try these extensions:
- Generalize
iterto work with any type, not justint. What does the signature look like? - Write
iterate_until f pred startthat appliesfrepeatedly untilpredholds, similar tofirstbut with a custom step function. - Use
iterto implement the Fibonacci sequence:fib nvia a pair accumulator(a, b)where each step becomes(b, a + b).
(* Fibonacci using iter with a pair accumulator *) let fib n = let step (a, b) = (b, a + b) in let rec loop acc k = if k < 1 then fst acc else loop (step acc) (k - 1) in loop (0, 1) n fib 10 (* 55 *)