About SICP The following Erlang code is derived from the examples provided in the book:
      "Structure and Interpretation of Computer Programs, Second Edition" by Harold Abelson and Gerald Jay Sussman with Julie Sussman.
      http://mitpress.mit.edu/sicp/

SICP Chapter #02 Examples in Erlang

-module(sicp02).
-export([start/0]).

print(X) ->
   io:write(X),
   io:format("~n").

start() ->
   section_2_1_1(),
   section_2_1_2(),
   section_2_1_3(),
   section_2_1_4(),
   section_2_2_1(),
   section_2_2_2(),
   section_2_2_3(),
   section_2_2_4(),
   section_2_3_1(),
   section_2_3_2(),
   section_2_3_3(),
   section_2_3_4(),
   section_2_4_1(),
   section_2_4_2(),
   section_2_4_3(),
   section_2_5_1().

% Functions defined in previous chapters
gcd(A, 0) -> A;
gcd(A, B) -> gcd(B, A rem B).

fib(0) -> 0;
fib(1) -> 1;
fib(N) -> fib(N - 1) + fib(N - 2).

identity(X) -> X.

% 2 Building Abstractions with Data
linear_combination(A, B, X, Y) ->
   (A * X) + (B * Y).

mul(A, B) -> A * B.

linear_combination_1(A, B, X, Y) ->
   mul(A, X) + mul(B, Y).


% 2.1.1 Introduction to Data Abstraction - Example: Arithmetic Operations for Rational Numbers
section_2_1_1() ->
   X = cons(1, 2),
   Y = cons(3, 4),
   Z = cons(X, Y),
   print (car(X)),
   print (cdr(X)),
   print (car(car(Z))),
   print (car(cdr(Z))),

   One_Half = make_rat(1, 2),
   print_rat(One_Half),

   One_Third = make_rat(1, 3),
   print_rat(add_rat(One_Half, One_Third)),
   print_rat(mul_rat(One_Half, One_Third)),
   print_rat(add_rat(One_Third, One_Third)),

   print_rat(add_rat_1(One_Third, One_Third)).

cons(X, Y) -> [X, Y].
car([H|_]) -> H.
cdr([_|T]) -> T.
cadr(X) -> car(cdr(X)).

make_rat(N, D) -> [N, D].
numer(X) -> hd(X).
denom(X) -> hd(tl(X)).

add_rat(X, Y) ->
   make_rat(numer(X) * denom(Y) + numer(Y) * denom(X), denom(X) * denom(Y)).
sub_rat(X, Y) ->
   make_rat(numer(X) * denom(Y) - numer(Y) * denom(X), denom(X) * denom(Y)).
mul_rat(X, Y) ->
   make_rat(numer(X) * numer(Y), denom(X) * denom(Y)).
div_rat(X, Y) ->
   make_rat(numer(X) * denom(Y), denom(X) * numer(Y)).
equal_rat(X, Y) ->
   ((numer(X) * denom(Y)) == (numer(Y) * denom(X))).

print_rat(X) ->
   io:write(numer(X)),
   io:format('/'),
   io:write(denom(X)),
   io:format("~n").

% reducing to lowest terms in constructor %
make_rat_1(N, D) ->
   G = gcd(N, D),
   [N div G, D div G].

add_rat_1 (X, Y) ->
   make_rat_1((numer(X) * denom(Y)) + (numer(Y) * denom(X)), denom(X) * denom(Y)).

% Exercise 2.1
make_rat_2(N, D) ->
   if
      ((D < 0 andalso N < 0) orelse N < 0) -> [D * -1, N * -1];
      true -> [D, N]
   end.


% 2.1.2 Introduction to Data Abstraction - Abstraction barriers
section_2_1_2() ->
   R = make_rat_3(6, 9),
   print (numer_3(R)),
   print (denom_3(R)).

% reducing to lowest terms in selectors
make_rat_3(N, D) -> cons(N, D).

numer_3(X) ->
   G = gcd(car(X), cadr(X)),
   car(X) div G.

denom_3(X) ->
   G = gcd(car(X), cadr(X)),
   cadr(X) div (G).

% Exercise 2.2
make_point(X, Y) -> [X, Y].
x_point(Point) -> hd(Point).
y_point(Point) -> hd(tl(Point)).
make_segment(Start_Segment, End_Segment) -> [Start_Segment, End_Segment].
start_segment_(Segment) -> hd(Segment).
end_segment_(Segment) -> hd(tl(Segment)).
midpoint_segment(Segment) ->
   S = start_segment_(Segment),
   E = end_segment_(Segment),
   make_point((x_point(S) + x_point(E)) / 2.0, (y_point(S) + y_point(E)) / 2.0).
print_point(P) ->
   io:format('('),
   io:write(x_point(P)),
   io:format(','),
   io:write(y_point(P)),
   io:format(")~n").

% Exercise 2.3
square(X) -> X * X.
length_segment(Segment) ->
   S = start_segment_(Segment),
   E = end_segment_(Segment),
   math:sqrt(square(x_point(E) - x_point(S)) + square(y_point(E) - y_point(s))).

% Constructors create type tagged using 
-record(axy, {anchor, xlen, ylen}).
-record(seg, {start_segment, end_segment}).
make_rectangle(Anchor, Xlen, Ylen) ->
   #axy{anchor=Anchor, xlen=Xlen, ylen=Ylen}.
make_rectangle_2(Start_Segment, End_Segment) ->
   #seg{start_segment=Start_Segment, end_segment=End_Segment}.

