[Oberon] Oberon and closures
Alexander Iljin
ajsoft.gm at gmail.com
Thu Oct 30 10:54:38 MET 2008
Hello!
BSM> Well, no, not really. The Module being a single giant closure over
BSM> all the procedures defined in it is *not* what is generally meant by
BSM> "closure". In particular, languages which support closures generally
BSM> allow something like this:
BSM> MODULE Example;
BSM> (* my oberon's a little rusty *)
BSM> TYPE
BSM> Printer = PROCEDURE();
BSM> VAR
BSM> p, q: Printer;
BSM> PROCEDURE CreateNumberPrinter(n: INTEGER): Printer;
BSM> PROCEDURE PrintingProcedure;
BSM> BEGIN Out.WriteInt(n)
BSM> END PrintingProcedure;
BSM> BEGIN
BSM> RETURN PrintingProcedure
BSM> END CreateNumberPrinter;
BSM> BEGIN
BSM> p := CreateNumberPrinter(1);
BSM> q := CreateNumberPrinter(2);
BSM> p; (* prints 1 *)
BSM> q; (* prints 2 *)
BSM> END Example;
BSM> Note that each invocation of CreateNumberPrinter creates a new closure
BSM> over the nested procedure PrintingProcedure. In fact the fact that
BSM> Pascal/Modula/Oberon only allows top-level procedures as procedure
BSM> values appears to be a direct result of the fact that the language
BSM> doesn't support proper closures. (In order to support closures as
BSM> above you need something more involved than a simple stack as an
BSM> instance of PrintingProcedure needs to find its "n", even after the
BSM> CreateNumberPrinter that created it is no longer active on the stack).
You can use virtual methods (AKA type-boud procedures) instead
of procedure variables to achieve similar functionality. The problem
with the example above is that no persistent memory storage is
allocated for different values of 'n'. Simply passing a value as a
parameter allocates the memory on stack, and that is automatically
released when the procedure terminates, hence it is not persistent.
MODULE Exapmle;
TYPE
Printer = RECORD (* closure context description *)
n: INTEGER
END;
VAR
p, q: Printer; (* memory for 'n' is allocated within the RECORD *)
PROCEDURE (VAR prn: Printer) PrintingProcedure;
(* type-bound procedure (AKA method) executed in the 'prn' context *)
BEGIN Out.WriteInt(prn.n)
END PrintingProcedure;
PROCEDURE CreateNumberPrinter(n: INTEGER): Printer;
VAR res: Printer; (* result *)
BEGIN
res.n := n; (* initialize the context *)
RETURN res
END CreateNumberPrinter;
BEGIN
p := CreateNumberPrinter(1);
q := CreateNumberPrinter(2);
p.PrintingProcedure; (* prints 1 *)
q.PrintingProcedure; (* prints 2 *)
(* ALTERNATIVELY *)
p.n := 1;
q.n := 2;
p.PrintingProcedure; (* prints 1 *)
q.PrintingProcedure; (* prints 2 *)
END Example;
Therefore, if you don't like the idea of a module being a single
static closure over all the procedures, you can describe the context
of your closure in a RECORD variable with all the required fields,
and use type-bound procedures to execute the same actions in
different data contexts.
Of course, you can make Printer a POINTER TO RECORD instead of
RECORD, if you want to be able to have 'uninitialized' value (NIL)
for 'p' or 'q'. Then you'll have to allocate instances of your
contexts using the NEW standard procedure.
---=====---
Alexander
More information about the Oberon
mailing list