Monday, July 5, 2010

Erjang - No, its not a typo and its Erlang-on-JVM

There's a new Erlang language in town built on-top of the JVM. Its Erjang. I seem to experience a explosion of hosts of languages built on-top of the Java Virtual Machine from Scala (my current favourite), Clojure (Lisp clone), Jython (Python clone), Groovy and JRuby to name a few; for a developer like myself, i do find myself suddenly swamped with a huge list of flavours in which to train myself in. Enough about me.

So Erjang is a runtime which basically loads the Erlang's BEAM file into the JVM and creates Java classes and its instances. Check out the project's homepage here. Like all languages, it'll need time to be adopted and adopted. Personally, i'll stick to Erlang since it fulfills most of my use cases from bit-hacking (which i absolutely adore) to general purpose computing and i'll love to listen to the use cases in which the author built Erjang. There's always something to be learned.

And i found it! Here's the rationale from the Creator which is ossm (check out the video)

Over the last few years, I have been meeting "Erlang people" more and more often, and I was getting this clear impression that "you people" have some kind of magic ability to reason intuitively about concurrent systems in a way that I could not. That bothered me, so I wanted to learn Erlang. Being a language implementor, the most obvious way to do that is to just go ahead and implement an Erlang VM, right?

The result of this "little exercise" is Erjang, an open-source JVM-based Erlang VM, which I think has potential to be useful as a means to push Erlang into new areas of application, help grow the Erlang community, and most importantly make all those Java-heads out there willing to learn how to do concurrency right. In this "brave new world" of networked services and multi-core everywhere, there are a lot of people who are challenged in providing even basic reliability, efficiency and correctness. My hope is that Erjang's existence can influence some of those outside the Erlang community to do better.

In technical terms, Erjang reads .beam files and compiles them to Java's equivalent .class files which are then read into the running JVM. It runs off a plain Erlang/OTP distribution - it only requires the beam files from there; Erjang itself is written in Java. As off this writing, it can run some non-trivial erlang programs, but is not yet capable of booting OTP [will have to update this abstract as we go along - follow updates on my blog]. Comparing the BEAM virtual machine and Erjang, the most obvious differences are that (a) Erjang will not be able to provide [soft] real-time guarantees since it uses Java's garbage collector, and (b) it has limited support for native code and port drivers (other than file and network I/O). The upside is the new ways this allows us to deploy Erlang systems.

In this presentation I will talk about how Erjang works, what behavior to expect from Erlang programs running on top of Erjang, what I learned along the way, and demonstrate [live] that Erjang runs well enough to be obviously useful.

Monday, November 2, 2009

Tsung - Load Testing Tool in Erlang

You've must have heard of Tsung - a load testing tool. Well i've got the opportunity to learn and use it in my current employment and its really versatile in conducting load tests for web-based applications. It has many features that i've come to appreciate after spending a couple of years with Mercury Interactive (its defunct now after being bought over by HP in 2006); admittedly Mercury's solutions were more versatile than what Tsung can offer but considering that web is the common platform where many applications are being hosted on; i think its a safe bet :)