% 'length_rectangle' and 'width_rectangle' act as an abstraction barrier for higher-level
% procedures because 'rectangle' implementation details are buried here, and should the
% implementation change, only these procedures will need to be altered to support the change
length_rectangle(#axy{anchor=Anchor, xlen=Xlen, ylen=Ylen}) -> 0;                   % Compute length ...
length_rectangle(#seg{start_segment=Start_Segment, end_segment=End_Segment}) -> 0.  % Compute length ...

width_rectangle(Rect) ->
   % As per 'length_rectangle' except that rectangle width is returned ...
   0.

% High-level procedures are quarantined from representation / implementation details
area_rectangle(Rect) ->
   length_rectangle(Rect) * width_rectangle(Rect).
perimeter_rectangle(Rect) ->
   length_rectangle(Rect) * 2 + width_rectangle(Rect) * 2.


% 2.1.3 Introduction to Data Abstraction - What is meant by data?
section_2_1_3() ->
   % Exercise 2.6
   Zero = fun (F) -> fun (X) -> X end end.

cons1(X, Y) ->
   fun (N) ->
      case N of
         0 -> X;
         1 -> Y;
         true -> throw({illformed_expression, "Argument not 0 or 1 -- CONS", N})
      end
   end.

car1(Z) -> Z(0).
cdr1(Z) -> Z(1).

% Exercise 2.4
cons2(X, Y) ->
   fun (M) ->
      M(X, Y)
   end.
car2(Z) ->
   Z(fun (P, Q) -> P end).
cdr2(Z) ->
   Z(fun (P, Q) -> Q end).

% Exercise 2.5
cons3(X, Y) ->
   math:pow(2, X * math:pow(3, Y)).
car3(Z) ->
   case Z rem 2 =:= 0 of
      true  -> car3((Z div 2) + 1);
      false -> 0
   end.
cdr3(Z) ->
   case Z rem 3 =:= 0 of
      true -> cdr3((Z div 3) + 1);
      false -> 0
   end.

% Exercise 2.6
add1(N) ->
   fun (F) ->
      fun (X) ->
         F((N(F))(X))
      end
   end.


% 2.1.4 Introduction to Data Abstraction - Extended Exercise: Interval Arithmetic
section_2_1_4() ->
   % Exercise 2.9
   I = make_interval(5.0, 10.0),
   J = make_interval(15.0, 25.0),
   print (width(I) + width(J)),

   % width of the sum (or difference) of two intervals *is* a function only of the widths of the intervals being added (or subtracted)
   print (width(add_interval(I, J))),
   print (width(sub_interval(I, J))),

   % width of the product (or quotient) of two intervals *is not* a function only of the widths of the intervals being multiplied (or divided)
   print (width(mul_interval(I, J))),
   print (width(div_interval(I, J))).

make_interval(A, B) -> {A, B}.
lower_bound({A, B}) -> A.
upper_bound({A, B}) -> B.

add_interval(X, Y) ->
   make_interval(lower_bound(X) + lower_bound(Y), upper_bound(X) + upper_bound(Y)).

min(X, Y) when X > Y -> Y;
min(X, Y) -> X.

max(X, Y) when X < Y -> Y;
max(X, Y) -> X.

mul_interval(X, Y) ->
   P1 = lower_bound(X) * lower_bound(Y),
   P2 = lower_bound(X) * upper_bound(Y),
   P3 = upper_bound(X) * lower_bound(Y),
   P4 = upper_bound(X) * upper_bound(Y),
   make_interval(
      min(min(P1, P2), min(P3, P4)),
      max(max(P1, P2), max(P3, P4))).

div_interval(X, Y) ->
   Z = make_interval(1.0 / upper_bound(Y), 1.0 / lower_bound(Y)),
   mul_interval(X, Z).

make_center_width(C, W) ->
   make_interval(C-W, C+W).

center(I) ->
   (lower_bound(I) + upper_bound(I)) / 2.0.

width(I) ->
   (upper_bound(I) - lower_bound(I)) / 2.0.

% parallel resistors
par1(R1, R2) ->
   div_interval(mul_interval(R1, R2), add_interval(R1, R2)).

par2(R1, R2) ->
   One = make_interval(1.0, 1.0),
   div_interval(
      One,
      add_interval(div_interval(One, R1),
                   div_interval(One, R2))).

% Exercise 2.7
make_interval_(A, B) -> {A, B}.
lower_bound_({A, B}) -> A.
upper_bound_({A, B}) -> B.

% Exercise 2.8
sub_interval(X, Y) ->
   make_interval(lower_bound(X) - lower_bound(Y), upper_bound(X) - upper_bound(Y)).

% Exercise 2.10
is_zero_interval(I) ->
   (lower_bound(I) =:= 0.0) orelse (upper_bound(I) =:= 0.0).
div_interval_zero_check(X, Y) ->
   case is_zero_interval(Y) of
      false -> div_interval(X, Y);
      true  -> throw({divide_by_zero, "Zero interval divisor"})
   end.

% Exercise 2.12
make_center_percent(C, P) ->
   make_center_width(C, P * C / 100.0).
percent(I) ->
   100.0 * width(I) / center(I).


% 2.2.1 Hierarchical Data and the Closure Property - Representing Sequences
section_2_2_1() ->
   One_Through_Four = [1,2,3,4],
   print (One_Through_Four),
   print (hd(One_Through_Four)),
   print (hd(tl(One_Through_Four))),
   print ([10|One_Through_Four]),

   Squares = [1, 4, 9, 16, 25],
   print (list_ref(Squares, 3)),

   Odds = [1, 3, 5, 7],
   print (length_1(Odds)),
   print (length_2(Odds)),

   print (append(Squares, Odds)),
   print (append(Odds, Squares)),

   print (scale_list(10, [1, 2, 3, 4, 5])),
   print (map(fun abs/1, [-10, 2.5, -11.6, 17])),
   print (map(fun (X) -> X*X end, [1, 2, 3, 4])),
   print (scale_list_(10, [1, 2, 3, 4, 5])),

   % Exercise 2.17
   print (last_pair([23, 72, 149, 34])),

   % Exercise 2.18
   print (reverse_1([1, 4, 9, 16, 25])),
   print (reverse_2([1, 4, 9, 16, 25])),

   % Exercise 2.19
   US_Coins = [50, 25, 10, 5, 1],
   print (cc(100, US_Coins)),
   UK_Coins = [100, 50, 20, 10, 5, 2, 1, 0.5],
   print (cc(100, UK_Coins)),

   % Exercise 2.20
   print (same_parity([1, 2, 3, 4, 5, 6, 7])),
   print (same_parity([2, 3, 4, 5, 6, 7])),

   % Exercise 2.21
   print (square_list_1([1, 2, 3, 4])),
   print (square_list_2([1, 2, 3, 4])),

   % Exercise 2.22
   print (square_list_3([1, 2, 3, 4])),
   print (square_list_4([1, 2, 3, 4])),
   print (square_list_5([1, 2, 3, 4])),

   % Exercise 2.23
   for_each([57, 321, 88], fun (X) -> print(X) end).

list_ref(Items, 0) -> hd(Items);
list_ref(Items, N) -> list_ref(tl(Items), N-1).

length_1([]) -> 0;
length_1(Items) -> 1 + length_1(tl(Items)).

length_2(Items) ->
   Length_Iter =
      fun (Self, [], Count) -> Count;
          (Self, A, Count)  -> Self(Self, tl(A), 1+(Count))
      end,
   Length_Iter(Length_Iter, Items, 0).

append([], List2)    -> List2;
append(List1, List2) -> [hd(List1) | append(tl(List1), List2)].

% Mapping over lists
scale_list(Factor, []) -> [];
scale_list(Factor, [H|T]) -> [H * Factor | scale_list(Factor, T)].

map(Proc, [])    -> [];
map(Proc, [H|T]) -> [Proc(H) | map(Proc, T)].

scale_list_(Factor, Items) ->
   map(fun (X) -> X * Factor end, Items).

% Exercise 2.17
last_pair(Items) ->
   Last_Pair_Iter =
      fun (Self, [], Prev)    -> Prev;
          (Self, [H|T], Prev) -> Self(Self, T, [H])
      end,
   Last_Pair_Iter(Last_Pair_Iter, Items, []).

% Exercise 2.18
reverse_1([])    -> [];
reverse_1([H|T]) -> append(reverse_1(T), [H]).

reverse_2(Items) ->
   Reverse_Iter =
      fun (Self, [], Accum)    -> Accum;
          (Self, [H|T], Accum) -> Self(Self, T, [H|Accum])
      end,
   Reverse_Iter(Reverse_Iter, Items, []).

% Exercise 2.19
no_more([])          -> true;
no_more(Coin_Values) -> false.
except_first_denomination(Coin_Values) -> tl(Coin_Values).
first_denomination(Coin_Values) -> hd(Coin_Values).
cc(0, Coin_Values)      -> 1;
cc(Amount, Coin_Values) ->
   case Amount < 0 orelse no_more(Coin_Values) of
      true  -> 0;
      false ->
         cc(Amount, except_first_denomination(Coin_Values)) +
         cc(Amount - (first_denomination(Coin_Values)), Coin_Values)
   end.

% Exercise 2.20
filter_1(Predicate, [])    -> [];
filter_1(Predicate, [H|T]) ->
   case Predicate(H) of
      true  -> [H|filter_1(Predicate, T)];
      false -> filter_1(Predicate, T)
   end.
is_odd(N) -> (N rem 2 =:= 1).
is_even(N) -> not(is_odd(N)).
same_parity(Items) ->
   Predicate =
      case is_odd(hd(Items)) of
         true  -> fun is_odd/1;
         false -> fun is_even/1
      end,
   filter_1(Predicate, tl(Items)).

% Exercise 2.21
square_list_1([]) -> [];
square_list_1([H|T]) -> [(H*H)|square_list_1(T)].
square_list_2(Items) ->
   map(fun (X) -> X*X end, Items).

% Exercise 2.22
square_list_3(Items) ->
   Iter =
      fun (Self, [], Answer)    -> Answer;
          (Self, [H|T], Answer) -> Self(Self, T, [square(H)|Answer])
      end,
   Iter(Iter, Items, []).
square_list_4(Items) ->
   Iter =
      fun (Self, [], Answer)    -> Answer;
          (Self, [H|T], Answer) -> Self(Self, T, Answer ++ [square(H)])
      end,
   Iter(Iter, Items, []).
square_list_5(Items) ->
   Iter =
      fun (Self, [], Answer)    -> Answer;
          (Self, [H|T], Answer) -> Self(Self, T, [square(H)|Answer])
      end,
   lists:reverse(Iter(Iter, Items, [])).

% Exercise 2.23
for_each([], F)    -> {};
for_each([H|T], F) ->
   F(H),
   for_each(T, F).


% 2.2.2 Hierarchical Data and the Closure Property - Hierarchical Structures
section_2_2_2() ->
   X = [[1, 2], [3, 4]],
   print (length(X)),
   print (count_leaves(X)),
   print (scale_tree([1, [2, [3, 4], 5], [6, 7]], 10)),
   print (scale_tree_2([1, [2, [3, 4], 5], [6, 7]], 10)),

   % Exercise 2.24
   print ([1, [2, [3, 4]]]),

   % Exercise 2.25
   print ([1, 3, [5, 7], 9]),
   print ([[7]]),
   print ([1, [2, [3, [4, [5, [6, 7]]]]]]),

   % Exercise 2.26
   X1 = [1, 2, 3],
   Y1 = [4, 5, 6],
   print (append(X1, Y1)),
   print ([X1|Y1]),
   print ([X1, Y1]),

   % Exercise 2.27
   print (X),
   print (lists:reverse(X)),
   print (deep_reverse(X)),

   % Exercise 2.28
   print (fringe(X)),
   print (fringe([X, X])),

   % Exercise 2.30
   print (square_tree([1, [2, [3, 4], 5], [6, 7]])),

   % Exercise 2.31
   print (square_tree_2([1, [2, [3, 4], 5], [6, 7]])),

   % Exercise 2.32
   print (subsets([1, 2, 3])).

count_leaves([])        -> 0;
count_leaves([[H|S]|T]) -> 1 + count_leaves(S) + count_leaves(T);
count_leaves([H|T])     -> 1 + count_leaves(T).

% Mapping over trees
scale_tree([], Factor)        -> [];
scale_tree([[H|S]|T], Factor) -> [[H * Factor | scale_tree(S, Factor)] | scale_tree(T, Factor)];
scale_tree([H|T], Factor)     -> [H * Factor | scale_tree(T, Factor)].

scale_tree_2(Tree, Factor) ->
   map(fun (X) when is_list(X) -> scale_tree_2(X, Factor);
           (X) -> X * Factor
       end,
       Tree).

% Exercise 2.27
deep_reverse([]) -> [];
deep_reverse([H|T]) when is_list(H) -> append(deep_reverse(T), [deep_reverse(H)]);
deep_reverse([H|T]) -> append(deep_reverse(T), [H]).

% Exercise 2.28
fringe([]) -> [];
fringe([H|T]) when is_list(H) -> append(fringe(H), fringe(T));
fringe([H|T]) -> [H|fringe(T)].

% Exercise 2.29
% List-based representation
% a.
make_mobile(Left, Right) -> [Left, Right].
make_branch(Length, Struc) -> [Length, Struc].
left_branch(Mobile) -> hd(Mobile).
right_branch(Mobile) -> hd(tl(Mobile)).
branch_length(Branch) -> hd(Branch).
branch_structure(Branch) -> hd(tl(Branch)).

% Helpers for b. and c.
branch_weight([]) -> 0;
branch_weight(Branch) ->
   case is_list(branch_structure(Branch)) of
      true  -> branch_weight(branch_structure(Branch));
      false -> branch_structure(Branch)
   end.
total_branch_length([]) -> 0;
total_branch_length(Branch) ->
   case is_list(branch_structure(Branch)) of
      true  -> branch_length(Branch) + total_branch_length(branch_structure(Branch));
      false -> branch_length(Branch)
   end.

% b.
total_weight(Mobile) ->
   branch_weight(left_branch(Mobile)) + branch_weight(right_branch(Mobile)).

% c. [Not as per specification]
is_mobile_balanced(Mobile) ->
   Lmwl = total_branch_length(left_branch(Mobile)) * branch_weight(left_branch(Mobile)),
   Rmwl = total_branch_length(right_branch(Mobile)) * branch_weight(right_branch(Mobile)),
   Lmwl =:= Rmwl.

% Exercise 2.30
square_tree([]) -> [];
square_tree([H|T]) when is_list(H) -> append([square_tree(H)], square_tree(T));
square_tree([H|T]) -> [(H*H)|square_tree(T)].

% Exercise 2.31
tree_map(Proc, []) -> [];
tree_map(Proc, [H|T]) when is_list(H) -> append([tree_map(Proc, H)], tree_map(Proc, T));
tree_map(Proc, [H|T]) -> [Proc(H)|tree_map(Proc, T)].
square_tree_2(Tree) ->
   tree_map(fun (X) -> X * X end, Tree).

% Exercise 2.32
subsets([]) -> [[]];
subsets([H|T]) ->
   Rest = subsets(T),
   append(Rest, map(fun (X) -> [H|X] end, Rest)).


% 2.2.3 Hierarchical Data and the Closure Property - Sequences as Conventional Interfaces
-record(employee, {name, jobtitle, salary}).

section_2_2_3() ->
   print (sum_odd_squares([1, 2, 3, 4, 5])),
   print (even_fibs(10)),

   % Sequence operations
   print (map(fun square/1, [1,2,3,4,5])),

   print (filter(fun is_odd/1, [1,2,3,4,5])),

   print (accumulate(fun erlang:'+'/2, 0, [1,2,3,4,5])),
   print (accumulate(fun erlang:'*'/2, 1, [1,2,3,4,5])),
   print (accumulate(fun (X,Y) -> [X|Y] end, [], [1,2,3,4,5])),

   print (enumerate_interval(2,7)),
   print (enumerate_tree([1, [2, [3, 4], 5]])),
   print (sum_odd_squares_2([1, 2, 3, 4, 5])),
   print (even_fibs_2(10)),
   print (list_fib_squares(10)),
   print (product_of_squares_of_odd_elements([1,2,3,4,5])),

   Recs = [#employee{name="Fred", jobtitle="Programmer", salary=180},
           #employee{name="Hank", jobtitle="Programmer", salary=150}],
   print (salary_of_highest_paid_programmer(Recs)),

   % Nested mappings
   N = 10,                   % book doesn't define N
   print (
      accumulate(
         fun (X,Y) -> [X|Y] end,
         [],
         map(
            fun (I) ->
               map(
                  fun (J) -> [I, J] end,
                  enumerate_interval(1, I-1)
               )
            end,
            enumerate_interval(1, N)))),

   % Exercise 2.38
   print (foldr(fun erlang:'/'/2, 1.0, [1.0,2.0,3.0])),
   print (foldl(fun erlang:'/'/2, 1.0, [1.0,2.0,3.0])),
   print (foldr(fun (X,Y) -> [X|Y] end, [], [1,2,3])),
   print (foldl(fun (X,Y) -> [X|Y] end, [], [1,2,3])).

% same as above
% is_odd(N) -> N rem 2 =:= 1.
% is_even(N) -> not(is_odd(N)).
% square(X) -> X * X.

sum_odd_squares([]) -> 0;
sum_odd_squares([[H|S]|T]) -> sum_odd_squares([H|S]) + sum_odd_squares(T);
sum_odd_squares([H|T]) ->
   case is_odd(H) of
      true  -> square(H) + sum_odd_squares(T);
      false -> sum_odd_squares(T)
   end.

even_fibs(N) ->
   Next =
      fun (Self, K) ->
         case K > N of
            true  -> [];
            false ->
               F = fib(K),
               case is_even(F) of
                  true  -> [F|Self(Self, K+1)];
                  false -> Self(Self, K+1)
               end
         end
      end,
   Next(Next, 0).

% Sequence operations
filter(Predicate, []) -> [];
filter(Predicate, [H|T]) ->
   case Predicate(H) of
      true  -> [H | filter(Predicate, T)];
      false -> filter(Predicate, T)
   end.

accumulate(Oper, Initial, [])    -> Initial;
accumulate(Oper, Initial, [H|T]) -> Oper(H, accumulate(Oper, Initial, T)).

enumerate_interval(Low, High) when Low > High -> [];
enumerate_interval(Low, High) -> [Low|enumerate_interval(Low+1, High)].

enumerate_tree([])        -> [];
enumerate_tree([[H|S]|T]) -> append(enumerate_tree([H|S]), enumerate_tree(T));
enumerate_tree([H|T])     -> [H|enumerate_tree(T)].

sum_odd_squares_2(Tree) ->
   accumulate(fun erlang:'+'/2, 0, map(fun square/1, filter(fun is_odd/1, enumerate_tree(Tree)))).

even_fibs_2(N) ->
   accumulate(fun (X,Y) -> [X|Y] end, [], filter(fun is_even/1, map(fun fib/1, enumerate_interval(0, N)))).

list_fib_squares(N) ->
   accumulate(fun (X,Y) -> [X|Y] end, [], map(fun square/1, map(fun fib/1, enumerate_interval(0, N)))).

product_of_squares_of_odd_elements(Sequence) ->
   accumulate(fun erlang:'*'/2, 1, map(fun square/1, filter(fun is_odd/1, Sequence))).

isProgrammer(#employee{_=_, jobtitle=JobTitle, _=_}) -> JobTitle =:= "Programmer".
salary(#employee{_=_, _=_, salary=Salary}) -> Salary.
salary_of_highest_paid_programmer(Records) ->
   accumulate(fun max/2, 0, map(fun salary/1, filter(fun isProgrammer/1, Records))).

% Nested mappings
flatmap(Proc, Seq) ->
   accumulate(fun append/2, [], map(Proc, Seq)).

has_no_divisors(N, 1) -> true;
has_no_divisors(N, C) ->
   case N rem C =:= 0 of
      true  -> false;
      false -> has_no_divisors(N, C-1)
   end.

is_prime(N) -> has_no_divisors(N, N-1).

prime_sum(X, Y) -> is_prime(X + Y).

make_pair_sum(X, Y) -> {X, Y, X+Y}.

prime_sum_pairs(N) ->
   map(
      fun make_pair_sum/2,
      filter(
         fun prime_sum/2,
         flatmap(
            fun (I) ->
               map(
                  fun (J) -> {I, J} end,
                  enumerate_interval(1, I-1)
               )
            end,
            enumerate_interval(1, N)))).

remove(Item, Sequence) ->
   filter(fun (X) -> X =/= Item end, Sequence).

permutations([]) -> [[]];
permutations(S) ->
   flatmap(
      fun (X) ->
         map(
            fun (P) -> [X|P] end,
            permutations(remove(X, S)))
      end,
      S).

% Exercise 2.34
% exercise left to reader to define appropriate functions
% horner_eval(X, Coefficient_Sequence) ->
%    accumulate(fun (This_Coeff, Higher_Terms) -> ??FILL_THIS_IN?? end, 0, Coefficient_Sequence).
% horner_eval(2, [1,3,0,5,0,1]).

% Exercise 2.36
% exercise left to reader to define appropriate functions
% accumulate_n(Oper, Init, []) -> [];
% accumulate_n(Oper, Init, Seqs) ->
%    [accumulate(Oper, Init, ??FILL_THIS_IN??) |
%       accumulate_n(Oper, Init, ??FILL_THIS_IN??)].
% accumulate_n(fun erlang:'+'/2, 0, S).

% Exercise 2.38
foldr(Oper, Initial, Sequence) -> accumulate(Oper, Initial, Sequence).
foldl(Oper, Initial, Sequence) ->
   Iter =
      fun (Self, Result, [])    -> Result;
          (Self, Result, [H|T]) -> Self(Self, Oper(Result, H), T)
      end,
      Iter(Iter, Initial, Sequence).

% Exercise 2.42
% exercise left to reader to define appropriate functions
% queens(Board_Size) ->
%    Queen_Cols =
%       fun (Self, 0) -> [Empty_Board];
%           (Self, K) ->
%             filter(
%                fun (Positions) -> is_safe(K, Positions) end,
%                flatmap(
%                   fun (Rest_Of_Queens) ->
%                      map(
%                         fun (New_Row) -> adjoin_position(New_Row, K, Rest_Of_Queens) end,
%                         enumerate_interval(1, Board_Size))
%                   end
%                   Self(Self, (K-1)))
%       end,
%    Queen_Cols(Board_Size).

% Exercise 2.43 *)
% exercise left to reader to define appropriate functions
% queens(Board_Size) ->
%    Queen_Cols =
%       fun (Self, 0) -> [Empty_Board];
%           (Self, K) ->
%             filter(
%                fun (Positions) -> is_safe(K, Positions) end,
%                flatmap(
%                   fun (New_Row) ->
%                      map(
%                         fun (Rest_Of_Queens) -> adjoin_position(New_Row, K, Rest_Of_Queens) end,
%                         queen_cols(K-1))
%                   end,
%                   enumerate_interval(1, Board_Size)))
%       end,
%    Queen_Cols(Board_Size).


% 2.2.4 Hierarchical Data and the Closure Property - Example: a picture language
section_2_2_4() ->
   A_Frame = make_frame(make_vect(0.0, 0.0), make_vect(1.0, 0.0), make_vect(0.0, 1.0)),
   _ = (frame_coord_map(A_Frame))(make_vect(0.0, 0.0)),
   _ = origin_frame(A_Frame),

   % print (flip_vert(fun wave/1)),
   Wave2 = beside(fun wave/1, flip_vert(fun wave/1)),
   Wave4 = below(wave2, fun wave/1),
   Wave4_ = flipped_pairs(fun wave/1),

   % footnote
   Flipped_Pairs = square_of_four(fun identity/1, fun flip_vert/1, fun identity/1, fun flip_vert/1).

% these two routines are to be written
draw_line(X, Y) -> {}.
wave(Xframe) -> Xframe.

-record(vect, {x, y}).
make_vect(X, Y) -> #vect{x=X, y=Y}.
xcor_vect(#vect{x=X, _=_}) -> X.
ycor_vect(#vect{_=_, y=Y}) -> Y.
add_vect(V1, V2) -> make_vect(xcor_vect(V1) + xcor_vect(V2), ycor_vect(V1) + ycor_vect(V2)).
sub_vect(V1, V2) -> make_vect(xcor_vect(V1) - xcor_vect(V2), ycor_vect(V1) - ycor_vect(V2)).
scale_vect(S, V) -> make_vect(S * xcor_vect(V), S * ycor_vect(V)).

-record(frame, {origin, edge1, edge2}).
make_frame(Origin, Edge1, Edge2) -> #frame{origin=Origin, edge1=Edge1, edge2=Edge2}.
origin_frame(#frame{origin=Origin, _=_, _=_}) -> Origin.
edge1_frame(#frame{_=_, edge1=Edge1, _=_}) -> Edge1.
edge2_frame(#frame{_=_, _=_, edge2=Edge2}) -> Edge2.

-record(segment, {x, y}).
start_segment(#segment{x=X, _=_}) -> X.
end_segment(#segment{_=_, y=Y}) -> Y.

% Frames
frame_coord_map(Xframe) ->
   fun (V) ->
      add_vect(
         origin_frame(Xframe),
         add_vect(scale_vect(xcor_vect(V), edge1_frame(Xframe)),
                  scale_vect(ycor_vect(V), edge2_frame(Xframe))))
   end.

% Painters
foreach(F, []) -> {};
foreach(F, [H|T]) ->
   F(H),
   foreach(F, T).

segments_painter(Segment_List) ->
   fun (Xframe) ->
      foreach(
         fun (Segment) ->
            draw_line(
               (frame_coord_map(Xframe))(start_segment(Segment)),
               (frame_coord_map(Xframe))(end_segment(Segment)))
         end,
         Segment_List)
   end.

transform_painter(Painter, Origin, Corner1, Corner2) ->
   fun (Xframe) ->
      M = frame_coord_map(Xframe),
      New_Origin = M(Origin),
      Painter(
         make_frame(
            New_Origin,
            sub_vect(M(Corner1), New_Origin),
            sub_vect(M(Corner2), New_Origin)))
   end.

flip_vert(Painter) ->
   transform_painter(
      Painter,
      make_vect(0.0, 1.0),
      make_vect(1.0, 1.0),
      make_vect(0.0, 0.0)).

flip_horiz(Painter) ->
   transform_painter(
      Painter,
      make_vect(1.0, 0.0),
      make_vect(0.0, 0.0),
      make_vect(1.0, 1.0)).

shrink_to_upper_right(Painter) ->
   transform_painter(
       Painter,
       make_vect(0.5, 0.5),
       make_vect(1.0, 0.5),
       make_vect(0.5, 1.0)).

rotate90(Painter) ->
   transform_painter(
      Painter,
      make_vect(1.0, 0.0),
      make_vect(1.0, 1.0),
      make_vect(0.0, 0.0)).

rotate180(Painter) ->
   transform_painter(
      Painter,
      make_vect(1.0, 1.0),
      make_vect(0.0, 1.0),
      make_vect(1.0, 0.0)).

squash_inwards(Painter) ->
   transform_painter(
      Painter,
      make_vect(0.0, 0.0),
      make_vect(0.65, 0.35),
      make_vect(0.35, 0.65)).

beside(Painter1, Painter2) ->
   fun (Xframe) ->
      Split_Point = make_vect(0.5, 0.0),
      Paint_Left =
         transform_painter(
            Painter1,
            make_vect(0.0, 0.0),
            Split_Point,
            make_vect(0.0, 1.0)),
      Paint_Right =
         transform_painter(
            Painter2,
            Split_Point,
            make_vect(1.0, 0.0),
            make_vect(0.5, 1.0)),
      Paint_Left(Xframe),
      Paint_Right(Xframe)
   end.

below(Painter1, Painter2) ->
   fun (Xframe) ->
      Split_Point = make_vect(0.0, 0.5),
      Paint_Below =
         transform_painter(
            Painter1,
            make_vect(0.0, 0.0),
            make_vect(1.0, 0.0),
            Split_Point),
      Paint_Above =
         transform_painter(
            Painter2,
            Split_Point,
            make_vect(1.0, 0.5),
            make_vect(0.0, 1.0)),
      Paint_Below(Xframe),
      Paint_Above(Xframe)
   end.

up_split(Painter, 0) -> Painter;
up_split(Painter, N) ->
   Smaller = up_split(Painter, N-1),
   below(Painter, beside(Smaller, Smaller)).

flipped_pairs(Painter) ->
   Painter2 = beside(Painter, flip_vert(Painter)),
   below(Painter2, Painter2).

right_split(Painter, 0) -> Painter;
right_split(Painter, N) ->
   Smaller = right_split(Painter, N-1),
   beside(Painter, below(Smaller, Smaller)).

corner_split(Painter, 0) -> Painter;
corner_split(Painter, N) ->
   Up = up_split(Painter, N-1),
   Right = right_split(Painter, N-1),
   Top_Left = beside(Up, Up),
   Bottom_Right = below(Right, Right),
   Corner = corner_split(Painter, N-1),
   beside(below(Painter, Top_Left), below(Bottom_Right, Corner)).

square_limit(Painter, N) ->
   Quarter = corner_split(Painter, N),
   Half = beside(flip_horiz(Quarter), Quarter),
   below(flip_vert(Half), Half).

% Higher_order operations
square_of_four(Tleft, Tright, Bleft, Bright) ->
   fun (Painter) ->
      Top = beside(Tleft(Painter), Tright(Painter)),
      Bottom = beside(Bleft(Painter), Bright(Painter)),
      below(Bottom, Top)
   end.

flipped_pairs_(Painter) ->
   Combine4 = square_of_four(fun identity/1, fun flip_vert/1, fun identity/1, fun flip_vert/1),
   Combine4(Painter).

square_limit_(Painter, N) ->
   Combine4 = square_of_four(fun flip_horiz/1, fun identity/1, fun rotate180/1, fun flip_vert/1),
   Combine4(corner_split(Painter, N)).

% Exercise 2.45
% exercise left to reader to define appropriate functions
% Right_Split = split(fun beside/2, fun below/2).
% Up_Split = split(fun below/2, fun beside/2)

% Exercise 2.47
make_frame_2(Origin, Edge1, Edge2) -> [Origin, Edge1, Edge2].
make_frame_3(Origin, Edge1, Edge2) -> [Origin, [Edge1, Edge2]].

% 2.3.1 Symbolic Data - Quotation

% To Be Done.
section_2_3_1() ->
   io:format("~n").


% 2.3.2 Symbolic Data - Example: Symbolic Differentiation
section_2_3_2() ->
   % dx(x + 3) = 1
   print (deriv({sum, x, 3}, x)),

   % dx(x*y) = y
   print (deriv({product, x, y}, x)),

   % dx(x*y + x + 3) = y + 1
   print (deriv({sum, {sum, {product, x, y}, x}, 3}, x)),

   % dx(x + 3) = 1
   print (deriv1({sum, x, 3}, x)),

   % dx(x*y) = y
   print (deriv1({product, x, y}, x)),

   % dx(x*y + x + 3) = y + 1
   print (deriv1({sum, {sum, {product, x, y}, x}, 3}, x)).

is_same_number(X, Y) ->
   is_number(X) andalso is_number(Y) andalso X == Y.

is_variable(X) ->
   is_atom(X).

is_same_variable(X, Y) ->
   is_variable(X) andalso is_variable(Y) andalso X =:= Y.

is_sum({sum, _, _}) -> true;
is_sum(_) -> false.

is_product({product, _, _}) -> true;
is_product(_) -> false.

make_sum(X, Y) ->
   case is_number(X) andalso is_number(Y) of
      true  -> X + Y;
      false -> {sum, X, Y}
   end.

make_product(X, Y) ->
   case is_number(X) andalso is_number(Y) of
      true  -> X * Y;
      false -> {product, X, Y}
   end.

add_end({sum, X, _}) -> X;
add_end(L) -> throw({invalid, "Invalid pattern match", L}).

aug_end({sum, _, Y}) -> Y;
aug_end(L) -> throw({invalid, "Invalid pattern match", L}).

multiplier({product, X, _}) -> X;
multiplier(L) -> throw({invalid, "Invalid pattern match", L}).

multiplicand({product, _, Y}) -> Y;
multiplicand(L) -> throw({invalid, "Invalid pattern match", L}).

deriv(Exp, Var) ->
   case is_number(Exp) of
      true  -> 0;
      false ->
         case is_variable(Exp) of
            true ->
               case is_same_variable(Exp, Var) of
                  true  -> 1;
                  false -> 0
               end;
            false ->
               case is_sum(Exp) of
                  true  ->
                     make_sum(
                        deriv(add_end(Exp), Var),
                        deriv(aug_end(Exp), Var));
                  false ->
                     case is_product(Exp) of
                        true  ->
                           make_sum(
                              make_product(multiplier(Exp), deriv(multiplicand(Exp), Var)),
                              make_product(deriv(multiplier(Exp), Var), multiplicand(Exp)));
                        false -> throw({invalid, "Invalid expression", Exp})
                     end
               end
         end
   end.

% With simplification
make_sum1(X, Y) ->
   if
      is_number(X) andalso X == 0 -> Y;
      is_number(Y) andalso Y == 0 -> X;
      is_number(X) andalso is_number(Y) -> X + Y;
      true -> {sum, X, Y}
   end.

make_product1(X, Y) ->
   if
      is_number(X) andalso X == 0 -> 0;
      is_number(Y) andalso Y == 0 -> 0;
      is_number(X) andalso X == 1 -> Y;
      is_number(Y) andalso Y == 1 -> X;
      is_number(X) andalso is_number(Y) -> X * Y;
      true -> {product, X, Y}
   end.

deriv1(Exp, Var) ->
   case is_number(Exp) of
      true  -> 0;
      false ->
         case is_variable(Exp) of
            true ->
               case is_same_variable(Exp, Var) of
                  true  -> 1;
                  false -> 0
               end;
            false ->
               case is_sum(Exp) of
                  true  ->
                     make_sum1(
                        deriv1(add_end(Exp), Var),
                        deriv1(aug_end(Exp), Var));
                  false ->
                     case is_product(Exp) of
                        true  ->
                           make_sum1(
                              make_product1(multiplier(Exp), deriv1(multiplicand(Exp), Var)),
                              make_product1(deriv1(multiplier(Exp), Var), multiplicand(Exp)));
                        false -> throw({invalid, "Invalid expression", Exp})
                     end
               end
         end
   end.


% 2.3.3 Symbolic Data - Example: Representing Sets
section_2_3_3() ->
   print (is_element_of_set2(3, {tree, 2, {tree, 1, {leaf}, {leaf}} , {tree, 3, {leaf}, {leaf}}})),

   print (adjoin_set2(3, {tree, 4, {tree, 2, {leaf}, {leaf}}, {tree, 6, {leaf}, {leaf}}})),

   % Exercise 2.63
   print (tree_to_list1({tree, 4, {tree, 2, {leaf}, {leaf}}, {tree, 6, {leaf}, {leaf}}})),
   print (tree_to_list2({tree, 4, {tree, 2, {leaf}, {leaf}}, {tree, 6, {leaf}, {leaf}}})),

   % Exercise 2.64
   print (list_to_tree([2, 4, 6])).

% unordered
is_element_of_set(X, []) -> false;
is_element_of_set(X, [H|T]) when X =:= H -> true;
is_element_of_set(X, [H|T]) -> is_element_of_set(X, T).

adjoin_set(X, Set) ->
   case is_element_of_set(X, Set) of
      true  -> Set;
      false -> [X|Set]
   end.

intersection_set([], _) -> [];
intersection_set(_, []) -> [];
intersection_set([H|T], Set2) ->
   case is_element_of_set(H, Set2) of
      true  -> [H|intersection_set(T, Set2)];
      false -> intersection_set(T, Set2)
   end.

% ordered
is_element_of_set1(_, []) -> false;
is_element_of_set1(X, [H|T]) when X =:= H -> true;
is_element_of_set1(X, [H|T]) when X < H -> false;
is_element_of_set1(X, [H|T]) -> is_element_of_set1(X, T).

intersection_set1([], _) -> [];
intersection_set1(_, []) -> [];
intersection_set1([X|Xs], [Y, Ys]) when X =:= Y -> [X|intersection_set1(Xs, Ys)];
intersection_set1([X|Xs], [Y, Ys]) when X < Y -> intersection_set1(Xs, [Y|Ys]);
intersection_set1([X|Xs], [Y, Ys]) -> intersection_set1([X|Xs], Ys).

is_element_of_set2(X, {leaf}) -> false;
is_element_of_set2(X, {tree, Y, _, _}) when X =:= Y -> true;
is_element_of_set2(X, {tree, Y, Left, _}) when X < Y -> is_element_of_set2(X, Left);
is_element_of_set2(X, {tree, Y, _, Right}) -> is_element_of_set2(X, Right).

adjoin_set2(X, {leaf}) -> {tree, X, {leaf}, {leaf}};
adjoin_set2(X, {tree, Y, Left, Right}) when X =:= Y -> {tree, Y, Left, Right};
adjoin_set2(X, {tree, Y, Left, Right}) when X < Y -> {tree, Y, adjoin_set2(X, Left), Right};
adjoin_set2(X, {tree, Y, Left, Right}) -> {tree, Y, Left, adjoin_set2(X, Right)}.

% Exercise 2.63
tree_to_list1({leaf}) -> [];
tree_to_list1({tree, Y, Left, Right}) ->
   append(tree_to_list1(Left), [Y|tree_to_list1(Right)]).

tree_to_list2(Node) ->
   Copy_To_List =
      fun (Self, {leaf}, L) -> L;
          (Self, {tree, X, Left, Right}, L) -> Self(Self, Left, [X|Self(Self, Right, L)])
      end,
   Copy_To_List(Copy_To_List, Node, []).

% Exercise 2.64
partial_tree(Elts, 0) -> {{leaf}, Elts};
partial_tree(Elts, N) ->
   LeftSize = (N-1) div 2,
   RightSize = N - (LeftSize + 1),
   LeftResult = partial_tree(Elts, LeftSize),
   {LeftTree, NonLeftElts} = LeftResult,
   ThisEntry = hd(NonLeftElts),
   RightResult = partial_tree(tl(NonLeftElts), RightSize),
   {RightTree, RemainingElts} = RightResult,
   {{tree, ThisEntry, LeftTree, RightTree}, RemainingElts}.

list_to_tree(Elements) ->
   {Result, _} = partial_tree(Elements, length(Elements)),
   Result.

% information retrieval
lookup(GivenKey, [{information, Key, Name, Age}|_]) when GivenKey =:= Key -> {information, Key, Name, Age};
lookup(GivenKey, [_|T]) -> lookup(GivenKey, T);
lookup(_, L) -> throw({invalid, "Invalid pattern match", L}).


%   (*** 2.3.4 Symbolic Data - Example: Huffman Encoding Trees ***)
section_2_3_4() ->
   % Exercise 2.67
   SampleTree =
      make_code_tree(
         make_leaf('A', 4),
         make_code_tree(
            make_leaf('B', 2),
            make_code_tree(
               make_leaf('D', 1),
               make_leaf('C', 1)))),
   SampleMessage = [0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0],
   print (decode(SampleMessage, SampleTree)).

make_leaf(Symbol, Weight) ->
   {leaf, Symbol, Weight}.

is_leaf({leaf, _, _}) -> true;
is_leaf(_) -> false.

symbol_leaf({leaf, Symbol, _}) -> Symbol;
symbol_leaf(Node) -> throw({invalid, "Invalid pattern match", Node}).

weight_leaf({leaf, _, Weight}) -> Weight;
weight_leaf(Node) -> throw({invalid, "Invalid pattern match", Node}).

symbols({leaf, Symbol, _}) -> [Symbol];
symbols({tree, SubSymbols, _, _, _}) -> SubSymbols.

weight({leaf, _, Weight}) -> Weight;
weight({tree, _, Weight, _, _}) -> Weight.

make_code_tree(Left, Right) ->
   {tree,
      append(symbols(Left), symbols(Right)),
      weight(Left) + weight(Right),
      Left,
      Right}.

left_node({tree, _, _, Left, _}) -> Left;
left_node(Node) -> throw({invalid, "Invalid pattern match", Node}).

right_node({tree, _, _, _, Right}) -> Right;
right_node(Node) -> throw({invalid, "Invalid pattern match", Node}).

choose_node(0, Node) -> left_node(Node);
choose_node(1, Node) -> right_node(Node);
choose_node(N, _) -> throw({invalid, "Invalid pattern match", N}).

% decoding
decode(Bits, Tree) ->
   Decode =
      fun (Self, [], _) -> [];
          (Self, [H|T], CurrentNode) ->
            NextNode = choose_node(H, CurrentNode),
            case is_leaf(NextNode) of
               true  -> [symbol_leaf(NextNode) | Self(Self, T, Tree)];
               false -> Self(Self, T, NextNode)
            end
      end,
   Decode(Decode, Bits, Tree).

% sets
adjoin_set3(X, Set) ->
   case Set of
      [] -> [X];
      [H|T] ->
         case weight(X) < weight(H) of
            true  -> [X|Set];
            false -> [H|adjoin_set3(X, T)]
         end
   end.

make_leaf_set([{leaf, Symbol, Weight}|Pairs]) -> adjoin_set3(make_leaf(Symbol, Weight), make_leaf_set(Pairs));
make_leaf_set(Node) -> throw({invalid, "Invalid pattern match", Node}).

% Exercise 2.68
% exercise left to reader to define appropriate functions
% encode([], Tree) -> [];
% encode([H|T], Tree) -> append(encode_symbol(H, Tree), encode(T, Tree)).


% 2.4.1 Multiple Representations for Abstract Data - Representations for Complex Numbers
section_2_4_1() ->
   Z = {1.0, 2.0},
   print (make_from_real_imag(real_part(Z), imag_part(Z))),
   print (make_from_mag_ang(magnitude(Z), angle(Z))).

% Same as above
% fun {Square X} X * X end

% Rectangular
real_part_r({Real, _}) -> Real.
imag_part_r({_, Imag}) -> Imag.

magnitude_r(Z) ->
   math:sqrt(square(real_part_r(Z)) + square(imag_part_r(Z))).

angle_r(Z) ->
   math:atan2(imag_part_r(Z), real_part_r(Z)).

make_from_real_imag_r(X, Y) -> {X, Y}.
make_from_mag_ang_r(R, A) ->
   {R * math:cos(A), R * math:sin(A)}.

% polar
magnitude_p({Mag, _}) -> Mag.
angle_p({_, Ang}) -> Ang.

real_part_p(Z) ->
   magnitude_p(Z) * math:cos(angle_p(Z)).

imag_part_p(Z) ->
   magnitude_p(Z) * math:sin(angle_p(Z)).

make_from_real_imag_p(X, Y) ->
   {math:sqrt(square(X) + square(Y)), math:atan2(Y, X)}.

make_from_mag_ang_p(R, A) -> {R, A}.

% using the abstract type
magnitude(Z) -> magnitude_p(Z).
angle(Z) -> angle_p(Z).
real_part(Z) -> real_part_p(Z).
imag_part(Z) -> imag_part_p(Z).
make_from_real_imag(X, Y) -> make_from_real_imag_p(X, Y).
make_from_mag_ang(R, A) -> make_from_mag_ang_p(R, A).

add_complex(Z1, Z2) ->
   make_from_real_imag(
      real_part(Z1) + real_part(z2),
      imag_part(Z1) + imag_part(Z2)).

sub_complex(Z1, Z2) ->
   make_from_real_imag(
      real_part(Z1) - real_part(Z2),
      imag_part(Z1) - imag_part(Z2)).

mul_complex(Z1, Z2) ->
   make_from_mag_ang(
      magnitude(Z1) * magnitude(Z2),
      angle(Z1) + angle(Z2)).

div_complex(Z1, Z2) ->
   make_from_mag_ang(
      magnitude(Z1) / magnitude(z2),
      angle(Z1) - angle(Z2)).

%   %%% 2.4.2 Multiple Representations for Abstract Data - Tagged Data %%%
section_2_4_2() ->
   print (add_complex_g(make_from_real_imag_g(3.0, 4.0), make_from_real_imag_g(3.0, 4.0))).

attach_tag({TypeTag, Contents}) -> {TypeTag, Contents}.

type_tag({rectangular, _}) -> rectangular;
type_tag({polar, _})       -> polar;
type_tag({A, _})           -> throw({invalid, "Invalid pattern match", A}).

contents({rectangular, X}) -> X;
contents({polar, X})       -> X;
contents({A, _})           -> throw({invalid, "Invalid pattern match", A}).

is_rectangular({rectangular, _}) -> true;
is_rectangular(_) -> false.

is_polar({polar, _}) -> true;
is_polar(_) -> false.

% Rectangular
make_from_real_imag_rectangular(X, Y) ->
   {rectangular, {X, Y}}.
make_from_mag_ang_rectangular(R, A) ->
   {rectangular, {R*math:cos(A), R*math:sin(A)}}.

real_part_rectangular({rectangular, {X, _}}) -> X.
imag_part_rectangular({rectangular, {_, Y}}) -> Y.

magnitude_rectangular(Z) ->
   math:sqrt(square(real_part_rectangular(Z)) +
             square(imag_part_rectangular(Z))).

angle_rectangular(Z) ->
   math:atan2(imag_part_rectangular(Z), real_part_rectangular(Z)).

% Polar
make_from_real_imag_polar(X, Y) ->
   {polar, {math:sqrt(square(X) + square(Y)), math:atan2(Y, X)}}.
make_from_mag_ang_polar(R, A) ->
   {polar, {R, A}}.

magnitude_polar({polar, {X, _}}) -> X.
angle_polar({polar, {_, Y}}) -> Y.

real_part_polar(Z) ->
   magnitude_polar(Z) * math:cos(angle_polar(Z)).
imag_part_polar(Z) ->
   magnitude_polar(Z) * math:sin(angle_polar(Z)).

% Generic selectors
real_part_g({rectangular, Z}) -> real_part_rectangular({rectangular, Z});
real_part_g({polar, Z}) -> real_part_polar({polar, Z});
real_part_g(A) -> throw({invalid, "Invalid pattern match", A}).

imag_part_g({rectangular, Z}) -> imag_part_rectangular({rectangular, Z});
imag_part_g({polar, Z}) -> imag_part_polar({polar, Z});
imag_part_g(A) -> throw({invalid, "Invalid pattern match", A}).

magnitude_g({rectangular, Z}) -> magnitude_rectangular({rectangular, Z});
magnitude_g({polar, Z}) -> magnitude_rectangular({polar, Z});
magnitude_g(A) -> throw({invalid, "Invalid pattern match", A}).

angle_g({rectangular, Z}) -> angle_rectangular({rectangular, Z});
angle_g({polar, Z}) -> angle_polar({polar, Z});
angle_g(A) -> throw({invalid, "Invalid pattern match", A}).

% Constructors for complex numbers
make_from_real_imag_g(X, Y) ->
   make_from_real_imag_rectangular(X, Y).
make_from_mag_ang_g(R, A) ->
   make_from_mag_ang_polar(R, A).

% same as before
add_complex_g(Z1, Z2) ->
   make_from_real_imag_g(
      real_part_g(Z1) + real_part_g(Z2),
      imag_part_g(Z1) + imag_part_g(Z2)).

sub_complex_g(Z1, Z2) ->
   make_from_real_imag_g(
      real_part_g(Z1) - real_part_g(Z2),
      imag_part_g(Z1) - imag_part_g(Z2)).

mul_complex_g(Z1, Z2) ->
   make_from_mag_ang_g(
      magnitude_g(Z1) * magnitude_g(Z2),
      angle_g(Z1) + angle_g(Z2)).

div_complex_g(Z1, Z2) ->
   make_from_mag_ang_g(
      magnitude_g(Z1) / magnitude_g(z2),
      angle_g(Z1) - angle_g(Z2)).


% 2.4.3 Multiple Representations for Abstract Data - Data-Directed Programming and Additivity

% To Be Done.
section_2_4_3() ->
   io:format("~n").


% 2.5.1 Systems with Generic Operations - Generic Arithmetic Operations

% To Be Done.
section_2_5_1() ->
   io:format("~n").

Chris Rathman / Chris.Rathman@tx.rr.com