[M3devel] pixmap problem (some success)

Jay jayk123 at hotmail.com
Sat Aug 9 20:41:36 CEST 2008


Thanks Rodney, good summary. I have been "almost aware" of much of this.I didn't think you could derive from opaque types for some reason.
So I instead relied on "gentleman's agreement", naming things "private".
Like how Modula-3 doesn't have constructors, but relies on people to call "init".Randy, go ahead and change what you want. I don't like red tape myself.
  You're welcome and definitely thanks to David.
Regarding pickles, are we to consider all types as likely to be pickled?
And therefore we are very stuck unable to change?Or only branded types? Or ...?
Or we don't believe people build up significant investment in pickle files
and can throw them all away upon rebuilding their code from current source?
 
This change could be restarted without breaking pickles.
As it is currently stated, it deliberately breaks existing code in order to
be certain the fix works, to be absolutely sure the wrong values aren't used.
That is, I didn't want to rely on any particular preexisting function call
being made before the values are read. There is no way to do that
given the "fully unprotected" form that was there before.
 
Agreed -- layering is great for design, and "agility" (ability to change),
but more code = more bugs, more code = more code to read when debugging,
more code = more code to understand when maintaining.
 
I don't think you need to know the size of things at the NEW site.
In a general "if I were designing the system" sense, not in the
"I know how Modula-3 works" (I don't yet).
NEW(T) could include within it a function call, or global read, to get the size of T.
Or heck, NEW(T) could call a function that passes the size on to
the next level down.
That is, in C, it could look like one of:
 
original code:
client:
 T* t = NEW(T) 
 
option 1 - function call
client:
T* t = (T*) malloc(GetSizeOfT());
 
implementation:
size_T GetSizeOfT() { return 42; }
 
option 2 - global read 
client:
T* t = (T*) malloc(SizeOfT);
 
implementation:
extern const SizeOfT = 42;
 
option 3 - 
client:
T* t = NewT();
 
implementation:
T* NewT(void) { return (T*) malloc(sizeof(T)); }
 
The compiler could go back later and recompile things to
client, or sometimes it could do this the first time:
t = (T*) malloc(sizeof(T));
 
Though option 3 potentially generates smaller code, if there are
many places that make a T.
This is just the usual "inlining can bloat code size" point.
"Make functions out of frequently occuring blocks of code
to reduce code size".
 
Option 3 becomes "better" as there are more used default
field initializers.
That is:
T = RECORD
  a := 1; b:= 2; c:=3; d:=4;e:=5;f:=6;
END;
 
If there are lots of NEW(T), without
replacements for the initial values (not sure you can do that,
but I think so), then one function to call malloc and fill in
all the values could be a win over inlining the initialization
everywhere, due to code size.
 
Code size is usually important for performance.
You want the code to be small to fit in cache.
And to reduce the cost of paging it in.
 
 - Jay> Date: Sat, 9 Aug 2008 10:45:45 -0500> From: rodney.bates at wichita.edu> To: m3devel at elegosoft.com> Subject: Re: [M3devel] pixmap problem (some success)> > > > Jay wrote:> > I committed a possible fix here.> > Please see how it fairs.> > > > I have a few Modula-3 questions related to the fix.> > > > - Did I have to expose the functions in the .i3 file> > that implement the methods? That seems "wrong".> > > > > > - Could I have used "stronger language opacity" instead> > of "informal privacy"? That is, could I have used an> > opaque type?> > I have a number of comments on this.> Here are 3 examples of different styles of coding in Modula-3.> > ---------------------------------------------------------------------------------> INTERFACE M1> ; TYPE T <: REFANY> ; PROCEDURE Op ( Arg : T )> ; END M1 .> > MODULE M1> ; REVEAL T = BRANDED REF RECORD (> * Fields of T, hidden from clients. *)> END (* T *)> ; PROCEDURE Op ( Arg : T ) = (* A body that uses the fields of T. *)> ; BEGIN END M1 .> > ; MODULE Client1 EXPORTS Main> ; IMPORT M1> ; VAR Obj := NEW ( M1 . T )> ; BEGIN> M1 . Op ( Obj )> END Client1 .> > I would call this the _abstract data type_ style. It uses a plain procedure Op,> not a method. The type T is opaque in the interface, so clients can manipulate> values of it only by calling Op (and, presumably, other procedures whose signatures> would also be given in the interface).> > -------------------------------------------------------------------------------> INTERFACE M2> ; TYPE T <: Public> ; TYPE Public = OBJECT METHODS op ( ) (* No default method body given here. *)> END (* Public *)> ; END M2 .> > MODULE M2> ; REVEAL T = Public BRANDED OBJECT (* Same fields as M1.T. *)> OVERRIDES op := Op (* Provide the method body for op. *)> END (* T *)> ; PROCEDURE Op ( Arg : T ) = (* Pretty much the same as M1.Op, maybe even exactly. *)> ; BEGIN END M2 .> > ; MODULE Client2 EXPORTS Main> ; IMPORT M2> ; VAR Obj : M2 . T := NEW ( M2 . T )> ; BEGIN> Obj . op ( )> END Client2 .> > This is the OO style. The operation is an overridable method. Clients can still> manipulate objects of type T only by using the operation op, but here, op is a> method, not a procedure, so they must use a method call. It will dispatch, but> without further code, it will always dispatch to M2.Op.> > But, if client code were to:> 1) declare a subtype Sub, of M2.T,> 2) which has a method override for op, say procedure OpSub,> 3) provide OpSub,> 4) then allocate an object of type Sub,> 5) but assign this object to variable Client2.Obj (whose type is M2.T, not Sub),> then the method call Obj.op() would dispatch to OpSub.> > -------------------------------------------------------------------------------> Here is a side point that I think is confusing about Modula-3 opaque types. At least> it took me years to fully understand. The same subtype mechanism is used in Modula-3> in two different ways. One is for creating a hierarchy of dynamically typed objects.> This is the usual OO use. The other is in opaque types. When used in the usual way,> actual objects of opaque type Public are never allocated. Public is only a static> structure to hold the subset of the properties of type T that are known everywhere.> > When you execute NEW(M2.T), you get a complete object of type T, with all its fields and> method overrides, even though you are in a context where these are not known. In a> sense, these things are hidden only from the programmer of this client code. But the> compiler may have to know at least some of the hidden information at the site of the> NEW call.> > (Actually, through clever implementation techniques, I think the compiler needs to> know at most the size of fully revealed type T and could probably do without that.> The messages about recompiling modules because of new opaque info are, I am sure,> the compiler generating better code by using revelations it didn't have the first time.)> > -------------------------------------------------------------------------------> INTERFACE M3> ; TYPE T <: Public> ; TYPE Public = OBJECT METHODS op ( )> := Op (* Specify the body of op, here in the interface. *)> END (* Public *)> ; PROCEDURE Op ( Arg : T ) = (* A body that uses the fields of T. *)> ; END M3 .> > MODULE M3> ; REVEAL T = Public BRANDED OBJECT (* Same fields as M1.T and M2.T. *)> (* No override for op needed. Its body was already> given in type Public. *)> END T> ; PROCEDURE Op ( Arg : T ) = (* Pretty much the same as M1.Op. *)> ; BEGIN END M3 .> > ; MODULE Client3 EXPORTS Main> ; IMPORT M3> ; VAR Obj := NEW ( M3 . T )> ; BEGIN> Obj . op ( )> ; M3 . Op ( Obj )> END Client3 .> > This is a hybrid. Still opaque, as before. But a client can either call plain> procedure M3.Op, which will be statically known to always invoke Op, or a method> call on op, which might dispatch to Op or OpSub, if the latter exists.> > ------------------------------------------------------------------------------------> An OO purist would say that every programmer-defined type should be opaque, hiding its> fields, and that every operation should always be a method, in case some later code> has a need to create a subtype and override the methods.> > The problem with (dispatching) methods is it makes tracking down bugs a nightmare. The> whole abstraction idea works great when designing one layer at a time, or even testing> one layer at a time. But when you have a specific bug, you can't do it a layer at a time.> You often have to trace, mentally or with actual execution, in and out of the calls and> returns, through the layers.> > If you see a procedure call, it is statically known and quite direct, at least in a> modular language, to find the code that is called. Every time you see a method call,> there is a big tangential process to try to figure out if there are overrides, what> subtype the object might be, etc., and the results may be dynamic. This can make an> otherwise sstraightforward process extremely tedious.> > I am a strong believer in using methods when there is a good reason, i.e., you know> or reasonably suspect there will be a need to actually create overrides and have> nontrivial dispatching happen. Otherwise, stick to static procedure calls. The> pickle code, for example, uses methods all over the place. Nearly all of them always> dispatch to exactly one place, but for each such, it takes a lot of work to ascertain> this. It is very hard code to vet.> > -------------------------------------------------------------------------------------> The hybrid approach, perhaps surprisingly, is sometimes very useful. For example,> you have a complex tree with several node types that are different subtypes of a> common parent node type. In some places, you have a node pointer that could be> any of the subtypes. Then you would want to make a method call. In other places,> you already know exactly what type node you have, perhaps because you are inside> a TYPECASE or a procedure that was already dispatched-to. Here, I prefer to use> a non-dispatching call. Aside from saving a tiny bit of runtime overhead for the> dispatch (which is minor), you avoid the debug problem above.> > > > > > - Will the change break pickles?> > "Both" due to the addition of data "or" methods?> > ie: What breaks pickles?> > Do I need to think in the mindset of> > C:> > typedef struct {> > int a,b;> > } foo_t;> > > > foo_t f;> > fwrite(&f, sizeof(f), 1, file);> > > > and not breaking such code? > > Only if the type is "branded"? Or if types derived from it are branded?> > There could be derived types not in the cm3 tree though.> > How much do we care about breaking code outside the cm3 tree?> > e.g. in this change, I had to change every use of .xres and .yres> > If by "break pickles" you mean invalidating existing pickle _data_ that was written> before the change, then yes. When reading an object from a pickle, the reading> program must contain a type that is exactly the same type as was used to write> the object. So if the program that reads the pickle is recompiled after the change,> then existing pickle data will have to be rewritten.> > A change to anything that is a property of a type, according to the rules of> the language, will cause this. For example, changes in brandedness, changes in> the string value of the brand, or, probably, the use of an anonymous brand would> all change the type. Note that the full revelation of any opaque type is required> by the language to be branded, though not necessarily with a string value.> > Also, note that the names of fields are part of a record or object type, so> changing .xres to a different name will invalidate existing pickle data. This> does not necessarily mean it's a bad idea.> > On the other hand, if you are willing/able to recompile and rerun both the program> that writes and the program that reads the pickle data, such changes will work fine.> Neither the source code in the Pickle library nor its client code will break.> > We definitely do care about breaking code outside the cm3 tree. Randy's application> would be a good example. So removing functions, constants, etc. just because they> are unused in cm3 would not be good. Nor would merging differently-named things,> just because they always have the same properties in cm3, because they could have> different properties in other code.> > > -- > -------------------------------------------------------------> Rodney M. Bates, retired assistant professor> Dept. of Computer Science, Wichita State University> Wichita, KS 67260-0083> 316-978-3922> rodney.bates at wichita.edu
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://m3lists.elegosoft.com/pipermail/m3devel/attachments/20080809/86c51ed0/attachment-0002.html>


More information about the M3devel mailing list