Groovy
Jarrett Billingsley
kb3ctd2 at yahoo.com
Mon Jan 1 08:59:04 PST 2007
"bls" <killing__Zoe at web.de> wrote in message
news:enb0r2$51f$1 at digitaldaemon.com...
> Suneido supports Smalltalk style "blocks". Basically, a block is a section
> of code within a function, that can be called like a function, but that
> operates within the context of the function call that created it (i.e.
> shares its local variables).
Nested functions and delegate literals in D do (almost, see below) the same
thing. Access to outer locals is a very useful feature indeed.
> Blocks can be used to implement user defined "control constructs". (In
> Smalltalk, all control constructs are implemented with blocks.) For
> example,
> you could implement your own version of "foreach":
>
> for_each = function (list, block)
> {
> for (i = 0; i < list.Size(); ++i)
> block(list[i])
> }
> list = #(12, 34, 56)
> for_each(list)
> { |x| Print(x) }
> => 12
> 34
> 56
What's funny about this example is that this is _precisely_ how D handles
iteration with foreach, except that it's hidden behind loop-like syntax.
The body of a foreach loop is converted into a nested function and passed as
a callback to the opApply for the given container.
> Suneido treats a block immediately following a function call as an
> additional argument.
This is a really cool idea that some people (myself included) would like to
see in D. The closest thing we have now is:
for_each(list, (x)
{
writefln(x);
});
> Blocks can also be used to execute sections of code in specific
> "contexts".
> For example, the Catch function traps exceptions and returns them. (This
> is
> useful in unit tests to verify that expected exceptions occur.)
>
> catcher = function (block)
> {
> try
> return block()
> catch (x)
> return x
> }
> catcher( { xyz } ) => "unitialized variable: xyz"
Exception catcher(void delegate() dg)
{
try
{
dg();
}
catch(Exception e)
{
return e;
}
return null;
}
...
Exception e = catcher
({
someCode();
});
> But the interesting part is that a block can outlive the function call
> that
> created it, and when it does so, it keeps its context (set of local
> variables). For example:
>
> make_counter = function (next)
> { return { next++ } }
> counter = make_counter(10)
> Print(counter())
> Print(counter())
> Print(counter())
> => 10
> 11
> 12
> In this example, make_counter returns a block. The block returns next++.
> You
> see this type of code in Lisp / Scheme.
This is called a static closure, and is something D's nested functions don't
do. The workaround involves manually creating a context using a struct or
something, and returning a bound delegate using an instance of that
aggregate allocated with new:
int delegate() makeCounter(int next)
{
struct Context
{
int next;
int func()
{
return next++;
}
}
Context* ctx = new Context;
ctx.next = next;
return &ctx.func;
}
...
auto counter = makeCounter(10);
writefln(counter());
writefln(counter());
writefln(counter());
This is basically what Suneido is doing for you behind the scenes. It seems
like a relatively simple feature to implement, but there are many issues to
consider. One, when do you allocate the context for the delegate? When it
leaves the function? How do you know that it isn't assigned to somewhere
else and not returned? So you allocate it when it's declared, but then you
lose the ability to access the outer function's copy of the locals, and you
also lose performance if the delegate never needs to be returned, so you're
allocating an unnecessary context with every call to the outer function.
Then there's the problem of nested functions in nested functions accessing
local variables from more than one level of nesting... etc. etc. etc.
Please don't take this post to mean that I'm shooting down your ideas! I'm
just trying to show some of the features of D which are something like what
you've demonstrated.
More information about the Digitalmars-d
mailing list