From: Adam Beneschan on 17 Nov 2009 17:10 On Nov 16, 2:28 pm, "Dmitry A. Kazakov" <mail...(a)dmitry-kazakov.de> wrote: > > No, what I have in mind is that each time a new type is derived, we could > somehow ensure that the whole row M+1 (from N x M+1) is filled either by a > overriding or per a reasonable inheritance. The major problem is that we > cannot see all Ns when we do M+1. > > The idea of deriving both types in one package looks like an attempt to > control the places where we can expand the table N x M. That is not enough > to enforce completeness of the table in the above sense. > > There should be something more strong, but also more liberal in terms of > modularization. Obviously to derive from all types related via a shared > primitive operation in one package is awful (and sometimes just impossible > within the given parent-child hierarchy of packages). ..... > Yep, this is what I meant. The compiler should require to override *all* > combinations: > > type T2 is new T1 with ...; > overriding > procedure Some_Operation (X : T2; Y : T2); > -- Error! > > overriding > procedure Some_Operation (X : T2; Y : T2); > overriding > procedure Some_Operation (X : T1; Y : T2); > overriding > procedure Some_Operation (X : T2; Y : T1); > -- OK > > Yes, this is tedious, but the programmer should know what we does when he > declares a multi-method. This is the semantics of - there are n**2 > combinations which do potentially different things. If there are only n > combinations, that is another case, which should *not* look MD, because it > is not. I think it should somehow use tuples instead. The error should > happen upon tuple construction, not in the call. For the reader it should > be clear that there is only one tag. I have no idea how to spell that > syntactically or by other means. It appears to me that we're talking at cross-purposes. I was trying to solve one particular class of problems; you seem to be trying to solve the entire Multiple Dispatch problem. I believe that when I first thought about making this proposal, I was assuming that a fully general multiple dispatch wasn't ever going to be part of the language, so I was thinking about an improvement for one particular case I had run into. Here's an example of the sort of problem I'm thinking about: Suppose you want to define an abstract type that represents a document, which could be a plain ASCII text file, a Word document, a PDF file, etc., that all have in common that they consist of sequences of "characters" and "other elements of some sort". Some of the documents may also have formatting information or other good stuff, but these won't be common to all documents. You want to write a package that deals with documents and particularly with the text (characters) in the documents. The type of document (plain text, Word, etc.) won't be known until run time. Some of the operations you want to provide are: search for given text in the document, and return "markers" that point to the beginning and end of the text that is found; and move text from one point to another in the document. A natural way to do this, it seems, would be to define a Document as an abstract type, and let other packages (Plaintext_Documents, Word_Documents, PDF_Documents, etc.) define concrete types as extensions of that type for plain text, Word documents, PDF documents, and so on. You also want to define, say, a Text_Region type that represents a region of text in a document, and a Marker type that represents a particular point in the document text, between two characters. The Text_Region and Marker types should be abstract also, since we don't know how they'd be implemented for different document types; for a plain text document, a Marker is probably just a string index, in essence, but the Word_Documents and PDF_Documents might unpack a file into some complex internal data structure when it reads it, so a Marker for those document types might contain pointers into the internal data structure, or something. So it seems natural to write the package like this (along with other operations): package Documents is type Document is abstract tagged private; type Text_Region is abstract tagged private; type Marker is abstract tagged private; function Search_For_Text (Doc : Document; Text : String) return Text_Region is abstract; -- I'm assuming here that it would return some sort of -- "no region" value if the text is not found function Beginning_Of (Reg : Text_Region) return Marker is abstract; function End_Of (Reg : Text_Region) return Marker is abstract; procedure Move_Text (Doc : in out Document; From : in Text_Region; To : in Marker) is abstract; end Documents; The last would cut the text represented by the From region and move it to the point specified by "To". (It would be up to the packages to figure out how to move formatting information or other information in an appropriate way.) Of course, Move_Text can't be declared because it is a primitive operation of more than one tagged type. Actually, none of the functions can be declared, either, but I want to focus on the Move_Text procedure. You might want to call Move_Text with a Word_Documents.Document, Word_Documents.Text_Region, and Word_Documents.Marker, or similarly with three objects all with types from the PDF_Documents or Plaintext_Documents package, but you would never want to mix objects from two different packages. And that was the purpose of the proposal I was thinking of making: to allow for restricted use of multiple dispatch for cases like this (and this is a situation I believe I've run into more than once), in which Move_Text could be declared but could only be called if the parameters' specific types all come from the same package, checked at runtime if necessary. The Move_Text procedure would be inherited in each of the other packages (Plaintext_Documents, etc.), with types from that package, but there would not be any inherited Move_Text that would involve types from different packages. (This was an idea I first thought about a long time ago, but eventually abandoned; I really hadn't thought about how to deal with the functions.) There are workarounds, of course. As I responded to the original poster in this thread, you can make some of the parameters to Move_Text class-wide, and the concrete procedures would check to make sure the actual tags for the class-wide parameters are correct---or just doing a type conversion will do the check: package body Word_Documents is overriding procedure Move_Text (Doc : in out Document; From : in Documents.Text_Region'Class; To : in Documents.Marker'Class) is X_From : Text_Region renames Text_Region(From); X_To : Marker renames Marker(To); ... I think that's correct. Anyway, those will raise Constraint_Error if the parameters are from the wrong package. However, I consider that to be a workaround (maybe others don't think it's that bad), which is why I was thinking about making this proposal---to make things work more "naturally", without a workaround, in this sort of situation that I've run into multiple times. I'm not sure what your idea is trying to solve (other than "making multiple dispatch perfect"); but even if it does make general multiple dispatch available, your way of doing things would not solve anything in this sort of case, because you would require Move_Text to be overridden for every possible combination of children of the types, which makes it unusable and means that even with your Grand Solution To Everything, those of us who want to implement something like the above would have to go back to using the workaround. That's why I think we're not on the same page---we're trying to solve two different and unrelated problems that seem only superficially related. Maybe we can't solve them both. -- Adam
From: Dmitry A. Kazakov on 18 Nov 2009 04:46 On Tue, 17 Nov 2009 14:10:54 -0800 (PST), Adam Beneschan wrote: > On Nov 16, 2:28�pm, "Dmitry A. Kazakov" <mail...(a)dmitry-kazakov.de> > wrote: > >> No, what I have in mind is that each time a new type is derived, we could >> somehow ensure that the whole row M+1 (from N x M+1) is filled either by a >> overriding or per a reasonable inheritance. The major problem is that we >> cannot see all Ns when we do M+1. >> >> The idea of deriving both types in one package looks like an attempt to >> control the places where we can expand the table N x M. That is not enough >> to enforce completeness of the table in the above sense. >> >> There should be something more strong, but also more liberal in terms of >> modularization. Obviously to derive from all types related via a shared >> primitive operation in one package is awful (and sometimes just impossible >> within the given parent-child hierarchy of packages). > > .... > >> Yep, this is what I meant. The compiler should require to override *all* >> combinations: >> >> � �type T2 is new T1 with ...; >> � �overriding >> � � � procedure Some_Operation (X : T2; Y : T2); >> � � � � -- Error! >> >> � �overriding >> � � � procedure Some_Operation (X : T2; Y : T2); >> � �overriding >> � � � procedure Some_Operation (X : T1; Y : T2); >> � �overriding >> � � � procedure Some_Operation (X : T2; Y : T1); >> � � � � �-- OK >> >> Yes, this is tedious, but the programmer should know what we does when he >> declares a multi-method. This is the semantics of - there are n**2 >> combinations which do potentially different things. If there are only n >> combinations, that is another case, which should *not* look MD, because it >> is not. I think it should somehow use tuples instead. The error should >> happen upon tuple construction, not in the call. For the reader it should >> be clear that there is only one tag. I have no idea how to spell that >> syntactically or by other means. > > It appears to me that we're talking at cross-purposes. No, it is the same problem of MD. It has some special cases like multi-methods, which are simpler, but it is still MD. The example you provided is full MD. You have three hierarchies: Document'Class, Text_Region'Class, Marker'Class An operation can be primitive in any combinations of types from these hierarchies. > I was trying to solve one particular class of problems; > you seem to be trying to solve the entire Multiple > Dispatch problem. I believe that when I first thought > about making this proposal, I was assuming that a fully > general multiple dispatch wasn't ever going to be part > of the language, so I was thinking about an improvement > for one particular case I had run into. > > Here's an example of the sort of problem I'm thinking > about: Suppose you want to define an abstract type that > represents a document, which could be a plain ASCII > text file, a Word document, a PDF file, etc., that all > have in common that they consist of sequences of > "characters" and "other elements of some sort". Some > of the documents may also have formatting information > or other good stuff, but these won't be common to all > documents. > > You want to write a package that deals with documents > and particularly with the text (characters) in the > documents. The type of document (plain text, Word, > etc.) won't be known until run time. Some of the > operations you want to provide are: search for given > text in the document, and return "markers" that point > to the beginning and end of the text that is found; and > move text from one point to another in the document. > > A natural way to do this, it seems, would be to define > a Document as an abstract type, and let other packages > (Plaintext_Documents, Word_Documents, PDF_Documents, > etc.) define concrete types as extensions of that type > for plain text, Word documents, PDF documents, and so > on. You also want to define, say, a Text_Region type > that represents a region of text in a document, and a > Marker type that represents a particular point in the > document text, between two characters. The Text_Region > and Marker types should be abstract also, since we > don't know how they'd be implemented for different > document types; for a plain text document, a Marker is > probably just a string index, in essence, but the > Word_Documents and PDF_Documents might unpack a file > into some complex internal data structure when it reads > it, so a Marker for those document types might contain > pointers into the internal data structure, or > something. > > So it seems natural to write the package like this > (along with other operations): > > package Documents is > > type Document is abstract tagged private; > type Text_Region is abstract tagged private; > type Marker is abstract tagged private; > > function Search_For_Text (Doc : Document; Text : String) > return Text_Region is abstract; > -- I'm assuming here that it would return some sort of > -- "no region" value if the text is not found > function Beginning_Of (Reg : Text_Region) return Marker > is abstract; > function End_Of (Reg : Text_Region) return Marker > is abstract; > > procedure Move_Text (Doc : in out Document; > From : in Text_Region; > To : in Marker) > is abstract; > > end Documents; > > The last would cut the text represented by the From > region and move it to the point specified by "To". (It > would be up to the packages to figure out how to move > formatting information or other information in an > appropriate way.) > > Of course, Move_Text can't be declared because it is a > primitive operation of more than one tagged type. > Actually, none of the functions can be declared, > either, but I want to focus on the Move_Text procedure. > You might want to call Move_Text with a > Word_Documents.Document, Word_Documents.Text_Region, > and Word_Documents.Marker, or similarly with three > objects all with types from the PDF_Documents or > Plaintext_Documents package, but you would never want > to mix objects from two different packages. As an example consider these operations: procedure Cut ( Doc : in out Document; From : in Text_Region; Clipboard : in out Document ); procedure Paste ( Clipboard : in Document; Doc : in out Document; To : in Marker ); > And that > was the purpose of the proposal I was thinking of > making: to allow for restricted use of multiple > dispatch for cases like this (and this is a situation I > believe I've run into more than once), in which > Move_Text could be declared but could only be called if > the parameters' specific types all come from the same > package, checked at runtime if necessary. The > Move_Text procedure would be inherited in each of the > other packages (Plaintext_Documents, etc.), with types > from that package, but there would not be any inherited > Move_Text that would involve types from different > packages. (This was an idea I first thought about a > long time ago, but eventually abandoned; I really > hadn't thought about how to deal with the functions.) So if you derived ASCII_Text from Plain_Text you would lose Move_Text if you forgot to derive from Text_Region and Marker? > There are workarounds, of course. As I responded to > the original poster in this thread, you can make some > of the parameters to Move_Text class-wide, and the > concrete procedures would check to make sure the actual > tags for the class-wide parameters are correct---or > just doing a type conversion will do the check: > > package body Word_Documents is > > overriding > procedure Move_Text (Doc : in out Document; > From : in Documents.Text_Region'Class; > To : in Documents.Marker'Class) is > X_From : Text_Region renames Text_Region(From); > X_To : Marker renames Marker(To); > ... > > I think that's correct. Anyway, those will raise > Constraint_Error if the parameters are from the wrong > package. (BTW, the jargon word is "covariance". Document, Text_Region and Marker are said "covariant" in Move_Text.) > However, I consider that to be a workaround (maybe > others don't think it's that bad), which is why I was > thinking about making this proposal---to make things > work more "naturally", without a workaround, in this > sort of situation that I've run into multiple times. > I'm not sure what your idea is trying to solve (other > than "making multiple dispatch perfect"); but even if > it does make general multiple dispatch available, your > way of doing things would not solve anything in this > sort of case, because you would require Move_Text to be > overridden for every possible combination of children > of the types, which makes it unusable and means that > even with your Grand Solution To Everything, those of > us who want to implement something like the above would > have to go back to using the workaround. Consider again ASCII_Text derived from Plain_Text. Let we also derived from Plain_Text_Region and Plain_Marker: Plain_Text <-- ASCII_Text Plain_Text_Region <-- ASCII_Region Plain_Marker <-- ASCII_Marker Now, my concern is what happens with Move_To called on ASCII_Text, Text_Region, Plain_Marker You say, it is Constraint_Error. The question what is the reason for this? I don't see any. It is for the programmer to decide. I you say the language forbids that, I say OK, but then that MUST be done static. Anyway your proposal would impose some draconian limitations on the packages structure. E.g. it would be impossible to provide implementations and different types of Marker in separate packages. Or consider a generic implementing a Marker for the given document type. Would that work? > That's why I think we're not on the same page---we're > trying to solve two different and unrelated problems > that seem only superficially related. Maybe we can't > solve them both. Actually I considered both cases with the requirement of the constraint being static. To summarize: 1. Full MD = no constraint => all combinations checked and required 2. SD on tuples = static constraint on the combinations The case 2 has consequences, of course. What I find totally inappropriate for a language like Ada is: 3. Dynamic constraint with run-time checks dropping arbitrary exceptions. I think we should have learnt something from the "accessibility checks disaster". -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de
From: Adam Beneschan on 18 Nov 2009 11:39 On Nov 18, 1:46 am, "Dmitry A. Kazakov" <mail...(a)dmitry-kazakov.de> wrote: > > It appears to me that we're talking at cross-purposes. > > No, it is the same problem of MD. It has some special cases like > multi-methods, which are simpler, but it is still MD. The example you > provided is full MD. You have three hierarchies: > > Document'Class, Text_Region'Class, Marker'Class > > An operation can be primitive in any combinations of types from these > hierarchies. > > Here's an example of the sort of problem I'm thinking > > about: Suppose you want to define an abstract type that > > represents a document, which could be a plain ASCII > > text file, a Word document, a PDF file, etc., that all > > have in common that they consist of sequences of > > "characters" and "other elements of some sort".... > > Of course, Move_Text can't be declared because it is a > > primitive operation of more than one tagged type. > > Actually, none of the functions can be declared, > > either, but I want to focus on the Move_Text procedure. > > You might want to call Move_Text with a > > Word_Documents.Document, Word_Documents.Text_Region, > > and Word_Documents.Marker, or similarly with three > > objects all with types from the PDF_Documents or > > Plaintext_Documents package, but you would never want > > to mix objects from two different packages. > > As an example consider these operations: > > procedure Cut > ( Doc : in out Document; > From : in Text_Region; > Clipboard : in out Document > ); > procedure Paste > ( Clipboard : in Document; > Doc : in out Document; > To : in Marker > ); Right, I wasn't envisioning this sort of operation that involved two *different* documents of (potentially) two different types. When I started thinking about this idea, I had had some issues with programs I was working on, but it was so long ago that I don't remember what the exact problem was and I can't find it. Whatever it was, it was *not* a case where there would have been any operations involving two different objects of two different child types. > So if you derived ASCII_Text from Plain_Text you would lose Move_Text if > you forgot to derive from Text_Region and Marker? Most likely, I would have written the rules so that if you *do* declare an operation like this of two (or more) different tagged types, and if in some other package you don't derive from all those types, it would be an error at that point. Again, although I can see that someone might want to derive ASCII_Text from Plain_Text, it wasn't something I considered at the time since it wasn't germane to the type of problem I was running into. > Consider again ASCII_Text derived from Plain_Text. Let we also derived from > Plain_Text_Region and Plain_Marker: > > Plain_Text <-- ASCII_Text > Plain_Text_Region <-- ASCII_Region > Plain_Marker <-- ASCII_Marker > > Now, my concern is what happens with Move_To called on > > ASCII_Text, Text_Region, Plain_Marker > > You say, it is Constraint_Error. The question what is the reason for this? > I don't see any. It is for the programmer to decide. > > I you say the language forbids that, I say OK, but then that MUST be done > static. I don't see a practical reason why that would be necessary. And I have a major objection, at least to what I think you're proposing. Suppose someone at some organization declares a package with some abstract types. Another organization is developing a large application. Since this is a large application, it's divided into several parts with one part being given to one team, another part to another team, and so on, working independently. The idea is that as long as each team can develop its part of the application that works according to some "contract", then you should be able to put the pieces together and get the whole thing to work. Suppose that two or three of those teams decide to use this library package and derives child types from those abstract types. This shouldn't be an issue for teams working independently, but your idea would make it an issue, because in order for the program to compile, you would require that somebody write useless versions of the inherited operations for every possible combination of derived types, even when the derived types come from two different teams working independently. Sounds like a needless burden to me. > Anyway your proposal would impose some draconian limitations on the > packages structure. E.g. it would be impossible to provide implementations > and different types of Marker in separate packages. Or consider a generic > implementing a Marker for the given document type. Would that work? 1) Keep in mind that I wasn't trying to solve the whole world, just one subset of the problem. The way I envisioned it would, I think, have been fairly simple to generate code for and wouldn't be a huge implementation burden, nor would it have imposed a huge burden on programmers. (I could be wrong, since there are some cases I didn't think about, particularly involving tag-indeterminate function calls.) I realize that my proposal wasn't going to solve everything, but my feeling is that if you can solve a subset of a problem simply, and solving the entire problem would be orders of magnitude more complex, then the fact that there will still be unsolved issues isn't a valid reason not to solve the narrower problem. (That would be called "letting the perfect be the enemy of the good enough". And even with the limitations you think are draconian, I think it would have been good enough for the types of problems I was encountering.) Unless we envision that the larger problem *will* be addressed at some point and the solution to the smaller problem might interfere with the larger solution. But at the time, I didn't see any reason to believe that the general "multiple dispatch" case will ever be addressed. Maybe it still won't. 2) I didn't finish thinking everything through before I abandoned the idea, so I didn't consider things like generics. Also, it's possible that there might have been a way to address some of your objections without significantly adding to the implementation burden. > What I find totally inappropriate for a language like Ada is: > > 3. Dynamic constraint with run-time checks dropping arbitrary exceptions. > > I think we should have learnt something from the "accessibility checks > disaster". OK, I don't understand this. First, I don't understand what about accessibility checks was a disaster; and second, I don't see what's wrong with runtime checks in cases where it's too hard for a compiler to figure out at compile time whether something will go wrong. Is this another case of letting the perfect be the enemy of the good enough? -- Adam
From: Dmitry A. Kazakov on 18 Nov 2009 14:21 On Wed, 18 Nov 2009 08:39:14 -0800 (PST), Adam Beneschan wrote: > On Nov 18, 1:46�am, "Dmitry A. Kazakov" <mail...(a)dmitry-kazakov.de> > wrote: > > Again, although I can see that someone might want to derive ASCII_Text > from Plain_Text, it wasn't something I considered at the time since it > wasn't germane to the type of problem I was running into. So, in effect you want a depth-1 hierarchy, like we have for the protected interfaces? >> Consider again ASCII_Text derived from Plain_Text. Let we also derived from >> Plain_Text_Region and Plain_Marker: >> >> � �Plain_Text <-- ASCII_Text >> � �Plain_Text_Region <-- ASCII_Region >> � �Plain_Marker <-- ASCII_Marker >> >> Now, my concern is what happens with Move_To called on >> >> � �ASCII_Text, Text_Region, Plain_Marker >> >> You say, it is Constraint_Error. The question what is the reason for this? >> I don't see any. It is for the programmer to decide. >> >> I you say the language forbids that, I say OK, but then that MUST be done >> static. > > I don't see a practical reason why that would be necessary. And I > have a major objection, at least to what I think you're proposing. > Suppose someone at some organization declares a package with some > abstract types. Another organization is developing a large > application. Since this is a large application, it's divided into > several parts with one part being given to one team, another part to > another team, and so on, working independently. The idea is that as > long as each team can develop its part of the application that works > according to some "contract", then you should be able to put the > pieces together and get the whole thing to work. Suppose that two or > three of those teams decide to use this library package and derives > child types from those abstract types. This shouldn't be an issue for > teams working independently, but your idea would make it an issue, > because in order for the program to compile, you would require that > somebody write useless versions of the inherited operations for every > possible combination of derived types, even when the derived types > come from two different teams working independently. Sounds like a > needless burden to me. No, it is not a burden it is a mere consequence of a decision made earlier. If the abstract types are Printer and Contents with the joint operation Print (the classic example of MD), you *have* to implement all combinations. It is not a question. The question is how to make the language helping us in doing this work safely and efficiently. I have no idea how to do this while keeping it modular. But that does not change the fact that all combinations have to be defined. To silently define some of them as Constraint_Error is a very bad idea for the language like Ada. >> What I find totally inappropriate for a language like Ada is: >> >> 3. Dynamic constraint with run-time checks dropping arbitrary exceptions. >> >> I think we should have learnt something from the "accessibility checks >> disaster". > > OK, I don't understand this. First, I don't understand what about > accessibility checks was a disaster; Because they are the major contributor to hours spent on debugging unhandled exceptions. > and second, I don't see what's > wrong with runtime checks in cases where it's too hard for a compiler > to figure out at compile time whether something will go wrong. Accessibility checks are known for being false positive. Same with MD, it is not a semantic error to cross types in an operation. It is an *artificial* error introduced by the language designer, because it would be too difficult to do it otherwise (e.g. right (:-)). Then the very same designer finds his own error too difficult to detect at compile time! I have no problem with the former, but do not accept the latter. > Is > this another case of letting the perfect be the enemy of the good > enough? There exist lots of imperfect languages already. But Ada must be perfect! (:-)) Returning to the case where we do not want cross combinations. I thought about it and came to the conclusion that this is single dispatch (tags are synchronous). I think we should approach this differently. Let us consider an example were dispatching table is semantically "diagonal": type Object is limited interface ...; type Handle is interface ...; Each time we derive from Object, we want a new Handle. This is IMO a better example than Document x Region x Marker. Note that here the issue of packages is especially important. Most likely we wanted to place Objects into private packages, making only Handles public. Most operations on Object and Handle are same and have only one controlled argument. I am tempted to say that Handle should also implement Object. type Handle is interface and Object ...; But there are two cross operations: function Ref (X : Object) return Handle is abstract; function Target (X : Handle) return access Object is abstract; The same problem more complicated. How to add type Readonly_Handle is interface ...; -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de
From: Randy Brukardt on 18 Nov 2009 19:27
"Dmitry A. Kazakov" <mailbox(a)dmitry-kazakov.de> wrote in message news:1wtsriaxu0s4s$.ikwnnz5teukp$.dlg(a)40tude.net... .... >> OK, I don't understand this. First, I don't understand what about >> accessibility checks was a disaster; > > Because they are the major contributor to hours spent on debugging > unhandled exceptions. That seems odd to me. I rather *like* getting unhandled exceptions, because it is almost always easy to see what the problem is from the exception name and the traceback. (And all of my long-running programs have systems to record this in the case of an unexpected exception.) It's the incorrect results, compiler bugs (where the code is right, but the wrong thing happens), and crashes that cause "hours of debugging". OTOH, doing something that even generates a runtime accessibility check is a bug IMHO (it's what Bob Duff calls a "tripping hazard" - a latent problem that will bite you when someone does something unconventional -- in that sense it is just like the bug of assigning a string parameter has lower bound 1); it would be useful if the compiler could optionally point out such places so you could fix them. Randy. |