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 #03 Examples in Erlang

-module(sicp03).
%-export([start/0]).
-compile(export_all).

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

start() ->
   section_3_1_1(),
   section_3_1_2(),
   section_3_1_3(),
   section_3_2_1(),
   section_3_2_2(),
   section_3_2_3(),
   section_3_2_4(),
   section_3_3_1(),
   section_3_3_2(),
   section_3_3_3(),
   section_3_3_4(),
   section_3_3_5(),
   section_3_4_1(),
   section_3_4_2(),
   section_3_5_1().

% Functions used to mimic mutable cells
new_cell(X) -> spawn(fun() -> cell(X) end).
cell(Value) ->
   receive
      {set, NewValue} -> cell(NewValue);
      {get, Pid}      -> Pid!{return, Value}, cell(Value);
      {dispose}       -> {}
   end.
set_cell(Cell, NewValue) -> Cell!{set, NewValue}.
get_cell(Cell) ->
   Cell!{get, self()},
   receive
      {return, Value} -> Value
   end.
dispose_cell(Cell) -> Cell!{dispose}.

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

square(X) -> X * X.

average(X, Y) -> (X + Y) / 2.

has_no_divisors(_, 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).

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


% 3.1.1 - Assignment and Local State - Local State Variables
-record(account_record, {withdraw, deposit, balance}).

