Erlang/OTP Forums

Author Message

<  Erlang questions mailing list  ~  Question about fun's

pearcs at rpi.edu
Posted: Wed Jan 27, 1999 7:28 pm Reply with quote
Guest
Ok, we're looking at implementing a system in Erlang where it would be nice to
have true object-oriented behavior. The way this is typically done in scheme
is to make each object a lambda and bind the object state to the lambda when
you construct the object. Thus you can do "(object print)" to make it print
out stuff.

I'm thinking that we'd do something similar in Erlang like so:

-module(myclass).
-export([new/0]).

new() -> save_state([]).

save_state(State) ->
fun(Message) ->
myclass:dispatch(State,Message)
end.

dispatch(State,{print}) -> io:format("Hi!");
dispatch(State,{append,Item}) -> save_state([Item|State]);
dispatch(State,{get}) -> State;
dispatch(State,{empty}) -> save_state([]).

Then I can work with the object like this:

O=myclass:new().
O1=O({append,TheThingy}).
O1({print}).
O2=O1({empty}).

What are the drawbacks, ie is the fun going to cost me a large amount of RAM,
or a large amount of processing overhead on each message?

--
Shawn.

(The above are the rantings of a body without a mind.)



Post generated using Mail2Forum (http://m2f.sourceforge.net)
ulf.wiger at etxb.ericsso
Posted: Wed Jan 27, 1999 8:24 pm Reply with quote
Guest
Shawn Pearce wrote:
>
> Ok, we're looking at implementing a system in Erlang where it would be nice to
> have true object-oriented behavior. The way this is typically done in scheme
> is to make each object a lambda and bind the object state to the lambda when
> you construct the object. Thus you can do "(object print)" to make it print
> out stuff.
>
> I'm thinking that we'd do something similar in Erlang like so:
>
> -module(myclass).
> -export([new/0]).
>
> new() -> save_state([]).
>
> save_state(State) ->
> fun(Message) ->
> myclass:dispatch(State,Message)
> end.
>
> dispatch(State,{print}) -> io:format("Hi!");
> dispatch(State,{append,Item}) -> save_state([Item|State]);
> dispatch(State,{get}) -> State;
> dispatch(State,{empty}) -> save_state([]).
>
> Then I can work with the object like this:
>
> O=myclass:new().
> O1=O({append,TheThingy}).
> O1({print}).
> O2=O1({empty}).

Personally, I'd go with 'print' and 'empty' instead of tuples with only
one element. You don't have a type checker that's gonna complain -
Erlang really couldn't care less, but your match operation speeds up
somewhat. A matter of preference, I guess.

>
> What are the drawbacks, ie is the fun going to cost me a large amount of RAM,
> or a large amount of processing overhead on each message?

Funs don't cost much in terms of memory. You can look at how a fun is
represented:


-module(myclass).
-export([new/0]).

new() ->
F = save_state([]),
tuple_to_list(F). %% hack to reveal the representation of a fun

save_state(State) ->
fun(Message) ->
myclass:dispatch(State,Message)
end.

If you compile and run:

1> myclass:new().
['fun',myclass,0,7662692,{[]}]

That is, a 5-tuple. The rest is compiled and represented as one
instance.

The processing overhead is roughly the same as for apply/3. A few
microseconds per call.

On the minus side:
In the current JAM implementation, error reporting for funs is lousy and
code change on-the-fly doesn't work well (the fun's internal reference
changes and the fun instance has to be recreated.)

