Reply To: "case of" vs "if" vs function clauses?

Home Forums Basic Erlang "case of" vs "if" vs function clauses? Reply To: "case of" vs "if" vs function clauses?



I would like to thank everyone for their advice. I learned some! First thing, I have the “Erlang and OTP in action” book by Martin Logan, Eric Merritt, and Richard Carlsson. I would say that the book doesn’t help so much get me going, as it helps explain what I have. In other words, being so new to Erlang, I sometimes don’t know how to ask a question, but once I stumble across something relevant, I can go back to the book and learn more because I know what to look at.

Would “Erlang Programming” by Francesco Cesarini and Simon Thompson be more suitable for me?

I got my Bass diffusion simulation going. The code is pasted below. I welcome additional insights and advice/criticism. I realize that my code may appear “babyish” to you experts, and that I might be doing apparently redundant or unnecessary things, but I have an eye toward evolving more heterogeneous shopper agents (different P and Q parameters for each shopper), more complex shopper behavior, etc. I don’t think I’m ready for OTP frameworks, but I am interested in what it takes to run the simulation on more than one machine.

-export([init/4, shopper/3]).

%% init/4 is the only exported function (see -export directive at top). We
%% spawn TotShoppers shopper agents and then create the store agent which
%% interacts with the shoppers mainly as a clock.
init(TotShoppers, P, Q, MaxIterations) ->
    ShopperPIds = spawn_shoppers(TotShoppers, P, Q, []),
    store(ShopperPIds, MaxIterations),

%% shopper/3 represents a shopper agent who has not yet made a first
%% purchase. It initializes a random number seed and calls shopper_loop/4.
shopper(Store_PId, P, Q) ->
    <<A:32, B:32, C:32>> = crypto:rand_bytes(12),
    random:seed({A, B, C}),
    shopper_loop(Store_PId, P, Q, 0).

%% shopper_loop/4 is the receiver loop for the shopper agent. It waits
%% for a "visit" message from the store agent, upon which it decides
%% probabilistically whether to make a first purchase or not. If so,
%% then the shopper becomes an adopter/2, otherwise the agent waits for 
%% another "visit" to decide.
shopper_loop(Store_PId, P, Q, VisitIndex) ->
	{visit, Store_PId, MarketFraction} ->
	    Prob = P + Q*MarketFraction,
	    U = random:uniform(),
%	    io:format("~p working with prob = ~p and U = ~p~n", [self(), Prob, U]),

	    case Prob >= U of
		true ->
%		    io:format("looks like ~p adopted!~n", [self()]),
		    Store_PId ! {adopted, self()},
		    adopter(Store_PId, VisitIndex+1);
		false ->
%		    io:format("looks like ~p passed up!~n", [self()]),
		    Store_PId ! {passed_up, self()},
		    shopper_loop(Store_PId, P, Q, VisitIndex+1)

	terminate ->

%% adopter/2 represents an adopter, a shopper who made a first purchase during
%% VisitIndex. We remove shoppers from the market after a first purchase, so 
%% basically this agent waits around for a terminate message.
adopter(Store_PId, VisitIndex) ->
	{visit_index, Store_PId} ->
	    Store_PId ! {visit_index, self(), VisitIndex},
	    adopter(Store_PId, VisitIndex);
	terminate ->

%% spawn_shoppers/4 is the function that spawns TotShoppers shopper agents.
%% When the desired count of shopper agents has been spawned, we return a
%% list of process ids.
spawn_shoppers(0, _P, _Q, Acc) -> Acc;
spawn_shoppers(TotShoppers, P, Q, Acc) ->
    PId = spawn(?MODULE, shopper, [self(), P, Q]),
    spawn_shoppers(TotShoppers-1, P, Q, [PId|Acc]).
%% gather_stats/1 polls the shopper agents to find out which iteration they
%% made their first purchase (became an adopter). I suppose the store agent
%% could record this info, but I wanted to try out a model where the store agent
%% acts more like a clock and knows little more than the count of adopters.
gather_stats(PIds) -> do_gather_stats(PIds, array:new({default,0})).

do_gather_stats([], Hist) -> 
    Counts = [array:get(I, Hist) || I <- lists:seq(1, array:size(Hist))],
    io:format("histogram = ~p~n", [Counts]),
do_gather_stats([PId|PIds], Hist) ->
    PId ! {visit_index, self()},
	{visit_index, PId, VisitIndex} ->
%	    io:format("~p\tvisit_index\t~p~n", [PId, VisitIndex]),
	    PId ! terminate,
	    NewHist = array:set(VisitIndex, 1+array:get(VisitIndex, Hist), Hist),
	    do_gather_stats(PIds, NewHist);
	_ ->

store(PIds, MaxIterations) -> store_loop(PIds, PIds, 0.0, [], 0, MaxIterations).

store_loop([], PIds, _MarketFraction, AvailablePIds, TotAdopters, MaxIterations) ->
    MarketFraction = TotAdopters/length(PIds),
%    io:format("~p total adopters, market fraction now at ~p~n", [TotAdopters, MarketFraction]),
%    io:format("available shoppers now ~p~n", [AvailablePIds]),
    case {AvailablePIds, MaxIterations} of
	{[], _} -> 
	    io:format("# looks like we are done, no shoppers left to adopt~n");
	{_, 0} ->
	    io:format("#looks like we got tired of waiting, done~n");
	{_, _} ->
	    store_loop(AvailablePIds, PIds, MarketFraction, [], TotAdopters, MaxIterations-1)
store_loop([PId|RemainingPIds], PIds, MarketFraction, AvailablePIds, TotAdopters, MaxIterations) ->
    PId ! {visit, self(), MarketFraction},
	{passed_up, PId} ->
	    store_loop(RemainingPIds, PIds, MarketFraction, [PId|AvailablePIds], TotAdopters, MaxIterations);
	{adopted, PId} ->
	    store_loop(RemainingPIds, PIds, MarketFraction, AvailablePIds, TotAdopters+1, MaxIterations);
	_ ->
	    store_loop(RemainingPIds, PIds, MarketFraction, AvailablePIds, TotAdopters, MaxIterations)

I ran bass:init(100000, 0.03, 0.3, 1000). and pasted the histogram counts into R where I plotted the results. Looks right to me. The vertical dotted red line is the theoretical location of where the peak first-time buying should occur.

While writing this post, I tried to include an image link to show my plotted results, but it doesn’t seem to be rendering. The URL is

Thanks again,

Mike Beddo

  • This reply was modified 7 months ago by  mbeddo.