So, IMO i think
  1. Tsung is good for conducting load testing scenarios and executing them in a local / distributed manner
  2. Load Testing can be relatively light weight (Erlang processes are hitting the target app) and hence the cost of using relatively heavy weight machines is likely to reduce since there is lesser need to use those machines since more concurrent users can be simulated on 1 machine in Tsung. (I'll see whether this assumption is correct)
Good starting points (URLs of interest):
  1. http://tsung.erlang-projects.org/
  2. http://www.erlang.org
A load testing tool would be useless if it didn't know how to capture server response data (e.g. checking whether an expected string is returned) and reuse it (e.g. session ids returned from servers which you can use subsequently in a HTTP POST/GET in an URL-rewrite type of string) subsequently, generate dynamic data, read data from an external file (commonly used in storing <username, password> pairs)
So my example would illustrate the logging in to a website (e.g. http://projecteuler.net) and logging out from it - simple enough to illustrate my point.
Next what i did was to record the series of events that mimick my user logging and logging out of the website and this is captured in the tsung_recorder_timestamp.xml
and i used that XML file and included some other stuff so that it looks like what i have for you below (this is a basic load test scenario)
<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/usr/local/share/tsung/tsung-1.0.dtd">

<tsung loglevel="info" dumptraffic="false" version="1.0">

<clients>
<client host="localhost" use_controller_vm="true"/>
</clients>
<servers>
<server host="78.110.165.8" port="80" type="tcp"></server>
</servers>

<load>
<arrivalphase phase="1" duration="2" unit="minute">
<users interarrival="1" unit="minute"></users>
</arrivalphase>
<user session="rec20091102-01:30" start_time="0" unit="second"></user>
</load>

<options>
<option name="file_server" value="/tmp/userlist.csv"></option>
</options>

<sessions>
<session name='rec20091102-01:30' probability='100'  type='ts_http'>
<request><http url='http://projecteuler.net/' version='1.1' method='GET'></http></request>
<request><http url='/style_main.css' version='1.1' if_modified_since='Sat, 29 Nov 2008 20:04:00 GMT' method='GET'></http></request>
<request><http url='/images/logo.jpg' version='1.1' if_modified_since='Thu, 28 Dec 2006 14:12:43 GMT' method='GET'></http></request>
<request><http url='/images/icon_register.png' version='1.1' if_modified_since='Fri, 31 Dec 2004 12:48:16 GMT' method='GET'></http></request>
<request><http url='/images/icon_about.png' version='1.1' if_modified_since='Fri, 31 Dec 2004 12:48:28 GMT' method='GET'></http></request>
<request><http url='/images/icon_problems.png' version='1.1' if_modified_since='Fri, 31 Dec 2004 12:48:26 GMT' method='GET'></http></request>
<request><http url='/images/icon_login.png' version='1.1' if_modified_since='Fri, 31 Dec 2004 12:48:32 GMT' method='GET'></http></request>
<request><http url='http://projecteuler.net/images/corner_tr.gif' version='1.1' if_modified_since='Thu, 10 Apr 2008 19:35:02 GMT' method='GET'></http></request>
<request><http url='/images/corner_tl.gif' version='1.1' if_modified_since='Thu, 10 Apr 2008 19:34:41 GMT' method='GET'></http></request>
<request><http url='/images/corner_br.gif' version='1.1' if_modified_since='Thu, 10 Apr 2008 19:35:10 GMT' method='GET'></http></request>
<request><http url='/images/corner_bl.gif' version='1.1' if_modified_since='Thu, 10 Apr 2008 19:34:55 GMT' method='GET'></http></request>
<request><http url='/images/euler_main.jpg' version='1.1' if_modified_since='Mon, 21 Jan 2002 19:18:20 GMT' method='GET'></http></request>

<thinktime random='true' value='2'/>

<request><http url='http://projecteuler.net/index.php?section=login' version='1.1' method='GET'></http></request>

<thinktime random='true' value='6'/>

<request subst="true">
<match do="continue" when="match">Logged in as %%readcsv:getUsername%%</match>
<http url='/index.php' version='1.1'  contents='%%readcsv:getUserString%%' content_type='application/x-www-form-urlencoded' method='POST'></http>
</request>

<thinktime random='true' value='4'/>

<request><http url='/images/icon_tick.png' version='1.1' method='GET'></http></request>

<thinktime random='true' value='4'/>

<thinktime random='true' value='3'/>

<request><http url='http://projecteuler.net/index.php?section=logout' version='1.1' method='GET'></http></request>
</session>
</sessions>

</tsung>


Hence, the main thing you should note is the use of dynamic substitution (e.g. %%readcsv:getUsername%%) where i wrote a simple erlang program to read my username and password from a file (see the XML tag option above) and replacing each simulated user with a valid user id and password.
Next, i checked that the server response contains a string Logged in as XXX where XXX would be dynamically generated by the function (Check out the erlang code for the function, simple stuff).
The erlang program is shown below.
1 -module(readcsv).
2 -export([getUserString/1, getUsername/1]).
3
4 getUserString({Pid, DynVar}) ->
5  {ok, Line} = ts_file_server:get_next_line(),
6  [Uid,Pwd] = string:tokens(Line, ","),
7  "username=" ++ Uid ++ "&password=" ++ Pwd ++ "&login=Login".
8
9 getUsername({Pid, DynVar}) ->
10  {ok, Line} = ts_file_server:get_next_line(),
11  [Uid,_] = string:tokens(Line, ","),
12  Uid.
The above erlang code must be compiled via erlc and you place the .beam file into the directory via a command like this
sudo mv readcsv.beam /usr/local/lib/erlang/lib/tsung-1.3.1/ebin/

Now, running the load test should be alright.
Note: In this load test, i defined a duration of 2 minutes with 2 users since load testing using 800 gazillion users is considered a chargeable offense so DON'T DO IT.


Have fun!

Tuesday, August 11, 2009

wxErlang

There's another graphics library other than "gs" aka Graphical System which is an adaptation of wxWidgets otherwise known as wxErlang. If you have never tried using wxErlang to run your stuff then you might run into some problems.

Sadly, the documentation on this is pitifully little

For me, i got this error when i attempt to run the default wx libraries shipped with Erlang.

=ERROR REPORT==== 7-Jun-2009::12:17:09 ===
WX Failed loading "wxe_driver"@"/usr/local/lib/erlang/lib/wx-0.98.1/
priv/i386-apple-darwin9.6.0"
** exception error: {load_driver,"dlopen(/usr/local/lib/erlang/lib/
wx-0.98.1/priv/i386-apple-darwin9.6.0/wxe_driver.so, 2): Symbol not
found: __ZN10wxGLCanvas20MacVisibilityChangedEv\n Referenced from: /
usr/local/lib/erlang/lib/wx-0.98.1/priv/i386-apple-darwin9.6.0/
wxe_driver.so\n Expected in: flat namespace\n"}
in function wxe_server:start/0
in call from wx:new/1

If this is familiar to you, then i suggest you do the following
1. Read all of this
or if you are like me building wxErlang & BEAM emulator 5.7 from source
1. Download the wxErlang libraries from wxErlang website and unzip to dir wxMac-2.8.10
2. Change to that directory
3. Execute the following command:
./configure --with-opengl --enable-unicode --disable-shared --enable-graphics_ctx

4. Do the following in sequence:
make && make install
cd contrib/src/stc/
make && make install
Note that you might need to do a "sudo make install" if you use the default installation prefix which is /usr/local/{bin,lib, etc}
5. Assumed that you've download Erlang-OTP 13B source code and unzipped to a directory
6. Export the PATH variable such that the build directory to your wxErlang is right at the front of the PATH. e.g. export PATH=<wxErlang dir>:<rest of your path>
7. Build your erlang source code as per your preferences/needs as defined in the README

After all that is over, you should be able to launch your wxErlang programs.


Thursday, August 6, 2009

An example of lazy evaluation for generating lists

One thing i've learnt from Erlang is only that its very expressive despite its annoyances but a good outcome of this expressiveness is possibly the availability of lazy(delayed) evaluation. In the example of passing lists to functions, the lists are evaluated fully before being passed to functions.

The big deal here is that you could potentially craft a function that's computationally expensive but the user of this function might not need all the results all at once. Example would be using these sorts of functions to obtain database records and to display them on a webpage but every good UI designer knows that its ineffective to display all 15,000 records at 1 time and you could design a web page that displays 10 - 20 records at a time and have "next" buttons/links to display subsequent pages.

So, naturally we can craft functions such that we can have lazy evaluation. A simple example is as follows where the function next/1 behaves like a generator function but only delayed; another way to think of this concept is possibly python generators. In my example below, its not hard to imagine that next/1 could be your computationally expensive operation.

1 -module(lazy_eval).
2 -export([next/1, test/2]).
3
4 next(Num) ->
5 fun() -> [Num|next(Num+1)] end.
6
7 test(Seq, Fun) when Seq =:= 100 -> [Seq];
8 test(Seq, Fun) when Seq < 100 ->
9 [Val|Func] = Fun(),
10 % io:format("~p ~p~n", [Val, Func()]),
11 [Val | test(Seq + 1, Func) ].

A sample output (with debugging statements):

6> lazy_eval:test(1, lazy_eval:next(1)).
1 [2|#Fun<lazy_eval.0.5801025>]
2 [3|#Fun<lazy_eval.0.5801025>]
3 [4|#Fun<lazy_eval.0.5801025>]
4 [5|#Fun<lazy_eval.0.5801025>]
...
99 [100|#Fun<lazy_eval.0.5801025>]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,
23,24,25,26,27,28,29|...]
Have fun!

Monday, August 3, 2009

Why Erlang Sucks - by Damien Katz

Found another interesting read why Erlang sucks. This post is not to create some sort of flame war and the end result is crucifying Damien but rather his post is a rather insightful look into the annoying part of Erlang that most Erlang developers must have seen (me including) and felt.

Examples of what i thought was good was his
* comparison of "if" and "case" in Erlang
* Erlang's concept of strings (Which is a real irritance personally to me)
* Erlang's concept of records which is super similar to C's struct (Another irritance but you can get the hang of it...after a while)
* Absolutely love Damien's take of exit(...) and his comments on hara-kiri (Really hilarious)
* I share Damien's criticisms on Erlang's documentation of the libraries

Overall, its a very honest look into the weaknesses of Erlang as a whole and there's more though...you can form your own opinions about the language though.


Job openings at Linden Lab

Linden Lab is hiring lots of talented people out there for software development and QA (that's where i'm in). Visit http://lindenlab.com/employment and if you do find something you like, feel free to drop me a email to tay_boon_leong@yahoo.com.sg or ray@lindenlab.com and yes we do stuff in Erlang, Python, Perl, Django blah blah blah


Sunday, August 2, 2009

Parallel QuickSort

Found an interesting blog by a Erlang enthusiast where he implemented a parallel version of the popular QuickSort algorithm. Read about his entry at http://bicosyes.com/paralelizando-quicksort-en-erlang/ where he used the list-comprehension version of QuickSort to implement parallel computation. Its very helpful to see how he made use of erlang:make_ref() to help in the algorithm. Good read.

Tuesday, April 21, 2009

Notice

Hi all, its been a while since i last blogged on Erlang ... infact its almost a year. Its not that i have given up on erlang but rather things have been moving in my career and life in general which left me little time to blog about this but i'll be picking this up again and hopefully u'll like it

Wednesday, November 7, 2007

Core Erlang + "GS" = GUI version of the "Insertion Sort"

In this article, i got another popular sorting algorithm commonly known as the "Insertion Sort". Basically, this sort an example of (a) in-place , (b) comparison-sort .You may wish to consult your favourite algorithm texts or visit the Wikipedia (I've heard its rather unreliable but i'm not so sure why). Again like all my previous articles, there are two parts to this: (a) Non-graphical, (b) Graphical version.

Here's the codes:

Filename: "isort.erl"
-module(isort).
-export([start/1]).
-copyright('Copyright (c) 2007 Raymond Tay').
-vsn('$Revision: 1').
%
%
% The insertion sort is a good middle-of-the-road choice for sorting lists of
% a few thousand items or less. The algorithm is significantly simpler than
% the shell sort, with only a small trade off in efficiency. At the same time
% the insertion sort is over twice as fast as the bubble sort and almost 40%
% faster than the selection sort. The insertion sort shouldn't be used for
% sorting lists larger than a couple thousands items or repetitive sorting
%of lists larger than a couple hundred items
%
% insertionSort(array A)
% for i = 1 to length[A]-1 do
% value = A[i]
% j = i-1
% while j >= 0 and A[j] > value do
% A[j + 1] = A[j]
% j = j-1
% A[j+1] = value
%

genNum(N,N,L,F,Num) -> [F(Num),L];
genNum(I,N,L,F,Num) -> Val = F(Num), genNum(I+1,N,[Val,L],F,Num).

for(N,N,F,L) -> Val = lists:nth(N,L), J = N - 1,T = lists:nth(J,L), F(J,L,Val,T);
for(I,N,F,L) -> Val = lists:nth(I,L), J = I - 1,T = lists:nth(J,L), L2 = F(J,L,Val,T), for(I+1,N,F,L2).

checkNInsert(Index,L,Value,T) when Index >= 1 andalso T > Value ->
{L1,L2} = lists:split(Index,L),
[_H|T1] = L2,
Temp1 = lists:flatten(lists:append([L1],[T])),
Temp2 = lists:append(Temp1, T1),
if
Index - 1 /= 0 ->
NewVal = lists:nth(Index - 1, Temp2),
checkNInsert(Index - 1, Temp2, Value, NewVal);
Index - 1 =:= 0 ->
checkNInsert(Index - 1, Temp2, Value, Value)
end;
checkNInsert(Index,L,Value,T) when Index =< 1 orelse T =< Value ->
{L1,L2} = lists:split(Index,L),
[_H|T1] = L2,
lists:append(lists:append(L1,[Value]),T1).

start(N) ->
L = lists:flatten(genNum(1,N,[],fun random:uniform/1, N)),
for(2, length(L), fun checkNInsert/4, L).

And next is for the graphical version

Filename: "isortgui.erl"
-module(isortgui).
-export([start/0]).
-copyright('Copyright (c) 2007 Raymond Tay').
-vsn('$Revision: 1').
%
% The insertion sort is a good middle-of-the-road choice for sorting lists of
% a few thousand items or less. The algorithm is significantly simpler than
% the shell sort, with only a small trade off in efficiency. At the same time
% the insertion sort is over twice as fast as the bubble sort and almost 40%
% faster than the selection sort. The insertion sort shouldn't be used for
% sorting lists larger than a couple thousands items or repetitive sorting
% of lists larger than a couple hundred items
%
% insertionSort(array A)
% for i = 1 to length[A]-1 do
% value = A[i]
% j = i-1
% while j >= 0 and A[j] > value do
% A[j + 1] = A[j]
% j = j-1
% A[j+1] = value
%
forL(N,N,{stretch,_N,_X}) -> [{stretch,_N,_X}];
forL(I,N,{stretch,_N,_X}) -> [{stretch,_N,_X}|forL(I+1,N,{stretch,_N,_X})].

for(N,N,F,L) -> {_,Val} = gs:read(dict:fetch({1,N},L),label), J = N - 1, {_,T} = gs:read(dict:fetch({1,J},L),label), {Val1,_} = string:to_integer(Val), {T1,_} = string:to_integer(T),F(J,L,Val1,T1);
for(I,N,F,L) -> {_,Val} = gs:read(dict:fetch({1,I},L),label), J = I - 1, {_,T} = gs:read(dict:fetch({1,J},L),label), {Val1,_} = string:to_integer(Val), {T1,_} = string:to_integer(T), D2 = F(J,L,Val1,T1), for(I+1,N,F,D2).

checkNInsert(Index,L,Value,T) when Index >= 1 andalso T > Value ->
ButId = dict:fetch({1,Index+1},L),
gs:config(ButId, [{label, {text, T}}]),
gs:config(ButId, [flash]),
sleep(50),
if
Index - 1 =:= 0 -> checkNInsert(0,L,Value,Value);
Index - 1 /= 0 ->
{_,Val} = gs:read(dict:fetch({1,Index -1},L),label),
{Val1,_} = string:to_integer(Val),
if
Val1 =< Value -> checkNInsert(Index - 1, L, Value, Value);
Val1 > Value -> checkNInsert(Index - 1, L, Value, Val1)
end
end;
checkNInsert(Index,L,Value,T) when Index =:= 0 orelse T =< Value ->
ButId = dict:fetch({1,Index+1},L),
gs:config(ButId, [{label, {text, Value}}]),
gs:config(ButId, [flash]),
L.

isort(Dict) ->
for(2, 50, fun checkNInsert/4, Dict). % Sort it for 2-to-<number of columns in GUI>

start() ->
D = dict:new(),
WH = [{width, 1000}, {height, 50}],
Win = gs:window(gs:start(), [{map, true}, {configure, true}, {title, "Insertion-Sort Algorithm"} |WH]),
XOpts = forL(1,50,{stretch,1,20}),
YOpts = [{stretch,1,20}],
gs:frame(packer, Win, [{packer_x, XOpts}, {packer_y, YOpts}]),
NewD = genBut(1,1,D),
gs:config(packer,WH),
dumpDict(NewD),
isort(NewD),
dumpDict(NewD),
io:format("algo done~n",[]),
loop().
%
%
% Generate the "button" GUI objects
%
genBut(Row,Col,Dict) when Col =< 50 ->
ButId = gs:button(packer, [{label, {text, random:uniform(50)}}, {pack_xy, {Col,Row}}]),
NewDict = dict:store({Row,Col}, ButId, Dict), % used later for GUI manipulation
genBut(Row,Col+1,NewDict);
genBut(Row,Col,Dict) when Col > 50 -> Dict.

%
% Pretty standard GUI-event loop
%
loop() ->
receive
{gs,_Id,destroy,_Data,_Arg} -> bye;
{gs,_Id,configure,_Data,[W,H|_]} ->
gs:config(packer, [{width,W}, {height,H}]), %refresh to initial size
loop();
_Other -> loop()
end.
%
% Pretty standard "sleep" function
%
sleep(T) ->
receive
after T -> ok
end.

%
% Dump the dictionary
%
dumpDict(D) ->
Keys = lists:sort(dict:fetch_keys(D)),
lists:foreach( fun({R,C}) -> Val = gs:read(dict:fetch({R,C},D),label), io:format("{~p,~p}->~p~n", [R,C,Val]) end, Keys).
While the algorithm is executing, you will notice that the buttons will flash every time it detects a re-positioning is required. Here is a screen shot and do let me know if you have any comments, it would be much appreciated; that applies to ideas as well :-)


Saturday, November 3, 2007

Core Erlang + "GS" = GUI version of the "BubbleSort"

Everyone who has studied Computer Science would know about the Bubble Sort algorithm. This is a simple sorting algorithm which runs at O(n^2). You may wish to consult your standard data structure text or the above hyperlink for further information.

Since this particular blog of mine is about Erlang and also i'm quite "hooked" to it, so again this article is about illustrating this algorithm implemented using Erlang. It's a fun exercise.

I have included source codes for running 2 versions: (a) Non-graphical and (b) Graphical.

%
% This is an attempt to create a GUI version of the "Bubble Sort"
%
% procedure bubblesort(A:list of unsorted items) defined as:
% do
% swapped := false
% for each i in 0 to length(A) -2 do
% if A[i] > A[i+1] then
% swap(A[i], A[i+1])
% swapped := true
% end if
% end for
% while swapped
% end procedure

-module(bsort).
-export([start/1, init/1]).
-copyright('Copyright (c) 2007 Raymond Tay').
-vsn('$Revision: 1').

start(N) -> spawn(bsort, init, [N]).

%
% Standard "for"-loop concept
%
for(N,N,Dict,F,Num) -> Val = F(Num), NewDict = dict:store(N,Val,Dict), NewDict;
for(I,N,Dict,F,Num) -> Val = F(Num), NewDict = dict:store(I,Val,Dict), for(I+1,N,NewDict,F,Num).

% Swaps the value but not the position of the record
%
swap({Pos1,Value1},{Pos2,Value2},Dict) ->
NewDict1 = dict:store(Pos1,Value2,Dict),
NewDict2 = dict:store(Pos2,Value1,NewDict1),
NewDict2.

%
% BubbleSort implementation
%
bsort(D,Swapped) when Swapped =:= false -> D;
bsort(D,Swapped) when Swapped =:= true ->
Len = erlang:length(dict:fetch_keys(D)),
{Again, NewDict} = checkNSwap(D,1,Len, false),
bsort(NewDict,Again).

%
% Check-N-Swap
%
checkNSwap(Dict,N,N,Again) -> {Again,Dict};
checkNSwap(Dict,I,N,Again) ->
Ele1 = dict:fetch(I,Dict),
Ele2 = dict:fetch(I+1,Dict),
if
Ele1 > Ele2 -> NewDict = swap({I,Ele1},{I+1,Ele2},Dict), checkNSwap(NewDict, I+1,N,true);
Ele1 =< Ele2 -> checkNSwap(Dict,I+1,N,Again)
end.

init(N) -> Dict = dict:new(),
NewDict = for(1,N,Dict, fun random:uniform/1, N),
dumpDict(bsort(NewDict,true)).


dumpDict(Dict) ->
Keys = lists:sort(dict:fetch_keys(Dict)),
lists:foreach(fun(X) -> io:format("{~p,~p} ...", [X,dict:fetch(X,Dict)]) end, Keys).

The next is the source code for the graphical version of the "Bubble Sort" and some screen-shots of the algorithm in action. I used an 25 x 25 "grid" composed of buttons (Will explore using the canvas when i have more time) and i tried the 100 x 100 "grid" but it didn't work out on my laptop as i had some resolution problems, but i think as an exercise a 25 x 25 was good enough for me :-)

-module(bsortgui).
-export([start/0]).
%
% BubbleSort implementation
%
bsort(D,Swapped) when Swapped =:= false -> D;
bsort(D,Swapped) when Swapped =:= true ->
{Again, NewDict} = checkNSwap(D,1,25, false),
bsort(NewDict,Again).

%
% Swaps the value but not the position of the record
%
swap({Pos1,Value1,ButId1},{Pos2,Value2,ButId2},Dict) ->
gs:config(ButId1,[{label, {text, ""}}]),
gs:config(ButId2,[{label, {text, ""}}]),
NewButId1 = dict:fetch({Value1,Pos2},Dict),
NewButId2 = dict:fetch({Value2,Pos1},Dict),
gs:config(NewButId1,[{label, {text, Value1}}]),
gs:config(NewButId2,[{label, {text, Value2}}]),
NewDict1 = dict:store(Pos1,{Value2,NewButId2},Dict),
NewDict2 = dict:store(Pos2,{Value1,NewButId1},NewDict1), NewDict2.

sleep(T) ->
receive
after T -> ok end.
%
% Check-N-Swap
%
checkNSwap(Dict,N,N,Again) -> {Again,Dict};
checkNSwap(Dict,I,N,Again) ->
{Ele1,ButId1} = dict:fetch(I,Dict),
{Ele2,ButId2} = dict:fetch(I+1,Dict),
%io:format("Col:~p,Row:~p~n", [I,Ele1]),
%io:format("Col:~p,Row:~p~n", [I+1,Ele2]),
if
Ele1 > Ele2 -> NewDict = swap({I,Ele1,ButId1},{I+1,Ele2,ButId2},Dict), sleep(300), checkNSwap(NewDict, I+1,N,true);
Ele1 =< Ele2 -> checkNSwap(Dict,I+1,N,Again)
end.


forL(N,N,{stretch,_N,_X}) -> [{stretch,_N,_X}];
forL(I,N,{stretch,_N,_X}) -> [{stretch,_N,_X}|forL(I+1,N,{stretch,_N,_X})].

%for(N,N,[H|_], D, F) -> F(H,D);
%for(I,N,[H|T], D, F) -> NewD = F(H,D), for(I+1,N,T,NewD,F).

genBut(Row,Col,Dict) when Row*Col =< 625 ->
if
Col =< 25 ->
ButId = gs:button(packer, [{label, {text, ""}}, {pack_xy, {Col,Row}}]),
NewDict = dict:store({Row,Col}, ButId, Dict), % used later for GUI manipulation
%io:format("genBut(), Row:~p, Col:~p, Id:~p~n", [Row,Col,ButId]),
genBut(Row,Col+1,NewDict);
Col > 25 -> genBut(Row+1, 1, Dict)
end;
genBut(Row,Col,Dict) when Row*Col > 625 -> Dict.

%
% Fill each column with exactly 1 random number
%
fillRand(Col,Dict) when Col =< 25 ->
Ran = random:uniform(25),
if
Col =< 25 ->
ButId = dict:fetch({Ran,Col},Dict),
gs:config(ButId, [{label, {text, Ran}}]),
NewDict = dict:store(Col, {Ran,ButId}, Dict), % used later for GUI manipulation
%io:format("Row:~p,Col:~p,Val:~p~n", [Ran,Col,Ran]),
fillRand(Col+1,NewDict)
end;
fillRand(Col,Dict) when Col > 25 -> Dict.



start() ->
D = dict:new(),
WH = [{width, 800}, {height, 800}],
Win = gs:window(gs:start(), [{map, true}, {configure, true}, {title, "BubbleSort Algorithm"} |WH]),
XOpts = forL(1,25,{stretch,1,15}),
YOpts = forL(1,25,{stretch,1,15}),
gs:frame(packer, Win, [{packer_x, XOpts}, {packer_y, YOpts}]),
NewD = genBut(1,1,D),
NewD2 = fillRand(1,NewD),
gs:config(packer,WH),
bsort(NewD2,true),
io:format("Done~n",[]),
loop().
%
% A typical loop for receiving messages
%
loop() ->
receive
{gs,_Id,destroy,_Data,_Arg} -> bye;
{gs,_Id,configure,_Data,[W,H|_]} ->
gs:config(packer, [{width,W},{height,H}]), %refresh to initial size
loop();
_Other -> loop()
end.

Below are some screenshots.



Let me know your comments if you got any :-)