Apart from this, I have found that it's most often counter-productive to
use an object-oriented style of programming in Erlang. Personally, I
spent my first 6 months with Erlang trying to make it object-oriented.
After that, I had convinced myself that you get further by using Erlang
as it was intended (granted, we didn't have funs in those days...)

You could do a similar thing without funs, to begin with:

-module(myclass).
-export([new/0,
dispatch/2]).

new() -> [].

dispatch(State, print) -> io:format("~p~n", [State]);
dispatch(State, {append,Item}) -> State ++ Item;
dispatch(State, get) -> State;
dispatch(State, empty) -> [].

then:

O = myclass:new().
O1 = myclass:dispatch(O, {append,TheThingy}).

etc:

You may argue that the object-oriented aspect got lost:

-module(myclass).
-export([new/0,
dispatch/2]).


new() ->
{?MODULE, []}.

dispatch(State, print) -> io:format("~p~n", [State]);
dispatch(State, {append,Item}) -> State ++ Item;
dispatch(State, get) -> State;
dispatch(State, empty) -> [].

-module(oo).
-export([msg/2]).

msg({Class, State}, Msg) ->
Class:dispatch(State, Msg). %% this uses apply/3 instead of funs

then:

O = myclass:new().
O1 = oo:msg(O, {append,TheThingy}).
O2 = oo:msg(O1, print).
...


Useless exercise?

Perhaps. What you gain is that you avoid the negative aspects (bugs?) of
funs, at little syntactical expense. I have written code like that.
Sometimes, I find it justified (when the OO paradigm really adds
advantages), but most of the time, it just makes the code harder to
read, harder to write, and harder to maintain.

So what do I propose?

Look at modules like:

lists (lists:append(L1, L2), lists:reverse(L), ...)
queue (queue:new(), queue:in(Q, Item), queue:out(Q), ...)
application (application:start(Name), application:stop(Name), ...)

and so on. The big advantage of this style of programming is that
programs become much easier to read, since the semantics are obvious.
You still get a great deal of reuse, and your productivity will be very
high.

I admit I haven't the faintest idea of what kind of program you're
trying to write. Perhaps your problem domain really calls for
object-oriented design?

If so, forgive me, but it's very common for programmers new to Erlang to
go overboard trying to make everything object-oriented.

/Uffe
--
Ulf Wiger, Chief Designer AXD 301 <ulf.wiger_at_etxb.ericsson.se>
Ericsson Telecom AB tfn: +46 8 719 81 95
Varuv
pearcs at rpi.edu
Posted: Wed Jan 27, 1999 8:47 pm Reply with quote
Guest
Ulf Wiger <ulf.wiger_at_etxb.ericsson.se> wrote:
> Personally, I'd go with 'print' and 'empty' instead of tuples with only
> one element. You don't have a type checker that's gonna complain -
> Erlang really couldn't care less, but your match operation speeds up
> somewhat. A matter of preference, I guess.
>
> >
> > What are the drawbacks, ie is the fun going to cost me a large amount of
> > RAM, or a large amount of processing overhead on each message?
>
> Funs don't cost much in terms of memory. You can look at how a fun is
> represented:
>
>
> -module(myclass).
> -export([new/0]).
>
> new() ->
> F = save_state([]),
> tuple_to_list(F). %% hack to reveal the representation of a fun
>
> save_state(State) ->
> fun(Message) ->
> myclass:dispatch(State,Message)
> end.
>
> If you compile and run:
>
> 1> myclass:new().
> ['fun',myclass,0,7662692,{[]}]
>
> That is, a 5-tuple. The rest is compiled and represented as one
> instance.
>
> The processing overhead is roughly the same as for apply/3. A few
> microseconds per call.
>
> On the minus side:
> In the current JAM implementation, error reporting for funs is lousy and
> code change on-the-fly doesn't work well (the fun's internal reference
> changes and the fun instance has to be recreated.)
>
> Apart from this, I have found that it's most often counter-productive to
> use an object-oriented style of programming in Erlang. Personally, I
> spent my first 6 months with Erlang trying to make it object-oriented.
> After that, I had convinced myself that you get further by using Erlang
> as it was intended (granted, we didn't have funs in those days...)
>
> You could do a similar thing without funs, to begin with:
>
> -module(myclass).
> -export([new/0,
> dispatch/2]).
>
> new() -> [].
>
> dispatch(State, print) -> io:format("~p~n", [State]);
> dispatch(State, {append,Item}) -> State ++ Item;
> dispatch(State, get) -> State;
> dispatch(State, empty) -> [].
>
> then:
>
> O = myclass:new().
> O1 = myclass:dispatch(O, {append,TheThingy}).
>
> etc:
>
> You may argue that the object-oriented aspect got lost:
>
> -module(myclass).
> -export([new/0,
> dispatch/2]).
>
>
> new() ->
> {?MODULE, []}.
>
> dispatch(State, print) -> io:format("~p~n", [State]);
> dispatch(State, {append,Item}) -> State ++ Item;
> dispatch(State, get) -> State;
> dispatch(State, empty) -> [].
>
> -module(oo).
> -export([msg/2]).
>
> msg({Class, State}, Msg) ->
> Class:dispatch(State, Msg). %% this uses apply/3 instead of funs
>
> then:
>
> O = myclass:new().
> O1 = oo:msg(O, {append,TheThingy}).
> O2 = oo:msg(O1, print).
> ...
>
>
> Useless exercise?
>
> Perhaps. What you gain is that you avoid the negative aspects (bugs?) of
> funs, at little syntactical expense. I have written code like that.
> Sometimes, I find it justified (when the OO paradigm really adds
> advantages), but most of the time, it just makes the code harder to
> read, harder to write, and harder to maintain.
>
> So what do I propose?
>
> Look at modules like:
>
> lists (lists:append(L1, L2), lists:reverse(L), ...)
> queue (queue:new(), queue:in(Q, Item), queue:out(Q), ...)
> application (application:start(Name), application:stop(Name), ...)
>
> and so on. The big advantage of this style of programming is that
> programs become much easier to read, since the semantics are obvious.
> You still get a great deal of reuse, and your productivity will be very
> high.
>
> I admit I haven't the faintest idea of what kind of program you're
> trying to write. Perhaps your problem domain really calls for
> object-oriented design?
>
> If so, forgive me, but it's very common for programmers new to Erlang to
> go overboard trying to make everything object-oriented.

I'm one of the authors of the Casbah Project, an open source project,
(http://www.ntlug.org/casbah/). Our entire system is very much object oriented
based, and has its "server" currently written in Java. We are looking at also
implementing our server in Erlang, for obvious reasons. The server is a very
"pluggable" architecture, but its all object-oriented based. A language like
Objective-C would be ideal for building our server, as it is very dynamic in
its object-dispatch mechanism, etc.

Essentially parts of the system will need to invoke code on some plugin module
whose name isn't known until runtime, and even then will change on invoctions.
I know that pretty much breaks erlang's ideas, but not that much: because if
we pass in the same arguments, we'll still expect it to always return the same
result, it will just alter its behavior based on that input. So if we pass in
an item of class NatrounFS, it should do a, if its of class NatrounSQL, it
needs to execute code b.

I didn't realize that the code example you showed above would work, and doh! I
should have thought about using oo:msg() to wrap around apply/3 rather than
using a fun.

We'll implement each class in its own module, but we really sort of need the
ability to have methods dispatched based on the class at runtime, rather than
at the time of the coding.

Thanks for the suggestions!


--
Shawn.

(The above are the rantings of a body without a mind.)



Post generated using Mail2Forum (http://m2f.sourceforge.net)
mbjk at eecs.umich.edu
Posted: Wed Jan 27, 1999 9:08 pm Reply with quote
Guest
Shawn Pearce <pearcs_at_rpi.edu> wrote:

> Essentially parts of the system will need to invoke code on some
> plugin module whose name isn't known until runtime, and even then
> will change on invoctions.

This is a pretty common situation in generic Erlang programs - use a
callback module (which may change dynamically) and call a certain
function in that module at certain occasions. One example is the
module gen_server, which implements a generic server process. The
server is given the name of a callback module when the process is
started. For each request the server receives, a function in the
callback module is called:


-module(gs).

start(Mod) -> spawn(gs, init, [Mod]).

init(Mod) ->
InitState = Mod:init(),
loop(Mod, InitState).

loop(Mod, State) ->
receive
{request, From, Req} ->
{Reply, NewState} = Mod:handle_request(Req, State),
From ! Reply,
loop(Mod, NewState);
{change_module, NewMod} ->
loop(NewMod, NewState)
end.


-module(callback1).

init() -> [];

handle_request({append, Item}, State) ->
{ok, State ++ Item};

handle_request(print, State) ->
io:format("~p", [State]),
{ok, State}.


etc.



> We'll implement each class in its own module, but we really sort of need the
> ability to have methods dispatched based on the class at runtime, rather than
> at the time of the coding.

Mod:handle_request(Mod) is an example of that. It is just a syntactic
sugar for apply(Mod, handle_request, [Mod]).

Also, the function name could be dynamic:

Mod:Fun(...) works just fine.



/martin


Post generated using Mail2Forum (http://m2f.sourceforge.net)
joe at erix.ericsson.se
Posted: Thu Jan 28, 1999 9:52 am Reply with quote
Guest
Hi Shawn, let me comment on your program line by line ...

> Ok, we're looking at implementing a system in Erlang where it would be nice to
> have true object-oriented behavior. The way this is typically done in scheme
> is to make each object a lambda and bind the object state to the lambda when
> you construct the object. Thus you can do "(object print)" to make it print
> out stuff.
>
> I'm thinking that we'd do something similar in Erlang like so:
>
> -module(myclass).
> -export([new/0]).
>
> new() -> save_state([]).
>
> save_state(State) ->
> fun(Message) ->
> myclass:dispatch(State,Message)

Better might be:

?MODULE:dispatch(State, Message)

?MODULE is a pre-defined macro whoes value is the current module
If you do it this way and decide to change the name of the module
then the program will still work! If you bake the name in and at a
later state decide to change the module
name (which I'm always doing), you'll
probably forget to change the call

> end.
>
> dispatch(State,{print}) -> io:format("Hi!");
> dispatch(State,{append,Item}) -> save_state([Item|State]);
> dispatch(State,{get}) -> State;
> dispatch(State,{empty}) -> save_state([]).
>

OOOps - Erlang is dynamically typed. But you should always "think types"

> dispatch(State,{print}) -> io:format("Hi!");

The return value is the atom ok

> dispatch(State,{append,Item}) -> save_state([Item|State]);

The return value is a Fun

> dispatch(State,{get}) -> State;

The return value is a list of Items

> dispatch(State,{empty}) -> save_state([]).

The Return value is a Fun.

Code like this might one day bite you in the tail.

Also why use {get}, {print} etc. {..} is a tuple construtor
This is like building a C struct and putting ONE value in it. C structs
and Erlang tuples are designed as holders for multiple values, not singletons.
So always use X instead of {X}.


> Then I can work with the object like this:
>
> O=myclass:new().
> O1=O({append,TheThingy}).
> O1({print}).
> O2=O1({empty}).
>
> What are the drawbacks, ie is the fun going to cost me a large amount of RAM,
> or a large amount of processing overhead on each message?
>

No. Funs involve very small overhead but they do make the program
shorter and easier to read.

Before hacking yourself to death I'd read the "design principles"
section in the on-line documentation. This is in:

http://www.erlang.org/doc/doc/design_principles/part_frame.html

Or wherever you've installed it locally.

(I'd recommend you take the *entire* documentation and install it
locally)

This tells how to set up a client/server in the "Erlang way" which is
not OO :-)

[BTW In making a system the tricky bit is concurrency/load balancing/
fault-tolerence/fail-over etc.]

The generic methods in the OTP libraries do the tricky bits. All you
have to do is plug in the "semantics". These are small functions, like:

Fun(State, Request} -> {reply, State', Reply}

Then you get the tricky bits for free. If you do the
small functions in an OO style then you should make sure that
match up with the OTP libraries so you get the fault-tolerence for free.


/Joe
--
Joe Armstrong Computer Science Laboratory +46 8 719 9452
AT2/ETX/DN/SU Ericsson Telecom SE-126 25 Stockholm Sweden
joe_at_cslab.ericsson.se http://www.ericsson.se/cslab/~joe




Post generated using Mail2Forum (http://m2f.sourceforge.net)

Display posts from previous:  

All times are GMT
Page 1 of 1
This forum is locked: you cannot post, reply to, or edit topics.

Jump to:  

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You cannot download files in this forum