[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