Messaging vs. subroutines

Joachim Durchholz joachim.durchholz at munich.netsurf.de
Wed May 19 19:34:43 UTC 1999


Marcel Weiher wrote:
> 
> [broadcast, multicast]
> > Well, I'm talking and thinking about Smalltalk-80. I'm not aware of
> > anything that one could call broadcast or multicast there (but I
> admit I
> > may have overlooked it - my understanding of Smalltalk goes back to
> > the colored books, and the language may have changed since then
> > despite surface appearance).
> 
> Once again, Smalltalk-80 contains a very specific implementation of
> the messaging concept, but the concept is more general.  So, with
> what is available present in ST-80, you have to implement broadcast +
> multicast on top of the built-in mechanism.  But, as you pointed
> out, this is just an implementation issue and doesn't affect the
> concept itself.

Well, nothing in the ST-80 literature has introduced anything beyond
simple, synchronous message sends. At least not the literature and
Squeak code that I have seen (and from what I gather there is no
asynchronous messaging in Squeak going on because it's just a ST-80
implementation without asynchronous messages).
If that is indeed the case, the point of asynchronous messages is moot.
I could equally well claim OO-ness for C because the C standardization
committee is thinking about adding OO elements to C itself (this is a
different effort from the C++ effort BTW).

> [ multiple returns ]
> > Ah, now I understand.
> > And I don't think it's a good idea (it introduces lots of state that
> > can get out of sync with outside expectations), but that's just
> > generally speaking; good language design may be able to harness such
> > problems.
> 
> Exactly!  With messaging, this works very nicely without introducing
> 'lots of state'.

I disagree (see below).

> > > For example, an iterator that 'returns' the elements of its
> > > collection:
> > >
> > > contents
> > >         self do:[ :each | ^^each ].
> > >
> > > " ^^ stands for 'return but continue executing' "
> 
> Take this routine.  It is compact and runs on its local variables,
> 'returning'  ( -> sending back ) values to a caller (which would bind
> the return values by repeatedly 'receiving').  I don't see where
> this introduces 'introduces lots of state that can get out of sync
> with outside expectations', because all the state is local to the
> routines and no outsider has access to it.

The state is not in the routine itself but in the caller. The caller has
to be *very* careful not to modify the state unless it is really through
with the current state of the coroutine object. It has no way of going
back, which would be extremely handy for, say, implementing a loop that
enumerates all pairs from the sequence.
The property that such routines are missing is "referential
transparency". Referential transparency means that you can use the same
call as often as you like, or replace the call with its result, and
always get the same final result. Coroutines as stream generators are
the exact opposite.

> > Conceptually, such things are more clearly implemented via lazy
> > evaluation: expressions are not evaluated until their result is
> > required. This does not introduce additional state into the system,
> > and it gives you the power to express such things.
> 
> Sorry, I don't see how this could be implemented nearly as easily as
> the solution I sketched above.  Not even close.

Easily for the application programmer or the system programmer?
Functional programming people say it's quite easy to define all sorts of
sequences this way, for the application programmer.

> > Here's how: Write a function that returns an infinite sequence (this
> > is easy to do via recursion; a loop won't do here).
> 
> ^numbers
>         | number |
>         number :=1;
>         true whileTrue: [ ^^number. number:=number+1. ].

No. What I mean is

  ^numbers
    | number |
    ^ number cons: ( numbers + 1 )

numbers 1 will return

  1 cons: (2 cons: (3 cons: .... )))

i.e. it will return an infinite list *at once*, conceptually. In
reality,
  (numbers 1) at: 3
will evaluate that infinite sequence only up to and including the third
element, and will not try to evalutate the ellipsis in the example above
(after all, there's no need to evaluate it: nobody has requested
  (numbers 1) at: 15
yet...)

> > Write an at: selector
> > that retrieves values from the such a sequence. Calling
> > myInfiniteSequence at: 3 will evaluate myInfiniteSequence just
> > enough that at: finds the value that it has to return.
> 
> You are glossing over a *lot* of implementation magic that must be
> present to support what you are trying to do, and I don't see how it
> is in any way clearer than the simple co-routine iterator.

Right. Functional programming researches ways of doing this in an
efficient way. However, things have become quite usable in the last
decade, it seems; a team has written a TCP/IP stack in a functional
language; they claim it was as fast (or even faster) than a commercial
TCP/IP stack. (The problem with TCP/IP is that while the layers are
clearly separated in theory, you have to integrate everything to make
the thing fast; functional programming allowed them to write fast code
without munging all the layers into a monolithic, bloated,
unmaintainable code monster like commercial stacks.)

> What happens when I call at:4 after calling at:3, does everything
> get re-evalutated?

That depends on the quality of the compiler and/or the run-time system.
Ideally, the answer is yes. But I don't know enough about compiler
technology for functional languages to tell you how good they really
are. My impression is that they are finally becoming good enough for
everyday use, but I may be wrong either way.

> > There's a caveat here: I lied a bit when I said this does not
> > introduce additional state. The expressions may go into a Heisenberg
> > state if you start modifying variables that go into an expression.
> > You're better off if you make as much stuff constant as you can if
> > you want to use this style of evaluation.
> 
> The infinite iterator cannot be disturbed (except by
> meta-programming) because all its state is local, the collection
> iterator could make a local copy and be equally protected.

As I wrote above, the state is in the caller. This is true regardless of
whether you use a coroutine or a lazily-evaluated list.
If the coroutine object is passed around, you can get into *very* ugly
problems - and subtle ones as well: typically, a method three calls away
from the original site won't know about the fact that somebody else
still depends on the fact that the sequence is undisturbed... happy
bug-hunting...
This is just the same with the non-modification of local variables that
go as parameters into a lazily-evaluated expression.

> > Another caveat: I'm not sure how much work applying these ideas to
> > Smalltalk is. I suspect Smalltalk is not built for accommodating
> > them.
> 
> You suspect wrong.  Messaging in general and Smalltalk in particular
> are quite suited to this style of programming.

Which style? Coroutines or lazy evaluation?
I know one can use blocks to simulation lazy evaluation. But from what I
gather in the FP world, this is similar to emulating message sends via
function pointers in C: Possible, but cumbersome and too circuitious to
be useful in practice. (I may be wrong of course; I'm not an expert
neither in Smalltalk nor in functional programming. I'm just reporting
what I have heard on several occasions.)

Regards,
Joachim
-- 
Please don't send unsolicited ads.





More information about the Squeak-dev mailing list