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