[M3devel] opaque types again

Rodney M. Bates rodney_bates at lcwb.coop
Sun Mar 31 18:50:14 CEST 2013



On 03/31/2013 12:10 AM, Jay K wrote:
> I don't understand opaque types.
>

It can be tricky.

>
> I do understand the notion of fully opaque types. That are fully revealed in one step.
> In C this is common:
>
>
>    window.h
>      struct Window_t;
>      typedef struct Window_t Window_t;
>      Window_t* Window_Create();
>      void Window_Show(Window_*t);
>      void Window_Close(Window_*t);
>      long Window_GetHeight(Window_*t);
>      long Window_GetWidth(Window_*t);
>    etc.
>
>    window.c
>      struct Window_t { ... } /* reveal */
>
>
> Back to Modula-3... opaque types must be OBJECTs, right?
>

It's slightly more liberal than that.  They can be any reference type, which
includes object types and REF Mumble, for any Mumble.  But since the subtype
hierarchy has only two levels for REF types (REFANY and REF Mumble), you can
only get fully opaque and fully revealed, as in the C example above.

>
> 1. What do they provide vs. more derived types?
>
>
> INTERFACE Animal;
>
> TYPE T = OBJECT
> METHODS
>      makeNoise();
> END;
>
>
> INTERFACE AnimalImpl;
>
> TYPE T = Animal.T OBJECT
> METHODS
>      private();
> END;
>
>
> If I had an Animal.T and wanted to call private(),
> wouldn't I just NARROW it to AnimalImpl.T?
>

If you allocate VAR V : Animal.T := NEW (Animal.T), the result has allocated type Animal.T, not AnimalImpl.T.
So it has no method private, and even with AnimalImpl imported, trying to narrow it to AnimalImpl.T will
suffer a runtime error.

If you do it this way: VAR V : Animal.T := NEW (AnimalImpl.T), V still has static type Animal.T, but
its runtime value is (for now, at least) an object with allocated type AnimalImpl.T, which has method
private, and you can thus narrow it to AnimalImpl.T and call private.

>
> Is the point that it is more efficient and avoids
> a runtime type check?
>

That's part of it.

>
>
> INTERFACE Animal;
>
> TYPE Public = OBJECT
> METHODS
>      makeNoise();
> END;
>
> TYPE T <: Public;
>
> INTERFACE AnimalImpl;
>
> REVEAL Animal.T = Animal.Public OBJECT
> METHODS
>      private();
> END;
>
>

Here, even in a context where it is not revealed, if you allocate VAR V : Animal.T := NEW (Animal.T),
the allocated type has method private.  Note that Animal.T here is the same type as AnimalImpl.T is
in the first example.  You just don't know all there is to know about what type that is.

So the object still sits in the heap with all the fields and methods of the final type.  If you
import AnimalImpl, this gives more (static) information about the same type Animal.T instead of just
a different type.  So the revelation would make it statically legal to call V.private(), without needing a
narrow and without a runtime check.  It is statically known everywhere Animal is imported, that
objects of Animal.T have all the properties of the final revealed type, even though it's not knowy what
they all are.

Of course, you could also do this: VAR W : Animal.Public := NEW (Animal.Public), and that would
behave just like Animal.T of the first example, since those are the same type.  Usually, you would probably
not want to use Animal.Private in that way.  It's something like a variation on the theme of an abstract
supertype that you will never allocate.

Sometimes I think it would help clarity if you could define a reference type as ABSTRACT, meaning it's
illegal to allocate it.  But that might open a can of worms in the language.  Hmm, maybe I'll work on
remembering to write (*ABSTRACT*) OBJECT ... I think I even did that once.

> ?
> This way I can import AnimalImpl and then just call private()
> without a NARROW?
>
>

Yes

>
>
>
> 2. Given that the final revelation must be a linear
> form of all the declared subtypes, I don't understand
> how the offset used by any module w/o a full revelation
> could be anything other than zero.
>

Not sure this helps much, but a complication comes from the fact that you can declare a truly new subtype
(not just reveal more about an existing opaque type), in a context where the opaque type is not fully revealed.

IMPORT Animal (* The second version of Animal above. *)
TYPE Cat = Animal.T OBJECT
    IsDeclawed : BOOLEAN
    END;

The compiler will not know while separately compiling this, the offset, relative to Animal.T, where new fields of
Cat will start.  I have never examined the way this is implemented.  I would think a typical linker's relocation
or external symbol mechanism could be used to add to the offset, a component that is defined in another compilation.
I think Tony understands how this is implemented.  I have noted that compilations sometimes tell us they are
recompiling some module because new opaque information has become available.

>
> I'd like to fully understand this, so I can make the C backend
> provide maximal type information. More pointers to structs, with members/fields,
> fewer untyped void*/char*.
>

If you are hoping to express Modula-3's actual static information hiding in C, I suspect that is a Quixotic quest.
At the very least, you would have to replace what is really a flat struct with lots of nesting of structs for
the different static visibility groups, which would just hurt readability elsewhere, in references to fields.
By definition, it is a lower-level form of code here.  I'd just arrange to get the full, flat struct available
anywhere it is needed, and rely on the fact that the M3 front end will already have refused to pass down any
references to fields that aren't statically legal in the M3 source code.  There is certainly no need to get
the C compiler to redundantly enforce this.

>
> Thanks,
>   - Jay
>
>




More information about the M3devel mailing list