From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Thread: 103376,50e705cdf2767cc6 X-Google-NewGroupId: yes X-Google-Attributes: gida07f3367d7,domainid0,public,usenet X-Google-Language: ENGLISH,ASCII-7-bit Path: g2news2.google.com!news4.google.com!feeder.news-service.com!news.albasani.net!news2.arglkargh.de!noris.net!newsfeed.arcor.de!newsspool3.arcor-online.net!news.arcor.de.POSTED!not-for-mail Date: Fri, 08 Apr 2011 00:13:17 +0200 From: Georg Bauhaus User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.15) Gecko/20110303 Thunderbird/3.1.9 MIME-Version: 1.0 Newsgroups: comp.lang.ada Subject: Re: Parser interface design References: <4d9c8c19$0$6769$9b4e6d93@newsspool3.arcor-online.net> In-Reply-To: Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Message-ID: <4d9e36fd$0$6760$9b4e6d93@newsspool3.arcor-online.net> Organization: Arcor NNTP-Posting-Date: 08 Apr 2011 00:13:18 CEST NNTP-Posting-Host: 2a8cc453.newsspool3.arcor-online.net X-Trace: DXC=\5PBgdI4FEZV0Pe9PRnbJ\McF=Q^Z^V3X4Fo<]lROoRQ8kFejVXUce:4i4jO1[c98^E5IgGAY X-Complaints-To: usenet-abuse@arcor.de Xref: g2news2.google.com comp.lang.ada:19685 Date: 2011-04-08T00:13:18+02:00 List-Id: > I have trouble abstracting concept out of your examples, so if you don't > mind I will provide my own, which I understand, and maybe we can work > out a common ground that I understand from there. Understood. (I know that example would be a bit involved. Too much of it will confuse me, too, reliably.) > Now at least I understand the idea of using generics instead: > > generic > with function Emphasis (Contents: String) return String is<>; > with function Normal_Text (Contents: String) return String is<>; > with function Paragraph (Contents: String) return String is<>; > function Parser (Renderer: Renderer_Callbacks; Input: String) > return String; I'll write about generics, first, because I think that even if you decide against them (or their "spirit"), I think it is worth knowing what they do. (There is no problem with functions not existing, for example; Dmitry's war against generics is built on a different fundament :-) Dmitry, I apologize! The part between "generic" and "function" indeed corresponds to function pointers, so that *is* the Renderer_Callbacks part, in Ada. Hence, the parameter Renderer is not needed. generic with function Emphasis (Contents: String) return String is<>; ... function Parser (Input: String) return String; The body of Parser will call Emphasis as needed (which is very much like a function pointer)etc. I'm assuming that the string returned is the rendered text. In a C struct of function pointers, you'd make them point to any function that fits the bill. You do the same with generics. function HTML_Rendering_Parser is new Parser (Emphasis => EM_printing_function, Normal_Text => Identity, Paragraph => P_printing_function); where EM_printing_function, Identity, and P_printing_function are defined anywhere you like. Or, in C99, struct Parser HTMLRenderingParser = { .Emphasis = EM_printing_function, .Normal_Text = Identity, .Paragraph = P_Printing_function }; The difference is that in C (or equivalent Ada), the function Parser is not a template to be instantiated and done, possibly at compile time, but instead needs an object HTMLRenderingParser. The similarity is that the functions are available to function Parser in both cases. In C, you pass an object that holds function pointers. In Ada, using generics, you just pass function "addresses" when instantiating, In O-O only programming, you'd pass an object that corresponds to a struct with pointers to functions and possibly a pointer to another such struct (for inheritance of implementation). Generic body, then: function Parser (Input: String) return String is begin loop ... subpart2 := Normal_Text("bar"); part2 := Emphasis (subpart2); ... end loop; end Parser; Where Emphasis stands for whatever function is passed when instantiating Parser, etc. But, away with generics, even though they will provide checking and performance gains. (No indirections through function pointers, and guaranteed function existence! Instantiate with NOP functions as needed.) >> Anyway, here is another approach, probably not very original, >> certainly lacking proper modularization and other things >> that I did't see, sorry, but if offers another idea. There aren't >> any generics in it. >> >> But there is a procedure >> >> procedure Print (Item : Token'Class; >> Output : in out Format'Class); >> >> The idea is that given any token, Print renders the token >> Item into any format passed for Output. > > I genuinely read several times your example, and I can't figure out what > thing performs what. OK. There was one more actor. I'll remove it in the next step. But first, assume that Tokens stand for atoms or composites, +--------+ +---------+ | Parser | -> Token -> | Printer | -> Rendered Token +--------+ +---------+ | v knows output format (polymorphic) I understand you wanted the parser to be in control of the printer, i.e., have Parser call Emphasis etc., instead. I'll come to that. Here is the parsing scheme I had in mind. The following is simplified because it omits the composites (the semantic units, which would be sort of handled via Printables, to be extracted from "tokens"). The example establishes a different kind of loose coupling. It shows Print at the center of polymorphic behavior: procedure Parsing is Output_Format: Format := Configuration.Choose; begin loop declare X : Token'Class := Parser.Next_Token; begin Print (X, Format); end; end loop; end Parsing; This control structure pulls tokens. Parser could be a "lazy" object, producing tokens as needed. (At this point, can you see why I thought that, for example, the loop above could be a task? Or the parser? But that is a different story and of no concern here.) Then, next in the control structure, is a call on Print. Print takes two arguments whose run-time type is not known. By design, Print renders any kind of token into any format. Print will therefore look at both X and Format, and find out what they are (inspect their run-time tags). Both X and Format could share O-O "interfaces", respectively. Or X may be a variant record, the variants reflecting the different kinds of token. In the C style O-O you mentioned, Print would do that by calling functions available through function pointers in both X's struct and Format's struct. Or, to learn about X, Print could just look at a distinguishing field of X. Your idea seems different in that the parser will call the rendering procedures whenever *it* thinks it has something ready to be rendered. That's fine, I think. Keeping your current design, just for the record, you could establish an interface in the logical sense, type Renderer_Callbacks is abstract tagged private; function Emphasis (This: Renderer_Callbacks; Contents: String) return String is abstract; function Normal_Text (This : Renderer_Callbacks; Contents: String) return String is abstract; function Paragraph (This : Renderer_Callbacks; Contents: String) return String is abstract; Add derive types that will override behavior for different output formats. Then function Parser (R : Renderer_Callbacks'Class; Input : String) return String; The calls within Parser will be dispatching, depending on the type of the actual renderer object. And yes, if you don't start the hierarchy from reusable functions that do not need to be overridden, there can be code bloat. This would not be the case with generics, which you instantiate with whatever reusable functions you like! :-)