section_3_1_1() ->
   Balance = new_cell(100),
   withdraw(Balance, 25),
   withdraw(Balance, 25),
   try withdraw(Balance, 60) catch {insufficientFunds, _} -> skip end,
   withdraw(Balance, 15),
   print (get_cell(Balance)),

   W1 = make_withdraw(100),
   W2 = make_withdraw(100),
   W1(50),
   W2(70),
   try W2(40) catch {insufficientFunds, _} -> skip end,
   W1(40),

   Acc = make_account(100),
   (Acc#account_record.withdraw)(50),
   try (Acc#account_record.withdraw)(60) catch {insufficientFunds, _} -> skip end,
   (Acc#account_record.deposit)(40),
   (Acc#account_record.withdraw)(60),
   print ((Acc#account_record.balance)()),

   Acc2 = make_account(100).

withdraw(Balance, Amount) ->
   case get_cell(Balance) >= Amount of
      true  -> set_cell(Balance, get_cell(Balance) - Amount);
      false -> throw ({insufficientFunds, get_cell(Balance)})
   end.

new_withdraw() ->
   Balance = new_cell(100),
   fun (Amount) ->
      case get_cell(Balance) >= Amount of
         true  -> set_cell(Balance, get_cell(Balance) - Amount);
         false -> throw ({insufficientFunds, get_cell(Balance)})
      end
   end.

make_withdraw(Initial_Balance) ->
   Balance = new_cell(Initial_Balance),
   fun (Amount) ->
      case get_cell(Balance) >= Amount of
         true  -> set_cell(Balance, get_cell(Balance) - Amount);
         false -> throw ({insufficientFunds, get_cell(Balance)})
      end
   end.

make_account(Initial_Balance) ->
   Balance = new_cell(Initial_Balance),
   Withdraw =
      fun (Amount) ->
         case get_cell(Balance) >= Amount of
            true  -> set_cell(Balance, get_cell(Balance) - Amount);
            false -> throw ({insufficientFunds, get_cell(Balance)})
         end
      end,
   Deposit =
      fun (Amount) ->
         set_cell(Balance, get_cell(Balance) + Amount)
      end,
   GetBalance =
      fun () ->
         get_cell(Balance)
      end,
   #account_record{withdraw = Withdraw, deposit = Deposit, balance = GetBalance}.


% 3.1.2 - Assignment and Local State - The Benefits of Introducing Assignment
section_3_1_2() ->
   print (estimate_pi(10)).

rand_update(X) ->
   A = 27,
   B = 26,
   M = 127,
   (A*X + B) rem M.

rand(Random) ->
   X = get_cell(Random),
   set_cell(Random, rand_update(X)),
   X.

cesaro_test() ->
   Random = new_cell(7),
   fun () ->
      gcd(rand(Random), rand(Random)) == 1
   end.

monte_carlo(Trials, Experiment) ->
   Iter =
      fun (Self, 0, TrialsPassed) -> TrialsPassed / Trials;
          (Self, TrialsRemaining, TrialsPassed) ->
               case (Experiment()) of
                  true  -> Self(Self, TrialsRemaining-1, TrialsPassed+1);
                  false -> Self(Self, TrialsRemaining-1, TrialsPassed)
               end
      end,
   Iter(Iter, Trials, 0).

estimate_pi(Trials) ->
   math:sqrt(6.0 / monte_carlo(Trials, cesaro_test())).

% second version (no assignment)
random_gcd_test(Trials, InitialX) ->
   Iter =
      fun (Self, 0, TrialsPassed, _) -> TrialsPassed / Trials;
          (Self, TrialsRemaining, TrialsPassed, X) ->
            X1 = rand_update(X),
            X2 = rand_update(X1),
            case gcd(X1, X2) == 1 of
               true  -> Self(Self, TrialsRemaining-1, TrialsPassed+1, X2);
               false -> Self(Self, TrialsRemaining-1, TrialsPassed, X2)
            end
      end,
   Iter(Iter, Trials, 0, InitialX).

% Exercise 3.6
% exercise left to reader to define appropriate functions
% random_in_range(Low High) ->
%    Range = High - Low,
%    Low + randomx(Range).


% 3.1.3 - Assignment and Local State - The Cost of Introducing Assignment
section_3_1_3() ->
   W = make_simplified_withdraw(new_cell(25)),
   W(20),
   W(10),

   D = make_decrementer(new_cell(25)),
   print (D(20)),
   print (D(10)),

   print ((make_decrementer(new_cell(25)))(20)),
   print ((fun (Amount) -> 25 - Amount end)(20)),
   print (25 - 20),

   print ((make_simplified_withdraw(new_cell(25)))(20)),

   % Sameness and change
   D1 = make_decrementer(new_cell(25)),
   D2 = make_decrementer(new_cell(25)),

   W3 = make_simplified_withdraw(new_cell(25)),
   W4 = make_simplified_withdraw(new_cell(25)),
   print (W3(20)),
   print (W3(20)),
   print (W4(20)),

   PeterAcc = make_account(100),
   PaulAcc = make_account(100),

   PeterAcc1 = make_account(100),
   PaulAcc1 = PeterAcc1,

   print (factorial1(5)).

make_simplified_withdraw(Balance) ->
   fun (Amount) ->
      set_cell(Balance, get_cell(Balance) - Amount),
      get_cell(Balance)
   end.

make_decrementer(Balance) ->
   fun (Amount) ->
      set_cell(Balance, get_cell(Balance) - Amount),
      get_cell(Balance)
   end.

% Pitfalls of imperative programming
factorial(N) ->
   Iter =
      fun (Self, Product, Counter) ->
         case Counter > N of
            true  -> Product;
            false -> Self(Self, Counter*Product, Counter+1)
         end
      end,
   Iter(Iter, 1, 1).

factorial1(N) ->
   Product = new_cell(1),
   Counter = new_cell(1),
   Iter =
      fun (Self) ->
         case get_cell(Counter) > N of
            true  -> get_cell(Product);
            false ->
               set_cell(Product, get_cell(Counter) * get_cell(Product)),
               set_cell(Counter, get_cell(Counter) + 1),
               Self(Self)
         end
      end,
   Iter(Iter).


% 3.2.1 - The Environment Model of Evaluation - The Rules for Evaluation
section_3_2_1() ->
   Square = fun (X) -> X * X end.

% Same as above
% square(X) -> X * X.


% 3.2.2 - The Environment Model of Evaluation - Applying Simple Procedures
section_3_2_2() ->
   print (sum_of_squares(5, 6)),

   % Exercise 3.9
   print (factorial2(5)),
   print (factorial3(5)).

% square(X) -> X * X.

sum_of_squares(X, Y) ->
   square(X) + square(Y).

f (A) ->
   sum_of_squares(A+1, A*2).

% Exercise 3.9
factorial2(1) -> 1;
factorial2(N) -> N * factorial2(N-1).

fact_iter(Product, Counter, MaxCount) ->
   case Counter > MaxCount of
      true  -> Product;
      false -> fact_iter(Counter*Product, Counter+1, MaxCount)
   end.

factorial3(N) -> fact_iter(1, 1, N).


% 3.2.3 - The Environment Model of Evaluation - Frames as Repository of Local State
section_3_2_3() ->
   W5 = make_withdraw1(new_cell(100)),
   W5(50),
   W6 = make_withdraw1(new_cell(100)),

   % Exercise 3.10
   W7 = make_withdraw2(100),
   W7(50),
   W8 = make_withdraw2(100).

make_withdraw1(Balance) ->
   fun (Amount) ->
      case get_cell(Balance) >= Amount of
         true  -> set_cell(Balance, get_cell(Balance) - Amount);
         false -> throw ({insufficientFunds, get_cell(Balance)})
      end
   end.

% Exercise 3.10
make_withdraw2(InitialAmount) ->
   Balance = new_cell(InitialAmount),
   fun (Amount) ->
      case get_cell(Balance) >= Amount of
         true  -> set_cell(Balance, get_cell(Balance) - Amount);
         false -> throw ({insufficientFunds, get_cell(Balance)})
      end
   end.


% 3.2.4 - The Environment Model of Evaluation - Internal Definitions
section_3_2_4() ->
   % Exercise 3.11
   Acc3 = make_account1(50),
   (Acc3#account_record.deposit)(40),
   (Acc3#account_record.withdraw)(60),
   Acc4 = make_account1(100).

% same as in section 1.1.8
sqrt_3(X) ->
   Good_Enough =
      fun (Guess) ->
         abs(square(Guess) - X) < 0.001
      end,
   Improve =
      fun (Guess) ->
         average(Guess, X / Guess)
      end,
   Sqrt_Iter =
      fun (Self, Guess) ->
         case (Good_Enough(Guess)) of
            true  -> Guess;
            false -> Self(Self, Improve(Guess))
         end
      end,
   Sqrt_Iter(Sqrt_Iter, 1.0).


% Exercise 3.11
make_account1(InitBalance) ->
   Balance = new_cell(InitBalance),
   Withdraw =
      fun (Amount) ->
         case get_cell(Balance) >= Amount of
            true  -> set_cell(Balance, get_cell(Balance) - Amount);
            false -> throw ({insufficientFunds, get_cell(Balance)})
         end
      end,
   Deposit =
      fun (Amount) ->
         set_cell(Balance, get_cell(Balance) + Amount)
      end,
   GetBalance =
      fun () ->
         get_cell(Balance)
      end,
   #account_record{withdraw = Withdraw, deposit = Deposit, balance = GetBalance}.


% 3.3.1 - Modeling with Mutable Data - Mutable List Structure
section_3_3_1() ->
   % Exercise 3.12
   X = mlist([a, b]),
   Y = mlist([c, d]),
   Z = mappend1(X, Y),
   W = mappend1(X, Y),
   print (listm(W)),
   print (listm(X)),

   % Exercise 3.13
   Zm = make_cycle(mlist([a, b])),
   print (listm(Zm)),

   % Exercise 3.14
   Vm = mlist([a, b, c, d]),
   Wm = mystery(Vm),

   % Sharing and identity
   % X = mlist([a, b]),
   Z1 = mlist([X, X]),
   Z2 = mcons(mlist([a, b]), mlist([a, b])),
   print (listm(Z1)),
   set_to_wow(Z1),
   print (listm(Z2)),
   set_to_wow(Z2),

   % Exercise 3.20
   X3 = mcons2(1, 2),
   Z3 = mcons2(X3, X3),
   set_mcar2(mcdr2(Z3), 17),
   print (mcar2(X3)).

mcons(X, Y) -> {new_cell(X), new_cell(Y)}.

mcar({H, _}) -> get_cell(H).
mcdr({_, T}) -> get_cell(T).

set_mcar({H, _}, X) -> set_cell(H, X).
set_mcdr({_, T}, Y) -> set_cell(T, Y).

% Exercise 3.12
mappend([], Ys) -> Ys;
mappend({H, T}, Ys) -> mcons(get_cell(H), mappend(get_cell(T), Ys)).

last_pair(Xs) ->
   case mcdr(Xs) of
      [] -> Xs;
      T  -> last_pair(T)
   end.

mappend1(Xs, Ys) ->
   set_mcdr(last_pair(Xs), Ys),
   Xs.

mlist(Xs) ->
   lists:foldr(fun (V, B) -> mcons(V, B) end, [], Xs).

listm(_, 0)      -> ['...'];
listm([], _)     -> [];
listm({H, T}, N) ->
   X = get_cell(H),
   case X of
      {A, B} -> [listm(X)|listm(get_cell(T), N-1)];
      _ -> [X|listm(get_cell(T), N-1)]
   end.

listm(Xs) -> listm(Xs, 25).

% Exercise 3.13
make_cycle(Xs) ->
   set_mcdr(last_pair(Xs), Xs),
   Xs.

% Exercise 3.14
mystery(X) ->
   Loop =
      fun(Self, [], Y) -> Y;
         (Self, X, Y) ->
            Temp = mcdr(X),
            set_mcdr(X, Y),
            Self(Self, Temp, X)
      end,
   Loop(Loop, X, new_cell([])).

set_to_wow(X) ->
   set_mcar(mcar(X), "Wow").

% Exercise 3.16
is_pair({_, _}) -> true;
is_pair(_) -> false.
count_pairs(X) ->
   case not(is_pair(X)) of
      true  -> 0;
      false -> count_pairs(mcar(X)) + count_pairs(mcdr(X)) + 1
   end.

% Mutation as assignment
mcons1(H, T) ->
   fun (mcar) -> H;
       (mcdr) -> T
   end.
mcar1(Z) -> Z(mcar).
mcdr1(Z) -> Z(mcdr).

mcons2(H, T) ->
   X = new_cell(H),
   Y = new_cell(T),
   fun (mcar) -> get_cell(X);
       (mcdr) -> get_cell(Y);
       (set_mcar) -> fun (V) -> set_cell(X, V) end;
       (set_mcdr) -> fun (V) -> set_cell(Y, V) end
   end.
mcar2(Z) -> Z(mcar).
mcdr2(Z) -> Z(mcdr).
set_mcar2(Z, V) -> (Z(set_mcar))(V).
set_mcdr2(Z, V) -> (Z(set_mcdr))(V).


% 3.3.2 - Modeling with Mutable Data - Local State Variables
section_3_3_2() ->
   % Exercise 3.21
   Q1 = make_queue(),
   insert_queue(Q1, a),
   insert_queue(Q1, b),
   print (delete_queue(Q1)),
   print (delete_queue(Q1)).

front_ptr({H, T}) -> H.
rear_ptr({H, T}) -> T.

set_mcar3({H, T}, Item) -> set_cell(H, Item).
set_mcdr3({H, T}, Item) -> set_cell(T, Item).

set_front_ptr(Q, Item) -> set_mcar3(Q, Item).
set_rear_ptr(Q, Item) -> set_mcdr3(Q, Item).

is_empty_queue(Q) ->
   case get_cell(front_ptr(Q)) =:= [] of
      true  -> true;
      false -> false
   end.

make_queue() ->
   N = [],
   {new_cell(N), new_cell(N)}.

front_queue(Q) ->
   case get_cell(front_ptr(Q)) of
      {A, _} -> A
   end.

insert_queue(Q, Item) ->
   N = {Item, new_cell([])},
   case get_cell(front_ptr(Q)) of
      [] ->
         set_front_ptr(Q, N),
         set_rear_ptr(Q, N);
      _ ->
         case get_cell(rear_ptr(Q)) of
            {_, Nxt} ->
               set_cell(Nxt, N),
               set_rear_ptr(Q, N)
         end
   end.

delete_queue(Q) ->
   case get_cell(front_ptr(Q)) of
      {_, Nxt} ->
         Item = front_queue(Q),
         set_front_ptr(Q, get_cell(Nxt)),
         Item
   end.


% 3.3.3 - Modeling with Mutable Data - Representing Tables
section_3_3_3() ->
   D3 = make_table(),
   insert(abc, 123, D3),
   print (lookup(abc, D3)),

   D4 = make_table(),
   insert2(abc, 123, 12.3, D4),
   print (lookup2(abc, 123, D4)),

   Dictionary2 = make_dictionary2(),
   put_dictionary2(Dictionary2, abc, 123, 12.3),
   print (get_dictionary2(Dictionary2, abc, 123)),

   Fib_Table = make_table(),
   print (memo_fib(Fib_Table, 10)).

assoc(Key, Rec) ->
   case Rec of
      {tab, Xs} -> assoc(Key, get_cell(Xs));
      {leaf} -> {leaf};
      {tree, K, V, Xs} when Key =:= K -> Rec;
      {tree, K, V, Xs} -> assoc(Key, get_cell(Xs))
   end.

lookup(Key, Table) ->
   Record = assoc(Key, Table),
   case Record of
      {tree, K, V, _} -> {some, get_cell(V)};
      _ -> {none}
   end.

insert(Key, Value, Table) ->
   Record = assoc(Key, Table),
   case Record of
      {tree, K, V, _} -> set_cell(V, Value);
      _ ->
         case Table of
            {tab, Xs} -> set_cell(Xs, {tree, Key, new_cell(Value), new_cell(get_cell(Xs))})
         end
   end.

make_table() -> {tab, new_cell({leaf})}.

% two-dimensional
lookup2(Key1, Key2, Table) ->
   Record = assoc(Key1, Table),
   case Record of
      {tree, K1, V, _} -> lookup(Key2, get_cell(V));
      _ -> {none}
   end.

insert2(Key1, Key2, Value, Table) ->
   Record = assoc(Key1, Table),
   case Record of
      {tree, K, V, _} -> insert(Key2, Value, get_cell(V));
      _ ->
         case Table of
            {tab, Xs} ->
               NewTab = make_table(),
               insert(Key2, Value, NewTab),
               set_cell(Xs, {tree, Key1, new_cell(NewTab), new_cell(get_cell(Xs))})
         end
   end.

% local tables
make_dictionary2() -> spawn(fun() -> dictionary2(make_table()) end).
dictionary2(Table) ->
   receive
      {get, Pid, Key1, Key2} ->
         Record = assoc(Key1, Table),
         case Record of
            {tree, K1, V, _} -> Pid!{return, lookup(Key2, get_cell(V))};
            _ -> Pid!{return, none}
         end,
         dictionary2(Table);
      {put, Key1, Key2, Value} ->
         Record = assoc(Key1, Table),
         case Record of
            {tree, K, V, _} -> insert(Key2, Value, get_cell(V));
            _ ->
               case Table of
                  {tab, Xs} ->
                     NewTab = make_table(),
                     insert(Key2, Value, NewTab),
                     set_cell(Xs, {tree, Key1, new_cell(NewTab), new_cell(get_cell(Xs))})
               end
         end,
         dictionary2(Table);
      {dispose} -> {}
   end.
put_dictionary2(Dictionary2, Key1, Key2, Value) -> Dictionary2!{put,  Key1, Key2, Value}.
get_dictionary2(Dictionary2, Key1, Key2) ->
   Dictionary2!{get, self(), Key1, Key2},
   receive
      {return, Value} -> Value
   end.
dispose_dictionary2(Dictionary2) -> Dictionary2!{dispose}.

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

memoize(Table, F, X) ->
   PreviouslyComputedResult = lookup(X, Table),
   case PreviouslyComputedResult of
      {some, Item} -> Item;
      {none} ->
         Result = F(X),
         insert(X, Result, Table),
         Result
   end.

memo_fib(Table, N) ->
   Fib =
      fun (0) -> 0;
          (1) -> 1;
          (N) -> memo_fib(Table, N-1) + memo_fib(Table, N-2)
      end,
   memoize(Table, Fib, N).


% 3.3.4 - Modeling with Mutable Data - A Simulator for Digital Circuits
-record(wire_record, {get_signal, set_signal, add_action}).

section_3_3_4() ->
   Aw = make_wire(),
   Bw = make_wire(),
   Cw = make_wire(),
   Dw = make_wire(),
   Ew = make_wire(),
   Sw = make_wire(),

   or_gate_1(Aw, Bw, Dw),
   and_gate(Aw, Bw, Cw),
   inverter(Cw, Ew),
   and_gate(Dw, Ew, Sw),

   % Sample simulation
   Input1 = make_wire(),
   Input2 = make_wire(),
   Sum = make_wire(),
   Carry = make_wire(),

   probe(sum, Sum),
   probe(carry, Carry),

   half_adder(Input1, Input2, Sum, Carry),
   set_signal(Input1, hi),
   propagate(),

   set_signal(Input2, hi),
   propagate().

call_each([]) -> {};
call_each([H|T]) ->
   H(),
   call_each(T).

get_signal(Wire) -> (Wire#wire_record.get_signal)().
set_signal(Wire, NewValue) -> (Wire#wire_record.set_signal)(NewValue).
add_action(Wire, ActionProcedure) -> (Wire#wire_record.add_action)(ActionProcedure).

make_wire() ->
   SignalValue = new_cell(lo),
   ActionProcedures = new_cell([]),
   SetMySignal =
      fun (NewValue) ->
         case not(get_cell(SignalValue) =:= NewValue) of
            true ->
               set_cell(SignalValue, NewValue),
               call_each(get_cell(ActionProcedures));
            false -> {}
         end
      end,
   AcceptActionProcedure =
      fun (Proc) ->
         set_cell(ActionProcedures, [Proc | get_cell(ActionProcedures)])
      end,
   GetSignal =
      fun () ->
         get_cell(SignalValue)
      end,
   #wire_record{get_signal = GetSignal, set_signal = SetMySignal, add_action = AcceptActionProcedure}.

logical_not(lo) -> hi;
logical_not(hi) -> lo.

logical_and(hi, hi) -> hi;
logical_and(_, _) -> lo.

logical_or(lo, lo) -> lo;
logical_or(_, _) -> hi.

make_time_segment(Time, Queue) ->
   {time_segment, new_cell(Time), Queue}.
segment_time({time_segment, Time, Queue}) -> Time.
segment_queue({time_segment, Time, Queue}) -> Queue.

% agenda is a list of time segments
make_agenda() -> mcons(make_time_segment(0, make_queue()), []).
current_time(Agenda) -> get_cell(segment_time(mcar(Agenda))).
current_time_ref(Agenda) -> segment_time(mcar(Agenda)).
set_current_time(Agenda, Time) -> set_cell(current_time_ref(Agenda), Time).

segments(Agenda) -> mcdr(Agenda).
set_segments(Agenda, Segments) -> set_mcdr(Agenda, Segments).
first_segment(Agenda) -> mcar(segments(Agenda)).
rest_segments(Agenda) -> mcdr(segments(Agenda)).

empty_agenda(Agenda) -> segments(Agenda) =:= [].

first_agenda_item(Agenda) ->
   case empty_agenda(Agenda) of
      true  -> throw({agenda, "Agenda is empty -- FIRST-AGENDA-ITEM"});
      false ->
         FirstSeg = first_segment(Agenda),
         set_current_time(Agenda, get_cell(segment_time(FirstSeg))),
         front_queue(segment_queue(FirstSeg))
   end.

remove_first_agendaitem(Agenda) ->
   Q = segment_queue(first_segment(Agenda)),
   delete_queue(Q),
   case is_empty_queue(Q) of
      true  -> set_segments(Agenda, rest_segments(Agenda));
      false -> {}
   end.

add_to_agenda(Time, Action, Agenda) ->
   BelongsBefore =
      fun (Segments) ->
         case Segments =:= [] of
            true  -> true;
            false -> Time < get_cell(segment_time(mcar(Segments)))
         end
      end,
   MakeNewTimeSegment =
      fun (Time, Action) ->
         Q = make_queue(),
         insert_queue(Q, Action),
         make_time_segment(Time, Q)
      end,
   AddToSegments =
      fun (Self, Segments) ->
         case get_cell(segment_time(mcar(Segments))) =:= Time of
            true  -> insert_queue(segment_queue(mcar(Segments)), Action);
            false ->
               Rest = mcdr(Segments),
               case BelongsBefore(Rest) of
                  true -> set_mcdr(Segments, mcons(MakeNewTimeSegment(Time, Action), mcdr(Segments)));
                  false -> Self(Self, Rest)
               end
         end
      end,
   SegmentsX = segments(Agenda),
   case BelongsBefore(SegmentsX) of
      true  -> set_segments(Agenda, mcons(MakeNewTimeSegment(Time, Action), SegmentsX));
      false -> AddToSegments(AddToSegments, SegmentsX)
   end.

% Note: erlang discourages use of process dictionary.  But it'll do for now.
the_agenda() ->
   case get(the_agenda) =:= undefined of
      true  ->
         put(the_agenda, make_agenda()),
         get(the_agenda);
      false -> get(the_agenda)
   end.

after_delay(Delay, Action) ->
   add_to_agenda(Delay + current_time(the_agenda()), Action, the_agenda()).

inverter_delay() -> 2.
and_gate_delay() -> 3.
or_gate_delay()  -> 5.

inverter(Input, Output) ->
   NewValue = logical_not(get_signal(Input)),
   InvertInput =
      fun () ->
         after_delay(inverter_delay(), fun () -> set_signal(Output, NewValue) end)
      end,
   add_action(Input, InvertInput).

and_gate(A1, A2, Output) ->
   NewValue = logical_and(get_signal(A1), get_signal(A2)),
   AndActionProcedure =
      fun () ->
         after_delay(and_gate_delay(), fun () -> set_signal(Output, NewValue) end)
      end,
   add_action(A1, AndActionProcedure),
   add_action(A2, AndActionProcedure).

or_gate(A1, A2, Output) ->
   NewValue = logical_or(get_signal(A1), get_signal(A2)),
   OrActionProcedure =
      fun () ->
         after_delay(or_gate_delay(), fun () -> set_signal(Output, NewValue) end)
      end,
   add_action(A1, OrActionProcedure),
   add_action(A2, OrActionProcedure).

half_adder(A, B, S, C) ->
   D = make_wire(),
   E = make_wire(),
   or_gate(A, B, D),
   and_gate(A, B, C),
   inverter(C, E),
   and_gate(D, E, S).

or_gate_1(A1, A2, Output) ->
   B = make_wire(),
   C = make_wire(),
   D = make_wire(),
   inverter(A1, B),
   inverter(A2, C),
   and_gate(B, C, D),
   inverter(D, Output).

full_adder(A, B, Cin, Sum, Cout) ->
   S = make_wire(),
   C1 = make_wire(),
   C2 = make_wire(),
   half_adder(B, Cin, S, C1),
   half_adder(A, S, Sum, C2),
   or_gate(C1, C2, Cout).

propagate() ->
   case empty_agenda(the_agenda()) of
      true  -> {};
      false ->
         FirstItem = first_agenda_item(the_agenda()),
         FirstItem(),
         remove_first_agendaitem(the_agenda()),
         propagate()
   end.

probe(Name, Wire) ->
   add_action(
      Wire,
      fun () ->
         io:write(Name),
         io:format(" "),
         io:write(current_time(the_agenda)),
         io:format("  NewValue = "),
         io:write(get_signal(Wire)),
         io:format("~n")
      end).

% Exercise 3.31
% accept_action_procedure_1(Proc) ->
%    set_cell(ActionProcedures, [Proc|ActionProcedures]).


% 3.3.5 - Modeling with Mutable Data - Propagation of Constraints
-record(propagator_record, {process_new_value, process_forget_value}).
-record(connector_record, {has_value, get_value, set_value, forget_value, connect}).

section_3_3_5() ->
   User = #propagator_record{process_new_value = fun () -> {} end,
                             process_forget_value = fun () -> {} end},

   Cx = make_connector(),
   Fx = make_connector(),

   celsius_fahrenheit_converter(Cx, Fx),

   probe_connector('Celsius temp', Cx),
   probe_connector('Fahrenheit temp', Fx),

   set_value(Cx, 100.0, User),
   forget_value(Cx, User),
   set_value(Fx, 32.0, User),

   % Exercise 3.36
   Ay = make_connector(),
   By = make_connector(),
   set_value(Ay, 10, User).

inform_about_value(Propagator) ->
   (Propagator#propagator_record.process_new_value)().

inform_about_no_value(Propagator) ->
   (Propagator#propagator_record.process_forget_value)().

has_value(Connector) ->
   (Connector#connector_record.has_value)().

get_value(Connector) ->
   (Connector#connector_record.get_value)().

set_value(Connector, NewValue, Informant) ->
   (Connector#connector_record.set_value)(NewValue, Informant).

forget_value(Connector, Retractor) ->
   (Connector#connector_record.forget_value)(Retractor).

connect(Connector, NewConstraint) ->
   (Connector#connector_record.connect)(NewConstraint).

for_each_except(Except, Procedure, List) ->
   Loop =
      fun (Self, []) -> {};
          (Self, [H|T]) when H =:= Except -> Self(Self, T);
          (Self, [H|T]) -> Procedure(H), Self(Self, T)
      end,
   Loop(Loop, List).

make_connector() ->
   ValueList = new_cell([]),
   InformantList = new_cell([]),
   Constraints = new_cell([]),
   HasValue =
      fun () ->
         not(get_cell(ValueList) =:= [])
      end,
   GetValue =
      fun () ->
         hd(get_cell(ValueList))
      end,
   Informant =
      fun () ->
         hd(get_cell(InformantList))
      end,
   SetValue =
      fun (NewVal, Setter) ->
         case not(HasValue()) of
            true ->
               set_cell(ValueList, [NewVal]),
               set_cell(InformantList, [Setter]),
               for_each_except(Setter, fun inform_about_value/1, get_cell(Constraints));
            false ->
               case not(GetValue()) =:= NewVal of
                  true  -> throw ({constraint, "Contradiction"});
                  false -> {}
               end
         end
      end,
   ForgetValue =
      fun(Retractor) ->
         case not(get_cell(InformantList) =:= []) andalso Retractor =:= Informant() of
            true ->
               set_cell(InformantList, []),
               set_cell(ValueList, []),
               for_each_except(Retractor, fun inform_about_no_value/1, get_cell(Constraints));
            false -> {}
         end
      end,
   Connect =
      fun (NewConstraint) ->
         case not(lists:member(NewConstraint, get_cell(Constraints))) of
            true  -> set_cell(Constraints, [NewConstraint | get_cell(Constraints)]);
            false -> {}
         end,
         case HasValue() of
            true  -> inform_about_value(NewConstraint);
            false -> {}
         end
      end,
   #connector_record{has_value = HasValue,
                     get_value = GetValue,
                     set_value = SetValue,
                     forget_value = ForgetValue,
                     connect = Connect}.



adder(A1, A2, Sum) ->
   Me = new_cell({}),
   ProcessNewValue =
      fun () ->
         case has_value(A1) andalso has_value(A2) of
            true  -> set_value(Sum, get_value(A1) + get_value(A2), get_cell(Me));
            false ->
               case has_value(A1) andalso has_value(Sum) of
                  true  -> set_value(A2, get_value(Sum) - get_value(A1), get_cell(Me));
                  false ->
                     case has_value(A2) andalso has_value(Sum) of
                        true  -> set_value(A1, get_value(Sum) - get_value(A2), get_cell(Me));
                        false -> {}
                     end
               end
         end
      end,
   ProcessForgetValue =
      fun () ->
         forget_value(Sum, get_cell(Me)),
         forget_value(A1, get_cell(Me)),
         forget_value(A2, get_cell(Me)),
         ProcessNewValue()
      end,
   set_cell(Me, #propagator_record{process_new_value = ProcessNewValue,
                                   process_forget_value = ProcessForgetValue}),
   connect(A1, get_cell(Me)),
   connect(A2, get_cell(Me)),
   connect(Sum, get_cell(Me)).

multiplier(M1, M2, Product) ->
   Me = new_cell({}),
   ProcessNewValue =
      fun () ->
         case (has_value(M1) andalso get_value(M1) == 0.0) orelse
              (has_value(M2) andalso get_value(M2) == 0.0) of
            true  -> set_value(Product, 0.0, get_cell(Me));
            false ->
               case has_value(M1) andalso has_value(M2) of
                  true  -> set_value(Product, get_value(M1) * get_value(M2), get_cell(Me));
                  false ->
                     case has_value(Product) andalso has_value(M1) of
                        true   -> set_value(M2, get_value(Product) / get_value(M1), get_cell(Me));
                        false ->
                           case has_value(Product) andalso has_value(M2) of
                              true  -> set_value(M1, get_value(Product) / get_value(M2), get_cell(Me));
                              false -> {}
                           end
                     end
               end
         end
      end,
   ProcessForgetValue =
      fun () ->
         forget_value(Product, get_cell(Me)),
         forget_value(M1, get_cell(Me)),
         forget_value(M2, get_cell(Me)),
         ProcessNewValue()
      end,
   set_cell(Me, #propagator_record{process_new_value = ProcessNewValue,
                                   process_forget_value = ProcessForgetValue}),
   connect(M1, get_cell(Me)),
   connect(M2, get_cell(Me)),
   connect(Product, get_cell(Me)).

constant(Value, Connector) ->
   Me = new_cell({}),
   ProcessNewValue =
      fun () ->
         throw ({constraint, "Unknown request -- CONSTANT -- process_new_value"})
      end,
   ProcessForgetValue =
      fun () ->
         throw ({constraint, "Unknown request -- CONSTANT  -- process_forget_value"})
      end,
   set_cell(Me, #propagator_record{process_new_value = ProcessNewValue,
                                   process_forget_value = ProcessForgetValue}),
   connect(Connector, get_cell(Me)),
   set_value(Connector, Value, get_cell(Me)).

probe_connector(Name, Connector) ->
   Me = new_cell({}),
   PrintProbe =
      fun (Value) ->
         io:format("Probe: "),
         io:write(Name),
         io:format(" = "),
         io:write(Value),
         io:format("~n")
      end,
   ProcessNewValue =
      fun () ->
         PrintProbe(get_value(Connector))
      end,
   ProcessForgetValue =
      fun () ->
         io:format("Probe: "),
         io:write(Name),
         io:format(" = ?"),
         io:format("~n")
      end,
   set_cell(Me, #propagator_record{process_new_value = ProcessNewValue,
                                   process_forget_value = ProcessForgetValue}),
   connect(Connector, get_cell(Me)).

celsius_fahrenheit_converter(C, F) ->
   U = make_connector(),
   V = make_connector(),
   W = make_connector(),
   X = make_connector(),
   Y = make_connector(),
   multiplier(C, W, U),
   multiplier(V, X, U),
   adder(V, Y, F),
   constant(9.0, W),
   constant(5.0, X),
   constant(32.0, Y).

% Exercise 3.34
squarer(A, B) ->
   multiplier(A, A, B).

% 3.4.1 - Concurrency: Time Is of the Essence - The Nature of Time in Concurrent Systems
section_3_4_1() ->
   Balance1 = new_cell(100),

   % Exercise 3.38
   set_cell(Balance1, get_cell(Balance1) + 10),
   set_cell(Balance1, get_cell(Balance1) - 20),
   set_cell(Balance1, get_cell(Balance1) - (get_cell(Balance1) div 2)),
   print(get_cell(Balance1)).

withdraw1(Balance, Amount) ->
   case get_cell(Balance) >= Amount of
      true  -> set_cell(Balance, get_cell(Balance) - Amount);
      false -> throw ({insufficientFunds, get_cell(Balance)})
   end.


% 3.4.2 - Concurrency: Time Is of the Essence - Mechanisms for Controlling Concurrency
section_3_4_2() ->
   X = new_cell(10),
   parallel_execute(fun () -> set_cell(X, get_cell(X) * get_cell(X)) end,
                    fun () -> set_cell(X, get_cell(X) + 1) end),
   receive after 100 -> {} end,
   print(get_cell(X)),

   set_cell(X, 10),
   S = make_serializer(),
   parallel_execute(fun () -> S(fun () -> set_cell(X, get_cell(X) * get_cell(X)) end) end,
                    fun () -> S(fun () -> set_cell(X, get_cell(X) + 1) end) end),
   receive after 100 -> {} end,
   print(get_cell(X)),

   % Exercise 3.39
   set_cell(X, 10),
   parallel_execute(fun () -> set_cell(X, S(fun () -> get_cell(X) * get_cell(X) end)) end,
                    fun () -> S(fun () -> set_cell(X, get_cell(X) + 1) end) end),
   receive after 100 -> {} end,
   print(get_cell(X)),

   % Exercise 3.40
   set_cell(X, 10),
   parallel_execute(fun () -> set_cell(X, get_cell(X) * get_cell(X)) end,
                    fun () -> set_cell(X, get_cell(X) * get_cell(X) * get_cell(X)) end),
   receive after 100 -> {} end,
   print(get_cell(X)),

   set_cell(X, 10),
   parallel_execute(fun () -> S(fun () -> set_cell(X, get_cell(X) * get_cell(X)) end) end,
                    fun () -> S(fun () -> set_cell(X, get_cell(X) * get_cell(X) * get_cell(X)) end) end),
   receive after 100 -> {} end,
   print(get_cell(X)).

parallel_execute(F1, F2) ->
   spawn(F1),
   spawn(F2).

% Implementing serializers
make_mutex() -> spawn(fun() -> mutex() end).
mutex() ->
   receive
      {lock, Pid} ->
         Pid!{lock},
         receive
            {unlock} -> {}
         end,
         mutex();
      {dispose} -> {}
   end.
lock_mutex(Mutex) ->
   Mutex!{lock, self()},
   receive
      {lock} -> {lock}
   end.
unlock_mutex(Mutex) -> Mutex!{unlock}.
dispose_mutex(Mutex) -> Mutex!{dispose}.

make_serializer() ->
   Mutex = make_mutex(),
   fun (P) ->
      lock_mutex(Mutex),
      X = P(),
      unlock_mutex(Mutex),
      X
   end.

make_account_2(InitBalance) ->
   Balance = new_cell(InitBalance),
   Withdraw =
      fun (Amount) ->
         case get_cell(Balance) >= Amount of
            true  -> set_cell(Balance, get_cell(Balance) - Amount);
            false -> throw ({insufficientFunds, get_cell(Balance)})
         end
      end,
   Deposit =
      fun (Amount) ->
         set_cell(Balance, get_cell(Balance) + Amount)
      end,
   GetBalance =
      fun () ->
         get_cell(Balance)
      end,
   Mutex = make_mutex(),
   Lock =
      fun (P) ->
         lock_mutex(Mutex),
         X = P(),
         unlock_mutex(Mutex),
         X
      end,
   #account_record{withdraw = fun (Amount) -> Lock(fun () -> Withdraw(Amount) end) end,
                   deposit  = fun (Amount) -> Lock(fun () -> Deposit(Amount) end) end,
                   balance  = GetBalance}.

% Exercise 3.41
make_account_3(InitBalance) ->
   Balance = new_cell(InitBalance),
   Withdraw =
      fun (Amount) ->
         case get_cell(Balance) >= Amount of
            true  -> set_cell(Balance, get_cell(Balance) - Amount);
            false -> throw ({insufficientFunds, get_cell(Balance)})
         end
      end,
   Deposit =
      fun (Amount) ->
         set_cell(Balance, get_cell(Balance) + Amount)
      end,
   Mutex = make_mutex(),
   Lock =
      fun (P) ->
         lock_mutex(Mutex),
         X = P(),
         unlock_mutex(Mutex),
         X
      end,
   #account_record{withdraw = fun (Amount) -> Lock(fun () -> Withdraw(Amount) end) end,
                   deposit  = fun (Amount) -> Lock(fun () -> Deposit(Amount) end) end,
                   balance  = fun () -> Lock(fun () -> get_cell(Balance) end) end}.

% Exercise 3.42
make_account_4(InitBalance) ->
   Balance = new_cell(InitBalance),
   Withdraw =
      fun (Amount) ->
         case get_cell(Balance) >= Amount of
            true  -> set_cell(Balance, get_cell(Balance) - Amount);
            false -> throw ({insufficientFunds, get_cell(Balance)})
         end
      end,
   Deposit =
      fun (Amount) ->
         set_cell(Balance, get_cell(Balance) + Amount)
      end,
   GetBalance =
      fun () ->
         get_cell(Balance)
      end,
   Mutex = make_mutex(),
   Lock =
      fun (P) ->
         lock_mutex(Mutex),
         X = P(),
         unlock_mutex(Mutex),
         X
      end,
   ProtectedWithdraw = fun (Amount) -> Lock(fun () -> Withdraw(Amount) end) end,
   ProtectedDeposit  = fun (Amount) -> Lock(fun () -> Deposit(Amount) end) end,
   #account_record{withdraw = ProtectedWithdraw,
                   deposit  = ProtectedDeposit,
                   balance  = GetBalance}.

% Multiple shared resources
-record(account_serialize_record, {withdraw, deposit, balance, serializer}).
make_account_5(InitBalance) ->
   Balance = new_cell(InitBalance),
   Withdraw =
      fun (Amount) ->
         case get_cell(Balance) >= Amount of
            true  -> set_cell(Balance, get_cell(Balance) - Amount);
            false -> throw ({insufficientFunds, get_cell(Balance)})
         end
      end,
   Deposit =
      fun (Amount) ->
         set_cell(Balance, get_cell(Balance) + Amount)
      end,
   GetBalance =
      fun () ->
         get_cell(Balance)
      end,
   Mutex = make_mutex(),
   Lock =
      fun (P) ->
         lock_mutex(Mutex),
         X = P(),
         unlock_mutex(Mutex),
         X
      end,
   #account_serialize_record{withdraw = Withdraw,
                             deposit  = Deposit,
                             balance  = GetBalance,
                             serializer = Lock}.

exchange(Account1, Account2) ->
   Difference = (Account1#account_serialize_record.balance)() - (Account2#account_serialize_record.balance)(),
   (Account1#account_serialize_record.withdraw)(Difference),
   (Account2#account_serialize_record.deposit)(Difference),
   Difference.

deposit(Account, Amount) ->
   S = Account#account_serialize_record.serializer,
   D = Account#account_serialize_record.deposit,
   S(fun () -> D(Amount) end).

serialized_exchange(Account1, Account2) ->
   Serializer1 = Account1#account_serialize_record.serializer,
   Serializer2 = Account2#account_serialize_record.serializer,
   Serializer1(
      fun () ->
         Serializer2(fun () -> exchange(Account1, Account2) end)
      end).

% Exercise 3.44
transfer(FromAccount, ToAccount, Amount) ->
   (FromAccount#account_serialize_record.withdraw)(Amount),
   (ToAccount#account_serialize_record.deposit)(Amount).

% Exercise 3.45
make_account_6(InitBalance) ->
   Balance = new_cell(InitBalance),
   Withdraw =
      fun (Amount) ->
         case get_cell(Balance) >= Amount of
            true  -> set_cell(Balance, get_cell(Balance) - Amount);
            false -> throw ({insufficientFunds, get_cell(Balance)})
         end
      end,
   Deposit =
      fun (Amount) ->
         set_cell(Balance, get_cell(Balance) + Amount)
      end,
   GetBalance =
      fun () ->
         get_cell(Balance)
      end,
   Mutex = make_mutex(),
   Lock =
      fun (P) ->
         lock_mutex(Mutex),
         X = P(),
         unlock_mutex(Mutex),
         X
      end,
   #account_serialize_record{withdraw = fun (Amount) -> Lock(fun () -> Withdraw(Amount) end) end,
                             deposit  = fun (Amount) -> Lock(fun () -> Deposit(Amount) end) end,
                             balance  = GetBalance,
                             serializer = Lock}.

deposit1(Account, Amount) ->
   (Account#account_serialize_record.deposit)(Amount).


% 3.5.1 - Streams - Streams Are Delayed Lists
section_3_5_1() ->
   % print (hd(tl(lists:filter(fun is_prime/1, enumerate_interval(10000, 1000000))))),
   print ("").

sum_primes(A, B) ->
   Iter =
      fun (Self, Count, Accum) when Count > B -> Accum;
          (Self, Count, Accum) ->
            case is_prime(Count) of
               true  -> Self(Self, Count+1, Count+Accum);
               false -> Self(Self, Count+1, Accum)
            end
      end,
   Iter(Iter, A, 0).

sum_primes_1(A, B) ->
   lists:foldr(fun erlang:'+'/2, 0, lists:filter(fun is_prime/1, enumerate_interval(A, B))).

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