License

MIT license

Copyright (c) 2008, Doug Edmunds

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

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.

 

Content © 2007 Erlbol |
Powered by Etomite CMS.