Connect to e3d_vec
An example
The absolute first thing to do
Before you try to attach a module to erlbol, make sure it works. Compile it in an Erlang console and test every exported function.
Specific steps
- Put the frameworks for erlbol in a directory (erlbol_framework.erl and erlbol_framework.r).
- Put the copy of your code module (in this case, e3d_vec.erl) in the same directory.
- Compile your code module and test it.
- Make changes to the erlbol/1 function, to provide for every exported function in your module. Note: it is the return value of your function that gets to erlbol/1. If your function is full of io:format statements, those will not get sent to erlbol. The return value of an io:format statement is 'ok', not the statement.
- Notice the change in the "try" statement: instead of using ?MODULE:Function(), make a specific reference to the other module: e3d_vec:Function().
Basic connections
If the code function name is unique (i.e., there is funname/3 but no funname/2 or funname/4), then you can simply pass the function name from erlbol-REBOL to erlbol-Erlang. The list that is sent holds the function name in the head, and the parameters in the tail. In Erlbol, you chop up the tail to pick off as many parameters as you need for the function, and leave whatever else happens to get sent over in the 'tail of the tail' (you just don't use it). So if your function has an arity of 2, the tail can be chopped up like this: [A, B | _C]. If the function has an arity of 3, you chop the tail up like this: [A, B, C | _D].
For example, in the e3d_vec.erl module, there is an exported function identified as cross/2. In the new erlbol server file, find erlbol/1 function. Then just before the last entry in the case statement i(the one that starts with "Other ->"), put in cross ->.
Now you need to insure that the correct datatypes are sent to cross/2. cross/2 takes two tuples. Each tuple has 3 elements. Each of those elements is a float.
cross({V10,V11,V12}, {V20,V21,V22})
when is_float(V10), is_float(V11), is_float(V12),
is_float(V20), is_float(V21), is_float(V22) ->
{V11*V22-V12*V21,V12*V20-V10*V22,V10*V21-V11*V20}.
Look at the function parameters
By design, the REBOL side of Erlbol sends the contents of fields as strings. In most cases you will design the fields to contain individual values. It is up to the Erlang side to put the values into lists, tuples, etc., as called for by the Erlang function. So in the case of the cross/2 function, you can see that it takes two tuples (each with 3 elements of type float). If Erlbol-REBOL sends 6 values, then Erlbol-Erlang will put those values into tuples:
case Function of
cross ->
[A , B, C, D, E, F | _G] = Tail,
try e3d_vec:Function(
[list_to_float(A), list_to_float(B), list_to_float(C)}),
[list_to_float(D), list_to_float(E), list_to_float(F)}) of
Val -> Val
catch
error:Reason -> Reason
end ;
If you design the REBOL window to have the user create tuples (using two fields), then the user will be typing something like this:
{3.2, 4.1, 5.3} in one field and {9.2, 1.5, 8.8} in the other. Erlang-REBOL converts each of those into strings so now your Erlbol-Erlang code will look something like this, which is more complicated:
case Function of
cross ->
[A , B | _C] = Tail,
try {D,E,F} = list_to_tuple(A), {G,H,I} = list_to_tuple(B),
e3d_vec:Function(
[list_to_float(D), list_to_float(E), list_to_float(F)}),
[list_to_float(G), list_to_float(H), list_to_float(I)}) of
Val -> Val
catch
error:Reason -> Reason
end ;
The downside of the second approach is that it leaves a lot of room for user data-entry errors
Next, a more complicated example
Erlang allows you to have more than one function with the same name, so long as they have different arities. e3d_vec has both add/1 and add/2. There is also add/4, but it is only used internally by the Erlang module, so we don't have to be concerned about it.
add/1 takes a list as its parameter:
add([{V10,V11,V12}|T]) ->
add(T, V10, V11, V12).
And add/2 takes 2 tuples as parameters
add({V10,V11,V12}, {V20,V21,V22}) when is_float(V10), is_float(V11), is_float(V12) ->
{V10+V20,V11+V21,V12+V22}.
Since erlbol/1 function does not know the size of the arity, how can you tell it which add function to use?
Handling the problem
One possible solution is just to rename one of the functions, so they don't have the same name. However, that breaks a cardinal rule of Erlbol: don't change the Erlang code. The better solution is to include the arity in the list that you send from REBOL:
["function", "arityN", "value1", ... valueN]
So for add/1, send this to erlbol/1: ["add", "1", X], and for add/2, send this: ["add", "2", X, Y], where both X and Y are some string values.
Inside erbol, you branch add -> into 2 paths:
add ->
%could be add/1 or add/2
[Head2 | Tail2] = Tail,
try
Arity_found = lists:member(list_to_integer(Head2),[1,2]), %possible arities
if Arity_found ->
case list_to_integer(Head2) of
1 ->
insert_here; %see_section_below
2 ->
[V10, V11, V12, V20, V21, V22 |_T] = Tail2,
try
e3d_vec:Function(
{list_to_float(V10),list_to_float(V11),list_to_float(V12)},
{list_to_float(V20),list_to_float(V21),list_to_float(V22)}) of
Val -> Val
catch
error:Reason2 -> Reason2
end %2
end %case
end % if
catch
error:Reason -> Reason
end ;
Modify the list [1,2] in this line. I.e., if your function has arities [0,1], change it to:
Arity_found = lists:member(list_to_integer(Head2),[0,1]),
Issues in add/1
add/1 takes a list of tuples, but does not have a fixed size. So, the function expects that you will send it an arbitrarily long list of 3-element tuples, for example [{1.2, 1.3, 1.35},{2.1, 1.4. 9.4},{8.22, 1.6, 12.4}, ..., {2.4, 12.2,7.5}]. When you are creating the Erlbol-REBOL window, the only way you can allow for a completely arbitrary list is to use one field, with opening an closing square brackets, and each tuple inside properly formatted for Erlang. That's an ugly and error-prone way to deal with it. A better approach may be to just decide the maximum number of tuples you will allow on the screen. For example, let's assume you will use add/1 with 3 tuples (nine fields). In that case, the erlbol code will look something like this:
add ->
%could be add/1 or add/2
[Head2 | Tail2] = Tail,
try
Arity_found = lists:member(list_to_integer(Head2),[1,2]), %possible arities
if Arity_found ->
case list_to_integer(Head2) of
1 ->
[V10, V11, V12, V20, V21, V22, V30, V31, V32
|_T] = Tail2,
try
e3d_vec:Function(
[{list_to_float(V10),list_to_float(V11),list_to_float(V12)},
{list_to_float(V20),list_to_float(V21),list_to_float(V22)},
{list_to_float(V30),list_to_float(V31),list_to_float(V32)} ] ) of
Val -> Val
catch
error:Reason1 -> Reason1
end ; %1
2 ->
insert_here %see_section_above
end %case
end % if
catch
error:Reason -> Reason
end ;
Note how the Erlang add/1 function sees a list, uses the first tuple as the head, and puts the rest in the Tail.
Try the erlbol-Erlang code as you build it out
Compile the code frequently to make sure you don't have syntax errors. Try it out. With the above code in place, you can run this line in Erlang, without a REBOL window (you don't need to start the server):
6> erlbol_e3d:erlbol(["add","1","2.1","22.2","3.3","30.0","0.40","230.0","480.10","0.001","430.20"]).
{512.2,22.601,663.5}
But there is a problem! The coding for add/1 locks in using 3 tuples. It won't work with less. A possible solution is to change the sending side to send a single parameter inside the main list:
["add", "1", "[ list_of_tuples_here]"]
if add -> receives a list of tuples, the code can look like this:
add ->
%could be add/1 or add/2
[Head2 | Tail2] = Tail,
try
Arity_found = lists:member(list_to_integer(Head2),[1,2]), %possible arities
if Arity_found ->
case list_to_integer(Head2) of
1 ->
try
[Tail3] = Tail2 %strip off quotes around list
e3d_vec:Function(Tail3) of
Val -> Val
catch
error:Reason1 -> Reason1
end ; %1
2 ->
insert_here %see_section_above
end %case
end % if
catch
error:Reason -> Reason
end ;
The place in erlbol-REBOL to generate the list-of-tuples string is before it is sent to the generic erlbol-out function. However, there is still a problem (unless only one field is used) of telling REBOL how many of the available fields to include. For example, if the window is designed to allow up to ten tuples (30 values), how to indicate how many to use? A solution might lie in knowing more about coding a REBOL window, so a slider could be set to the assigned number.
Yes, dealing with arbitrary-length lists can be challenging.