Structs (was RE: Record selectors)

Chris Pressey cpressey@REDACTED
Thu Jan 16 23:28:56 CET 2003


On Wed, 15 Jan 2003 09:18:38 +0100
"Vlad Dumitrescu (EAW)" <Vlad.Dumitrescu@REDACTED> wrote:

> Hi Chris,

Hey :)  Sorry if this reply is a bit rambling, it prompted me to get down
some of my thoughts.

> >Forgive me, but is Erlang really so object-shy that encapsulation is
> >something that we feel we can afford to avoid simply because "they"
> >have turned it into a meaningless buzzword?
> 
> I am by no means an Erlang guru, but I was once for not so long ago just
> as puzzled by the same question. Now I got used to the "hard core"
> Erlang way and it works just as well.

I can appreciate that, but I've gone back and forth a bit lately.

OO is a weirdly nebulous school of thought... there's a tendency to fall
into a false dilemma of "either you worship it or you despise it", while
it's of almost no value to say "this language is OO but this one isn't",
so some deconstruction is probably in order.

>From the OO literature I've read, three tenets seems to be universally
agreed upon: encapsulation, polymorphism, and inheritance.  Two others get
mentioned often enough, abstraction and identity, but they seem less well
understood.

I can live without inheritance - when it is called for, it's rarely more
than one level deep (any deeper than that usually stems from an almost
Platonic desire to categorize everything perfectly, which strikes me as
doomed.)  Erlang's behaviours accomplish the equivalent of one level of
inheritance beautifully, and so much more too.

Polymorphism pretty much comes "for free" in any dynamically-typed
language, so it's not really worth worrying about.

Abstraction seems to take on a special meaning to some OO folks - I have a
hard time distinguishing it from encapsulation 90% of the time.  My
feeling is that if something has been encapsulated, then it's also been
abstracted, so it's also not worth worrying much about.

Identity is similarly subjective - you could argue that this 5 doesn't
have a distinct identity from this 5, therefore integers are not objects,
or, you could argue that those two 5's do have distinct identities because
they appear in different places in this paragraph.  It depends on the
model.  Either way, identity issues are easy enough to implement by using
references or names or other keys into an ETS table or something similar
(and you can address mutability issues at the same time.)

Which leaves us with encapsulation, which has been around a lot longer
than OO of course, and can be seen in lots of places - local variables,
namespaces, even protected memory is a form of encapsulation at the
hardware level.  I consider it to be the most important of these concepts
(although it is of course still possible to overdo it)

> To make sure we are referring to the same thing: encapsulation is one
> issue, having support from the compiler & run-time for a nice syntax and
> better efficiency is another.

Yes.  If encapsulation isn't provided (as is the case with records), I
find it important enough that I'll roll my own.  (I can't really roll my
own syntax or implementation without a lot of ugliness.)  My point
regarding structs is that if I still have to roll my own encapsulation "on
top" of them, the net gain from using them instead of records is very
small.

> In fact, in Erlang one can use
> encapsulation on two levels: by writing wrapper functions and by using a
> server process. One can also use both (even if I remember someone
> arguing that it is better to write "Pid ! Msg" than encapsulate it in a
> function call, because the former does not hide that there is a process
> doing the work).

That was me.  I should take this opportunity to clarify, though.

If you think that, syntactically, '!' implies a probable change of state
(as in ServerPid ! do_something), while '()' implies no change of state
(as in math:sin(Theta),) then encapsulating a message send with a
wrapper function makes code less clear.  To make things clearer, a message
send (or anything else that changes state or otherwise has side-effects)
should be encapsulated with a wrapper *process*.

In practical terms this is somewhat expensive, since you now have two
processes per server, one of which is only there to translate messages
from the external format used by the client to the internal format used by
the server.

> The latter issue is at stake, together with the expressed need to have
> some kind of type checking. If there is a way to get the best of both
> worlds without paying too much for it, then it will be found and
> implemented. 
> 
> As for objects, I think processes can implement the underlying concept
> better than for example C++ objects. Hey, one could even think of
> compiling today's records (or tomorrow's mutable coagulators :-) into
> processes! The big minus is efficiency, but the coolness factor might be
> bigger :-)

It's not as absurd as it sounds!  I'll put it this way: if you're going to
do this, then Erlang is definately the language to do it in.

An example of this sort of 'use processes for *everything*' approach might
be a project I want to try sometime this year, that is, to write a digital
circuit simulator in Erlang.  Instead of messing with nasty physics
equations, I think the problem could be modelled with one process per gate
(or chip). Each chip process would receive a message every time one of
it's pins changed state, so messages would essentially look like {N, M}
where N is the pin number and M is either rising_edge or falling_edge. 
The process itself would take care of 'steady' state by tracking it every
time a new message came in, and would also produce new messages to send to
other chips wired to its output pins.

Doing this with one process per truly concurrent unit should make it
simple (almost trivial) to implement - but the thought of doing it this
way with any language besides Erlang gives me shivers.

But (again in regards to structs) there are still going to be cases where
you have basically 'passive' data structures that don't have any dynamic
behaviour, so modelling them as processes *is* going to be wasteful.  I do
grasp that the Erlang Way is to do validation is aggressively, by writing
code for the correct case and then just letting it crash (and be caught
from on high) when things don't work out as planned - but in the case of
passive data structures, you still need some sort of entry point where
you can describe the correct case (with guards) - currently I use wrapper
functions for this purpose, but if that entry point can be put on the data
structure itself, all the better (I dimly recall suggesting guards on
records once, a long time ago - it's the same basic idea.)

-Chris
not a guru, but possibly a bishop-errant or something



More information about the erlang-questions mailing list