Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c++ > #91307 > unrolled thread
| Started by | wij <wyniijj5@gmail.com> |
|---|---|
| First post | 2023-08-20 06:55 -0700 |
| Last post | 2023-08-29 19:27 +0200 |
| Articles | 20 on this page of 53 — 8 participants |
Back to article view | Back to comp.lang.c++
What is visitor pattern? wij <wyniijj5@gmail.com> - 2023-08-20 06:55 -0700
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-20 07:22 -0700
Re: What is visitor pattern? wij <wyniijj5@gmail.com> - 2023-08-20 19:24 -0700
Re: What is visitor pattern? wij <wyniijj5@gmail.com> - 2023-08-27 13:13 -0700
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-28 00:13 -0700
Re: What is visitor pattern? wij <wyniijj5@gmail.com> - 2023-08-28 23:18 -0700
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-20 14:12 -0400
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-20 13:34 -0700
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-20 18:01 -0400
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-20 21:12 -0700
Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-21 06:53 +0000
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-21 00:10 -0700
Re: What is visitor pattern? Tim Rentsch <tr.17687@z991.linuxsc.com> - 2023-08-26 21:26 -0700
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-26 22:57 -0700
Re: What is visitor pattern? Tim Rentsch <tr.17687@z991.linuxsc.com> - 2023-08-28 15:07 -0700
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-21 22:55 -0400
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-21 21:56 -0700
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-24 01:38 -0400
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-24 04:45 -0700
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-24 23:11 -0400
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-25 07:52 -0700
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-26 00:55 -0400
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-21 06:18 +0200
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-21 23:28 -0400
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-22 05:47 +0200
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-22 00:11 -0400
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-22 14:30 +0200
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-24 01:56 -0400
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-24 09:15 +0200
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-24 20:02 -0400
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-25 05:13 +0200
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-26 01:05 -0400
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 09:24 +0200
Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-26 08:01 +0000
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 11:29 +0200
Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-26 15:00 +0000
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 17:08 +0200
Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-26 15:15 +0000
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 17:24 +0200
Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-26 15:36 +0000
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 17:39 +0200
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 17:26 +0200
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-29 21:44 -0400
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-29 22:14 -0400
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-30 06:13 +0200
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-30 23:18 -0400
Re: What is visitor pattern? "Alf P. Steinbach" <alf.p.steinbach@gmail.com> - 2023-08-21 01:17 +0200
Re: What is visitor pattern? "Alf P. Steinbach" <alf.p.steinbach@gmail.com> - 2023-08-21 01:24 +0200
Re: What is visitor pattern? Michael S <already5chosen@yahoo.com> - 2023-08-25 05:21 -0700
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-25 14:33 +0200
Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-25 08:28 -0700
Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-26 01:17 -0400
Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-29 19:27 +0200
Page 2 of 3 — ← Prev page 1 [2] 3 Next page →
| From | Öö Tiib <ootiib@hot.ee> |
|---|---|
| Date | 2023-08-25 07:52 -0700 |
| Message-ID | <60a2715e-2d6b-4fc3-93d4-c37ff75f74cen@googlegroups.com> |
| In reply to | #91401 |
On Friday, 25 August 2023 at 06:12:09 UTC+3, Pavel wrote:
> Öö Tiib wrote:
> > On Thursday, 24 August 2023 at 08:39:04 UTC+3, Pavel wrote:
> >> Öö Tiib wrote:
> >>> On Tuesday, 22 August 2023 at 05:55:32 UTC+3, Pavel wrote:
> >>>> Öö Tiib wrote:
> >>>>> On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote:
> >>>>>> Öö Tiib wrote:
> >>>>>>> On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote:
> >>>>>>>
> >>>>>>>> 1. Ensure all objects have a common base class (say, it's B)
> >>>>>>>
> >>>>>>> Not needed.
> >>>>>>>
> >>>>>>>> 2. create an abstract class Visitor with an abstract method doOp with an
> >>>>>>>> arg of type [ref to] B.
> >>>>>>>
> >>>>>>> Nope, pile of overloads of doOp one for each type processed.
> >>>>>>
> >>>>>> This would render the pattern useless as has no advantage over adding a
> >>>>>> virtual method to do the op. The rationale of the pattern is to permit
> >>>>>> coding one op in one place.
> >>>>>>
> >>>>> The Visitor class is that "one place"; doing same operation lets say printing
> >>>>> of 50 types in one function results with huge function that is headache
> >>>>> not advantage. Separate functions are testable and maintainable.
> >>>>>
> >>>>>>> Otherwise
> >>>>>>> how you process different types differently?
> >>>>>> I explained this in the problem statement. "Given [a ref to] any such
> >>>>>> object, you are able to
> >>>>>> compute its type and/or call a virtual method".
> >>>>>
> >>>>> IOW you do huge switch typeid ... case static_ cast or have the virtual
> >>>>> method (over what we were "gaining advantage") already there?
> >>>>
> >>>> Even a switch with 30 cases is no worse than 30 methods although switch
> >>>> is not always necessary (try to read the above instead of imagining).
> >>>>
> >>> Visitor is typically used to search, filter, draw or print whole data object
> >>> hierarchy, convert to JSON to XML or to tree in GUI.
> >> correct
> >>> If whole data hierarchy
> >>> is small then you get 30 cases. Switch case is worse than virtual methods.
> >>
> >> *In general* neither is better or worse.
> >
> > Switch case over type is in general worse than virtual functions. Longer
> > function,
> Longer than what? Definitely the one function is shorter than the sum of
> the length of all the element type hierarchy shims plus concrete visitor
> visit overloads.
>
> > bigger cyclomatic complexity,
> with good code organization, no bigger than absolutely necessary.
>
> > harder to understand, with good code organization, actually, easier (see my point below about
> maintenance.
> > harder to
> > form confidence that it is valid,
> see below in maintenance.
>
> > harder to maintain,
> actually, easier to maintain. For using that GoF / wiki DD
> implementation, non-insignificant amount of scaffolding code has to be
> written. Especially in a complex hierarchy, one can easily forget to
> write that accept shim for a couple of classes where it is needed. E.g.
> if in the hierarchy (or a part thereof) is "(B, C, D) > A" (> meaning
> inheritance), imagine the op shall do same on A and D but different on B
> and C. Isn't it easy to forget to add the accept shim to C (even though
> the actual logic visit(C), at which the programmer is likely to focus,
> is written all right).
>
Can't. You can not leak knowledge of particular operation implemented
by VisitorX that happens to be same for A and D into base visitor. Base
visitor has to have all. Something like:
virtual doOp(A& a) {std::abort();}
virtual doOp(B& b) {std::abort();}
virtual doOp(C& c) {std::abort();}
virtual doOp(D& d) {std::abort();}
It is because different operation implemented by VisitorY may be
same for A and B and different for C and D. You can only leave out
handling of D in derived visitor if D is never processed by that visitor,
so std::abort() suits you.
> > harder to test and
> why?
> > also may need more access to implementation details of all types
> > involved, breaking encapsulation.
> How so? All code is written in the op function that has no access to
> internals.
>
You have example where you need short function and then argue against
whole existence of issues why procedural programming was invented in
fifties, structured programming was invented in sixties, object-oriented
programming and double dispatch were invented in seventies. The issues
are still real in real projects, and ignoring these is called code smell.
I can not be expected to teach all that knowledge of decades in one post.
Lets take step by step?
> >
> >> In particular, as I explained
> >> at the beginning, if the operation is decomposed correctly, its behavior
> >> on the objects of different types has to be coherent, similar in some
> >> way (and maybe more similar than different) and, as such, a significant
> >> parts of the context or results of intermediate computations could be
> >> shared.
> >
> > Translating data hierarchy to xml for particular version of particular
> > protocol while supporting several such in parallel?
>
> Imagine {ABCD) type hierarchy above is to be translated to XML and B and
> C have to become XML elements with some non-trivially computable
> attribute atr1 but XML elements produced from A and D don't need it.
>
Because different endpoints (for what you form the XML) want different
data, one wants like you describe ... other does not want atr1 for A and B
... while third does not want information about D to be ever sent to it.
Now you have 3 different visitors making XML for each endpoint and any
logic in base visitor about first hurts other two.
> > There can be
> > intermediate computations shared but all are typically in in visitor
> > classes or helpers of those. No one wants the classes in hierarchy
> > to know anything about xml whatsoever,
> correct but do not see how it is connected to anything I wrote
> > nothing to talk about
> > particular brand used in particular version of kinds of end-points.
> let's not argue with anything I did not say, it's really irritating, I
> will gladly give you an example, just explain why {ABCD} above is no good.
>
I tried to.
> >
> >> I demonstrated this in my example with red and green borders: a)
> >> a condition by area prevents visitor from operating on objects is
> >> computed from their property that is orthogonal to their dynamic type;
> >> and b) the exact behavior of the operation (red vs green border in the
> >> example) depends on a subset of types (which, in general, is not
> >> necessarily compact in the hierarchy).
> >>
> > Your example did not need visitor pattern.
>
> No example "needs" visitor pattern: any problem solved with visitor can
> be solved in other ways, e.g. just by adding a virtual method to every
> class. But *both* my examples could be perfectly solved with visitor. If
> the one-time scaffolding is in place, I am ready to argue it is a good
> solution.
>
True about adding virtual method, just that adding 3 virtual functions for XML
generation code into every class will clutter code of those classes.
Especially if you want (some of) same classes to be used in other project
that does not communicate with said end-points at all.
> >
> >>> Think why virtual methods were added? To get rid of switch cases over type.
> >> Of course, not. Virtual methods were *used*, with less or greater
> >> success, to replace switch statements in event-processing scenarios
> >> (like window behavior) (with greater success) or in state machines (with
> >> lesser success).
> >>
> >> But, they were *invented* to implement polymorphic behavior and
> >> relatively loose coupling (generally, lesser dependencies) between the
> >> code that calls the method and the code that implements the method.
> >>
> > Same loose coupling is with code that calls function that does switch
> > case over type, so that kind of victory wasn't achieved.
>
> There is no such thing as "coupling in general".
Where I said the quoted text "coupling in general"? What you mean by it?
And why you switch to visitors when discussing benefits of virtual
methods?
> Any particular
> dependency is some kind of coupling. If one has to add
> accept(AbstractVisitor) method to the root of the type hierarchy, both
> the type hierarchy's interface and implementation become compile-time
> dependent on the new operation's interface (AbstractVisitor) (interface
> may depend on forward-declaration only in C++, but not the
> implementation so the implementation will also "see" N "visit" methods).
> Additionally, because every AbstractVisitor depends on (the interface
> of) every of (say, M) concrete element types, the Element's interface
> implementation becomes dependent on every of M Element types (at least
> their forward declarations, in C++). In this implementation, any new
> operation adds the dependency, needs recompilation of class hierarchy etc.
>
The whole point of visitor pattern is that you need to add accept of base
visitor only once to every class visited, no need to add 3 virtual methods to
generate XML for 3 different endpoints. But can't simplify from there,
otherwise the pattern is broken.
> If, as in my example, you only have one AbstractVisitor, adding 2nd and
> further operations adds zero dependencies, don't require recompilation.
>
No class needs to be recompiled when 4th server needing obscure
abomination of JSON is added. Same stock
void WhateverClass::accept(BaseVisitor& s) override { s.doOp(*this); }
will work like these already did. Only
class ObscureJSONGeneratorVisitor: public BaseVisitor { /* ... */ };
will need to be implemented.
> >
> >> Obviously, a single switch statement couples everything together and the
> >> compilation unit where the switch is depends on every piece of code and
> >> every concrete type the switch statement handles -- to the extent its
> >> behavior in the operation must differ from that of other types). But
> >> this loose coupling is only desirable when the behaviors are severely
> >> different.
> >>
> > I don't follow what you are saying. Different stock markets and other
> > financial institutions for example are not severely different in essence,
> > but the format of data you are expected to exchange with their servers
> > differs massively enough.
>
> Nah, it's the other way around. Say, BankA and BankB may have different
> formats of their, say, client-side FIX protocol. But, they likely both
> talk to NYSE using NYSE FIX/FAST and OATS reporting in exactly same or
> almost same format.
>
They talk with NYSE in manner that NYSE requires, but that does not concern
you, you are not NYSE that financial institutions themselves have to care how
to be conformant with. You are probably sucking the client-side butt holes of
theirs.
> Also, who needs to write an app that puts BankA and BankB to the same
> hierarchy? It's unlinkely that it's one of BankA or BankB. More likely,
> some regulatory agency or an exchange or settlement firm (hereinafter
> *an outside firm*). For the purpose of the outside firm, the formats
> it's interested in to operate on are likely the formats it uses to
> communicate with those Banks (i.e. same or with small customizations). A
> drastically different formats that BankA and B use to communicate with
> their clients will unlikely be needed by an exchange (they could be
> needed by a surveillance agency; but then "my" code would become
> respectively simpler, *always staying simpler* than the correspondent
> GoF code).
>
So you never heard of anyone who happens to have accounts in multiple
banks (even in multiple countries)? I suggest urgently to read up on
Silicon Valley Bank, Signature Bank, First Republic Bank and Heartland
Tri-State Bank ... your bank may be next.
> >
> >> On the other hand, when the operation does *to significant
> >> extent, a single thing* on different type, with 2-3 relatively small
> >> type-dependent differences, these differences are often better expressed
> >> in if statement, switch or in a number of different ways (including
> >> calling combinations of virtual functions *from within the top-level
> >> method implementing the operation*)
> >>
> > That is not use-case of visitor pattern.
> You say it more than once and never cared to provide any reasoning. I
> cited the cause to use Visitor (from Wikipedia you should like). That
> cause is fully applicable for the above setup assuming one needs to add
> many operations. I do not intend to repeat this again unless you provide
> good reasoning.
>
I did, you just do not read.
> >
> >>> Visitor is not meant as excuse to add those back, otherwise just use
> >>> the switch case in function, do not manufacture visitors that do not use
> >>> double dispatch for confusion.
> >>
> >> Ohh. Not again. If you want to use virtual functions, use them, you
> >> don't need visitor for this. Add virtual void newOperation(context
> >> parameters) to the base class and call it in some for_each. This is
> >> exactly what I explained in the first post: Visitor itself is an
> >> alternative to adding a virtual methods.
> >>
> > You add same number of virtual methods of visitor if you need several
> > different classes of visitors to visit over same data hierarchies. The
> > gain is that these are in one place, in particular visitor and that the
> > classes in hierarchies are not changed by single letter.
> Yes, not changing classes when adding an op is the one of the causes for
> using Visitor. Both my and Wiki visitor implementation formally achieve
> this cause. Plain and simple. BUT, Wiki implementation changes the
> compilation units of every Element (aka creates compilation dependency).
> Plain and simple. My implementation doesn't (except for the very 1st
> operation).
> >
> >> The criteria which one to
> >> choose are well known from the original works on Visitor, like GoF etc:
> >> if your type hierarchy is big, stable, its model is well understood and
> >> established (like GUI toolkit), and hence you are unlikely to change the
> >> hierarchy often BUT you need to add new operations often, use visitors
> >> (because you don't need to add thing to every class for every
> >> operation). If your type hierarchy is dynamic and is expected to grow
> >> often but the set of operation is stable, well establish, unlikely to
> >> change often (like unit actions in a strategy game where you add and
> >> enhance units but all they are going to do is to essentially move, shoot
> >> and die), add a virtual method.
> >>
> >> The first thing to explain about Visitor (or any other design pattern)
> >> is how to decide when to use it and when not to use it (the scope).
> >>
> > That is true, but you seem to suggest to misuse it, where unneeded.
> >
> >> If the decision to use Visitor is correct, we already know the class
> >> hierarchy is stable and there is no harm for the code to depend on a
> >> fixed set of these classes. The double dispatch for Visitor is just a
> >> tool to dispatch to the code specific to the type after you dispatched
> >> to the specific operation. Nothing more, nothing less, see definition of
> >> multiple dispatch. The specific way to double-dispatch that many Visitor
> >> examples (unfortunately, started with GoF book, and shown in Wikipedia)
> >> show, is simply the *implementation detail*. You do not have to trust me.
> >>
> > Do not touch multiple dispatch yet. Going there now is like going into
> > four dimensional math when you haven't yet got the planar 2D grid
> > to work. Stay firmly within studying double dispatch until you understand
> > what it is.
> That's not I, that's you favorite ref source Wiki correctly says double
> a dispatch is a special form of multiple dispatch and then, in all C++
> examples it gives to show MD, it actually shows DD only. In fact, both
> articles use same "Asteroid-Spaceship" example (DD) to exemplify either.
>
Wikipedia is correct, but multiple dispatch is way over head when you can't
apparently figure what is double dispatch ... so go not into multiple.
> Now IMO, here is nothing magical in dispatching on dynamic types of 3
> parameters as compared to the 2. It is surely more boring to express as
> there is usually more functions to write; but otherwise, nothing too
> exciting.
>
You can't understand rectangle yet so parallelepiped is way too early.
> >
> >> Says Wikipedia:
> >>
> >> "In software engineering, double dispatch is a special form of multiple
> >> dispatch, and a mechanism that dispatches a function call to different
> >> concrete functions depending on the runtime types of two objects
> >> involved in the call"
> >>
> >> And, looking at same Wikipedia on multiple dispatch, C++ is *not* one of
> >> the languages that natively support multiple dispatch and there are
> >> multiple ways of implementing it, among them
> >> - "using run time type comparison via dynamic_cast" (if statement method)
> >> - "pointer-to-method lookup table"; and (misleadingly)
> >> - "the visitor pattern"
> >>
> >> Why "misleadingly"? Because, looking at visitor pattern, you find
> >>
> >> "The visitor takes the instance reference as input, and implements the
> >> goal through double dispatch."
> >>
> >> So, according to Wikipedia article on multiple dispatch one can
> >> implement it (among the others) using visitor pattern, whereas visitor
> >> pattern "implements the goal via double dispatch" whereas the double
> >> dispatch is a special form of multiple dispatch.
> >>
> > Yes. you were confused by first sentence in wikipedia and stopped to
> > read there. Double dispatch is multiple dispatch like 2D grid is special
> > case of 4 dimensional room with 2 dimensions being of zero size.
> Nothing like it. Implementing DD thru the manual scaffolding of what you
> seem to believe *the one and the only correct implementation of DD for
> Visitor pattern in C++" is boring and error-prone enough (see above
> about maintenance) to not be the first choice in explaining Visitor.
>
Then you are out of luck as you talk about something that is not visitor
pattern.
"In essence, the visitor allows adding new virtual functions to a family of
classes, without modifying the classes. Instead, a visitor class is created
that implements **all** of the appropriate specializations of the virtual
function. The visitor takes the instance reference as input, and implements
the goal through **double dispatch**."
You argue with that and GoF not me.
> >
> >> Some readers (which seem to include you) resolve this circular reference
> >> ad-hoc: they see that *specific implementation* of multiple dispatch in
> >> C++ that happens to illustrate the Visitor article (stemming from the
> >> GoF book, unfortunately) and somehow decide that this is *the right way*
> >> (TM) to implement double dispatch. This is despite:
> >>
> >> - that double dispatch is defined as a concept an there is no *the right
> >> way* of doing it in C++ and Wikipedia itself illustrates two such ways
> >> (both different from the one used in the visitor article and GoF book)
> >> in the article on multiple dispatch.
> >>
> >> - that it must be obvious that it is Visitor that uses double dispatch
> >> in its implementation, not other may around, because the part cannot use
> >> the whole and Visitor is "bigger" than double-dispatch because it uses
> >> both double dispatch and iteration (through the objects in a data
> >> structure) but iteration is not a part of double dispatch.
> >>
> >> The problem with that (by and large, randomly-selected) implementation
> >> of double dispatch is that it is a bad choice to illustrate Visitor
> >> pattern specifically. Besides the mere distracting the reader to its
> >> clever *implementation details* (not essential for understanding the
> >> Visitor pattern), it also makes it difficult to emphasize the coherence
> >> and cohesiveness of the newly added operation and does nothing to
> >> explain how to organize the access of the new operation to all pieces of
> >> data it may need. Taking into account that the whole Visitor pattern is
> >> about adding a new operation to working on a collection or stream ("the
> >> structure") of objects, it is a very unfortunate choice indeed.
> >>
> > The whole point of calling o.accept(v) that then calls v.doOp(o) is double
> > dispatch. Otherwise why not to call v.doOp(o) right away and erase the
> > accept()?
> You could, the complete class of Visitor problems is still solvable
> without the accept shim. It is an implementation detail as well. It is
> sometimes useful for the purposes other than that particular DD
> implementation, e.g. to limit the exposure of the element types to the
> "object structure" participant.
>
IOW as you do something else but visitor the accept() is unneeded
implementation detail, but for whatever cargo cult reason you added
that accept().
> For an example of a framework implementing Visitor without shims, see
> e.g. std::visit.
>
There you go into another paradigm entirely, generic programming and
specific tagged union implementation. Note if constexpr's used.
So the dispatch is done compile time by generic lambda. Generic
programming was invented also in seventies. C++ is multi-paradigm
programming language. Don't confuse unrelated paradigms into
visitor that is pattern of object-oriented programming and uses double
dispatch.
> > Think a bit, wall of text how everybody around are confused
> > does not make it true.
> agree
> >
> >>>
> >>>> The advantage is in having this logic in one place. If your visitor does
> >>>> a coherent thing (which it is supposed to do; else why is it a single
> >>>> visitor, there could be and usually would be common code for all or many
> >>>> visited). The most pliant cases (that are exactly those gaining most
> >>>> from applying a visitor pattern) may get by object virtual methods.
> >>>> Simple example:
> >>>>
> >>>> If we wanted a green border around a shape of any of 10 classes derived
> >>>> from convex polygon (ConvexTriangle, ConvexQuadrilateral etc), red
> >>>> border around a shape of any of 10 classes derived from Oval and no
> >>>> border around any of 30 other zero-area shape classes, all we need to do
> >>>> is to change doOp to:
> >>>>
> >>>
> >>> Unused cp in your code?
> >> Yes, I started with taking bounding rectangle from every type separately
> >> (which sometimes may be a valid micro-optimization if we use a fully
> >> qualified non-virtual call; but then decided to keep it simple to
> >> emphasize the points I wanted to emphasize)
> >>> Can't post sane code from gg ... but also don't want to install newsreaders
> >>> to all devices I use. It is drawing border to any shape and so it is unclear
> >>> why you need visitor.
> Because to decide whether to draw the border and (in the Example 2) its
> color the element type is needed. I was about to introduce an
> intermediate "ConvexArea" class but got lazy; regardless, Example 2
> demonstrates the dependency on type.
> Logical one place is either DrwawingContext's or
> >>> Shape's nonvirtual method or free function:
> >>>
> >>> void Shape::draw_border(DrawingContext &dc) const {
> >>> if (area() <= 25) return;
> >>> const Rectangle br{boundingRectangle().increaseBy(0.1)};
> >>> if (isConvexPolygon()) { dc.drawGreenRectangle(br); return; }
> >>> assert(isOval());
> >>> dc.drawRedRectangle(br);
> >>> }
> >> exactly, if you are ok to change the hierarchy every time you add an op,
> >> you might not need the visitor, all you need is to call draw_border from
> >> some for_each loop or high-order function.
> >>
> > Free function does no way change hierarchy. Adding non-virtual
> > convenience method to base class of one of participants does
> > it minimally.
> See above about dependency and maintenance.
> >
> >> This might be a valid choice sometimes (my first post started with
> >> this), but you pay by changing every class *every time you need a new
> >> operation* and making all Shape classes dependent on DrawingContext (and
> >> other contexts you might need for different operations (e.g. log file,
> >> network socket, database connection etc)). In visitor pattern, on the
> >> other hand, the concrete visitor is the only code that becomes dependent
> >> on that op-specific context.
> >>
> > It is totally OK choice for your presented use-case, visitor is unneeded
> > bloat there.
> It *is* a Visitor as it has all the participants and solve the problem.
> >
> >>>
> >>>>
> >>>> And this is the complete code for adding a new operation (virtual cast
> >>>> is to be only defined once for all operations)
> >>>
> >>> You forget that you have BorderingVisitor class of unclear life , inject
> >>> DrawingContext to it (is it copy?), etc. result is
> >>>
> >>> BorderingVisitor bv(dc);
> >>> s.acceptVisit(bv);
> >>>
> >>> instead one of:
> >>>
> >>> dc.draw_border(s);
> dc is not supposed to know anything about a Shape, It draws a
> rectangular body given a Rectangle only to specify the exact geometry of
> the Shape. It is not a business of dc to compute. What if I want to,
> instead of as a border of the size of the shape-cirumscribing rectangle
> increased by 0.1 unit in all directions, add margins of 2 units at the
> right and 1 unit at other 3 directions? That increaseBy call in my
> example code is there for reason.
> >>>
> >>> s.draw_border(dc);
> >>>
> >>> draw_border(dc, s);
> >>>
> > See? Crickets are chirping,
> no, they are strangely croaking, the border does not look like I wanted
> it to, see above.
> > no one is explaining why the bloat was even
> > needed.
> someone did but someone else did not like to read. Where's my increaseBy?
> >
I repeat:
void Shape::draw_border(DrawingContext &dc) const {
if (area() <= 25) return;
const Rectangle br{boundingRectangle().increaseBy(0.1)};
**Try reading above line extra carefully.**
if (isConvexPolygon()) { dc.drawGreenRectangle(br); return; }
assert(isOval());
dc.drawRedRectangle(br);
}
Can easily transform into free function if Shape is bad place.
> >>>>> But we need no bloat from that pattern to do neither. These were the
> >>>>> things we wanted to get rid of.
> >>>> Care to show us the code for your "non bloated" "one-place" visitor
> >>>> class for the above 2 problems? (like I did, you can omit the code that
> >>>> is common for all operations).
> >>>
> >>> Yes, sorry for gg breaking all formating.
> >>>>>
> >>>>>> In practice this can be
> >>>>>> anything, examples (non-exhaustive): a enum type discriminator, virtual
> >>>>>> cast method, (in modern languages), pattern matching.
> >>>>>>>
> >>>>>>>> 3. add a single method to B, e.g. acceptVisit that takes [ref to]
> >>>>>>>> Visitor and calls doOp on it. NOTE: only one such method is needed, no
> >>>>>>>> matter how many ops you are adding.
> >>>>>>>
> >>>>>>> Not enough. Every type processed has to have that copy-paste
> >>>>>>> acceptVisit method. Otherwise double dispatch does not work.
> >>>>>>
> >>>>>> Double dispatch works just fine without copy-pasting. Dispatch #1 (by
> >>>>>> the operation type) is done by the virtual method doOp. Dispatch #2 (by
> >>>>>> the object type) is done by using the actual type to call specific
> >>>>>> methods and/or calling virtual methods, on the object ref.
> >>>>>
> >>>>> Of course huge switch or calling virtual method (or now even multiple)
> >>>>> work without the whole pattern. Read up on "double dispatch".
> >>>>> It is tricky like I already said.
> >>>>>
> >>>> Surely you are the most competent software engineer understanding
> >>>> multiple dispatch. Care to point out a "huge switch" in either of the
> >>>> above 2 examples?
> >>>
> >>> There are no double dispatch in your code so the whole acceptVisit, doOp
> >>> add nothing.
> >>
> >> Yes it is, see above (or directly Wikipedia article on multiple dispatch
> >> for the examples on how Double dispatch can be implemented in C++).
> >>
> > Do not consider multiple dispatch before figuring what double
> > dispatch is.
> not again. I already cited the definition of what DD is. You refer to a
> particular implementation as ff it were the definition.
>
It is multiple dispatch, but do not go there as multiple dispatch you can't
grasp yet.
> >
> >>> These
> >> Unsure I understand what "these" refers to, will assume my doOp and
> >> acceptVisit functions
> >>> were entirely added to do double dispatch
> >>
> >> correct. acceptVisit dispatches by op (the 1st parameter of the outer
> >> structure "visit" operation) and doOp dispatches by object type (in my
> >> examples, either by using its base type's virtual function only or via a
> >> combination of the same and `if' statement on virtual cast result)
> >>
> > Except on your case it does not. There is just single function that
> > decides if to draw red or green border.
> yes, depending on the type. Do you expect colors, styles, font foundries
> and textures from an example on visitor? You are welcome to provide your
> own code (the above does not work).
>
It was doing exactly same as your posted code.
> >
> >>> to add
> >>> virtual methods to large potentially unrelated data hierarchies without
> >>> changing any of classes in those hierarchies.
> >> I assume, by "potentially unrelated" data hierarchy, you mean the new op
> >> has to behave entirely differently on every type of the hierarchy and
> >> does not need common data. The issue with this assumption is that the
> >> very first op you add might relate to the classes like this with but the
> >> second might benefit from exploiting the type similarities or grouping
> >> them differently (not even necessary in groups compact in the hierarchy
> >> graph).
> >>> What you do is not even
> >>> use case for visitor pattern.
> >> Says Wikipedia:
> >>
> >> "
> >> What problems can the Visitor design pattern solve?
> >>
> >> It should be possible to define a new operation for (some) classes of an
> >> object structure without changing the classes.
> >> "
> >
> > "When new operations are needed frequently and the object structure
> > consists of many unrelated classes, it's inflexible to add new subclasses
> > each time a new operation is required because "[..] distributing all these
> > operations across the various node classes leads to a system that's hard
> > to understand, maintain, and change."
> You know what you did? You made me dig out my (licensed) CD-ROM with GoF
> book. Thank you! As I suspected, the Wikipedia quoted GoF in absolutely
> wrong context. There is nothing about "adding new subclasses" in GoF
> book, it is all about changing Node hierachy classes. The complete
> paragraph says:
>
> "
> This diagram shows part of the Node class hierarchy. The problem here is
> that distributing all these operations across the various node classes
> leads to a system that's hard to understand, maintain, and change. It
> will be confusing to have type-checking code mixed with pretty-printing
> code or flow analysis code. Moreover, adding a new operation usually
> requires recompiling all of these classes. It would be better if each
> new operation could be added separately, and the node classes were
> independent of the operations that apply to them.
> "
> (this paragraph is below the diagram depicting a part of Element
> hierarchy, has nothing to do to Visitor classes or adding "new
> subclasses". Unsure where this came from).
>
> Whoever wrote this page should be .. well, politely reprimanded.
>
> The quote above also emphasizes my point that adding compilation
> dependency to the element type hierarchy shall not be taken lightly.
> >
> > Exactly. But if all you need is single little function then write single
> > little function. Neither single dispatch (virtual functions) nor double
> > dispatch (matching visitor classes to data hierarchy classes) are
> > needed for single little function. How did your function being in
> > visitor make anything easier to understand, maintain and change?
> How many operations should I have exemplified? I showed 2 and stated the
> Visitor is applicable when one is to add plenty -- what else do you
> expect, not providing a single complete counterexample for the same problem.
>
The visitor pattern examples are present in GoF book and in Wikipedia
article. You argue with those, what is the point of me adding one more?
> >
> >>
> >> "What I do" achieves the above IMO, so it is definitely the use case.
> >> You might argue whether or not my solution is a Visitor pattern (it is,
> >> at least it includes all canonical Visitor participants (Abstract
> >> Visitor, Concrete Visitors, Abstract Element, Concrete Elements (an
> >> object structure providing access to the elements is implied)) and none
> >> extra) but I don't understand how it can be argued that my code does not
> >> define a new operation for (some) classes of an object structure. And it
> >> surely does not change the classes.
> >>
> > That is especially bad and confusing when something looks like a
> > pattern, uses names from that pattern but actually isn't the pattern
> > at all in essence.
> Why is it you and not I who gets to define the "essence"? I stated the
> formal Visitor applicability conditions exactly to make this discussion
> objective, to the point, and maybe agree to something useful. How can we
> agree on anything useful if I start referring to some esoteric "essence"
> as I understand it?
>
The OOP pattern we discuss is not invented by me or you. I don't change
it into "less boring" or whatever. It is what it is. You do change it. So you do
not argue with my "personal definitions" here.
[toc] | [prev] | [next] | [standalone]
| From | Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> |
|---|---|
| Date | 2023-08-26 00:55 -0400 |
| Message-ID | <nBfGM.634172$TCKc.364728@fx13.iad> |
| In reply to | #91415 |
Öö Tiib wrote:
> On Friday, 25 August 2023 at 06:12:09 UTC+3, Pavel wrote:
>> Öö Tiib wrote:
>>> On Thursday, 24 August 2023 at 08:39:04 UTC+3, Pavel wrote:
>>>> Öö Tiib wrote:
>>>>> On Tuesday, 22 August 2023 at 05:55:32 UTC+3, Pavel wrote:
>>>>>> Öö Tiib wrote:
>>>>>>> On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote:
>>>>>>>> Öö Tiib wrote:
>>>>>>>>> On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote:
>>>>>>>>>
>>>>>>>>>> 1. Ensure all objects have a common base class (say, it's B)
>>>>>>>>>
>>>>>>>>> Not needed.
>>>>>>>>>
>>>>>>>>>> 2. create an abstract class Visitor with an abstract method doOp with an
>>>>>>>>>> arg of type [ref to] B.
>>>>>>>>>
>>>>>>>>> Nope, pile of overloads of doOp one for each type processed.
>>>>>>>>
>>>>>>>> This would render the pattern useless as has no advantage over adding a
>>>>>>>> virtual method to do the op. The rationale of the pattern is to permit
>>>>>>>> coding one op in one place.
>>>>>>>>
>>>>>>> The Visitor class is that "one place"; doing same operation lets say printing
>>>>>>> of 50 types in one function results with huge function that is headache
>>>>>>> not advantage. Separate functions are testable and maintainable.
>>>>>>>
>>>>>>>>> Otherwise
>>>>>>>>> how you process different types differently?
>>>>>>>> I explained this in the problem statement. "Given [a ref to] any such
>>>>>>>> object, you are able to
>>>>>>>> compute its type and/or call a virtual method".
>>>>>>>
>>>>>>> IOW you do huge switch typeid ... case static_ cast or have the virtual
>>>>>>> method (over what we were "gaining advantage") already there?
>>>>>>
>>>>>> Even a switch with 30 cases is no worse than 30 methods although switch
>>>>>> is not always necessary (try to read the above instead of imagining).
>>>>>>
>>>>> Visitor is typically used to search, filter, draw or print whole data object
>>>>> hierarchy, convert to JSON to XML or to tree in GUI.
>>>> correct
>>>>> If whole data hierarchy
>>>>> is small then you get 30 cases. Switch case is worse than virtual methods.
>>>>
>>>> *In general* neither is better or worse.
>>>
>>> Switch case over type is in general worse than virtual functions. Longer
>>> function,
>> Longer than what? Definitely the one function is shorter than the sum of
>> the length of all the element type hierarchy shims plus concrete visitor
>> visit overloads.
>>
>>> bigger cyclomatic complexity,
>> with good code organization, no bigger than absolutely necessary.
>>
>>> harder to understand, with good code organization, actually, easier (see my point below about
>> maintenance.
>>> harder to
>>> form confidence that it is valid,
>> see below in maintenance.
>>
>>> harder to maintain,
>> actually, easier to maintain. For using that GoF / wiki DD
>> implementation, non-insignificant amount of scaffolding code has to be
>> written. Especially in a complex hierarchy, one can easily forget to
>> write that accept shim for a couple of classes where it is needed. E.g.
>> if in the hierarchy (or a part thereof) is "(B, C, D) > A" (> meaning
>> inheritance), imagine the op shall do same on A and D but different on B
>> and C. Isn't it easy to forget to add the accept shim to C (even though
>> the actual logic visit(C), at which the programmer is likely to focus,
>> is written all right).
>>
> Can't.
??? You forgot to add accept function to C. Then your object of dynamic
type C will be happily dispatched to VisitorX::visitElementA by A::accept.
You can not leak knowledge of particular operation implemented
> by VisitorX that happens to be same for A and D into base visitor. Base
> visitor has to have all. Something like:
> virtual doOp(A& a) {std::abort();}
> virtual doOp(B& b) {std::abort();}
> virtual doOp(C& c) {std::abort();}
> virtual doOp(D& d) {std::abort();}
>
> It is because different operation implemented by VisitorY may be
> same for A and B and different for C and D. You can only leave out
> handling of D in derived visitor if D is never processed by that visitor,
> so std::abort() suits you.
what do argue against?
>
>>> harder to test and
>> why?
>>> also may need more access to implementation details of all types
>>> involved, breaking encapsulation.
>> How so? All code is written in the op function that has no access to
>> internals.
>>
> You have example where you need short function and then argue against
> whole existence of issues why procedural programming was invented in
> fifties, structured programming was invented in sixties, object-oriented
> programming and double dispatch were invented in seventies. The issues
> are still real in real projects, and ignoring these is called code smell.
> I can not be expected to teach all that knowledge of decades in one post.
> Lets take step by step?
You are still to provide an example. For now, there nothing to take
apart step by step except for my code that you are not even trying to
take apart.
>
>>>
>>>> In particular, as I explained
>>>> at the beginning, if the operation is decomposed correctly, its behavior
>>>> on the objects of different types has to be coherent, similar in some
>>>> way (and maybe more similar than different) and, as such, a significant
>>>> parts of the context or results of intermediate computations could be
>>>> shared.
>>>
>>> Translating data hierarchy to xml for particular version of particular
>>> protocol while supporting several such in parallel?
>>
>> Imagine {ABCD) type hierarchy above is to be translated to XML and B and
>> C have to become XML elements with some non-trivially computable
>> attribute atr1 but XML elements produced from A and D don't need it.
>>
> Because different endpoints (for what you form the XML) want different
> data, one wants like you describe ... other does not want atr1 for A and B
> ... while third does not want information about D to be ever sent to it.
> Now you have 3 different visitors making XML for each endpoint and any
> logic in base visitor about first hurts other two.
What does your "Because" refer to? There is no "why" in my text (and no
question mark at all). I thought you disliked walls of words? What are
"endpoints"? I do not understand what "make XML for an endpoint" means.
I likely format XML to whatever output stream the visitor caller
provides. If that stream is an endpoint, what significance it has to
this discussion?
>
>>> There can be
>>> intermediate computations shared but all are typically in in visitor
>>> classes or helpers of those. No one wants the classes in hierarchy
>>> to know anything about xml whatsoever,
>> correct but do not see how it is connected to anything I wrote
>>> nothing to talk about
>>> particular brand used in particular version of kinds of end-points.
>> let's not argue with anything I did not say, it's really irritating, I
>> will gladly give you an example, just explain why {ABCD} above is no good.
>>
> I tried to.
No, your words above do not refer to my argument even grammatically.
>
>>>
>>>> I demonstrated this in my example with red and green borders: a)
>>>> a condition by area prevents visitor from operating on objects is
>>>> computed from their property that is orthogonal to their dynamic type;
>>>> and b) the exact behavior of the operation (red vs green border in the
>>>> example) depends on a subset of types (which, in general, is not
>>>> necessarily compact in the hierarchy).
>>>>
>>> Your example did not need visitor pattern.
>>
>> No example "needs" visitor pattern: any problem solved with visitor can
>> be solved in other ways, e.g. just by adding a virtual method to every
>> class. But *both* my examples could be perfectly solved with visitor. If
>> the one-time scaffolding is in place, I am ready to argue it is a good
>> solution.
>>
> True about adding virtual method, just that adding 3 virtual functions for XML
> generation code into every class will clutter code of those classes.
You are essentially repeating arguments for Visitor from
advantage/drawback statements on my first post. What purpose is this
repetition supposed to serve in this discussion?
> Especially if you want (some of) same classes to be used in other project
> that does not communicate with said end-points at all.
what is "end-points"? I am addressing every your point but your
"responses" don't even refer to mine. Can you keep to the point and not
go on tangents please?
I will make last honest effort to structure this discussion to something
legible: do you agree that we disagree on the following points:
1. That my examples demonstrate adding an op to a correctly built
Visitor pattern, assuming verbally described scaffolding (I say yes, you
say no)
2. That any correct implementation of a Visitor pattern in C++ shall use
that specific implementation of DD that GoF and Wikipedia happen to use.
(I say no, you say yes)
?
It is the implication of my opinions on #1 and #2 above that Visitor can
be tought on smaller and more to-the-point examples. But we don't need
to argue about this until we put to bed #1 and #2. Agree?
>
>>>
>>>>> Think why virtual methods were added? To get rid of switch cases over type.
>>>> Of course, not. Virtual methods were *used*, with less or greater
>>>> success, to replace switch statements in event-processing scenarios
>>>> (like window behavior) (with greater success) or in state machines (with
>>>> lesser success).
>>>>
>>>> But, they were *invented* to implement polymorphic behavior and
>>>> relatively loose coupling (generally, lesser dependencies) between the
>>>> code that calls the method and the code that implements the method.
>>>>
>>> Same loose coupling is with code that calls function that does switch
>>> case over type, so that kind of victory wasn't achieved.
>>
>> There is no such thing as "coupling in general".
>
> Where I said the quoted text "coupling in general"? What you mean by it?
You mentioned coupling without specifying what particular coupling you
meant in what particular code so it is impossible for me to guess what
you mean. Therefore I reasonably called your reference to coupling as
reference to "coupling in general".
> And why you switch to visitors when discussing benefits of virtual
> methods?
It was you who went on tangent about virtual functions asking me to
think why "virtual methods were added" and then provided incorrect
answer to your own question. I simply corrected that incorrect answer of
yours.
>
>> Any particular
>> dependency is some kind of coupling. If one has to add
>> accept(AbstractVisitor) method to the root of the type hierarchy, both
>> the type hierarchy's interface and implementation become compile-time
>> dependent on the new operation's interface (AbstractVisitor) (interface
>> may depend on forward-declaration only in C++, but not the
>> implementation so the implementation will also "see" N "visit" methods).
>> Additionally, because every AbstractVisitor depends on (the interface
>> of) every of (say, M) concrete element types, the Element's interface
>> implementation becomes dependent on every of M Element types (at least
>> their forward declarations, in C++). In this implementation, any new
>> operation adds the dependency, needs recompilation of class hierarchy etc.
>>
> The whole point of visitor pattern is that you need to add accept of base
> visitor only once to every class visited, no need to add 3 virtual methods to
> generate XML for 3 different endpoints. But can't simplify from there,
> otherwise the pattern is broken.
Another bareword. How exactly this breaks the pattern? Does operation
start to behave not as specified? -- IMO, no. Do we introduce extra
participant to the pattern to "save" the desired behavior by adding
extra entity? -- IMO, no. Do we force a compilation unit depend on more
entities? -- IMO, no. So, what exactly do we break?
>
>> If, as in my example, you only have one AbstractVisitor, adding 2nd and
>> further operations adds zero dependencies, don't require recompilation.
>>
> No class needs to be recompiled when 4th server needing obscure
> abomination of JSON is added.
When did I say the opposite? I just counted the number of dependencies
(that have to be there from the outset). Every type in the hierarchy
becomes dependent on any other type because its accept method depends on
AbstractVisitor and it has methods dependent on every type in the
hierarchy, at least its forward declaration. This may be also true for
my approach if specific type implementation is dispatched with virtual
cast function (because we need to define them at the base class) or not
if we use typeid or enum or pattern matching.
> Same stock
>
> void WhateverClass::accept(BaseVisitor& s) override { s.doOp(*this); }
>
> will work like these already did. Only
>
> class ObscureJSONGeneratorVisitor: public BaseVisitor { /* ... */ };
>
> will need to be implemented.
>
>>>
>>>> Obviously, a single switch statement couples everything together and the
>>>> compilation unit where the switch is depends on every piece of code and
>>>> every concrete type the switch statement handles -- to the extent its
>>>> behavior in the operation must differ from that of other types). But
>>>> this loose coupling is only desirable when the behaviors are severely
>>>> different.
>>>>
>>> I don't follow what you are saying. Different stock markets and other
>>> financial institutions for example are not severely different in essence,
>>> but the format of data you are expected to exchange with their servers
>>> differs massively enough.
>>
>> Nah, it's the other way around. Say, BankA and BankB may have different
>> formats of their, say, client-side FIX protocol. But, they likely both
>> talk to NYSE using NYSE FIX/FAST and OATS reporting in exactly same or
>> almost same format.
>>
> They talk with NYSE in manner that NYSE requires, but that does not concern
> you, you are not NYSE that financial institutions themselves have to care how
> to be conformant with. You are probably sucking the client-side butt holes of
> theirs.
See below why you are likely NYSE if you care about supporting different
financial institutions.
>
>> Also, who needs to write an app that puts BankA and BankB to the same
>> hierarchy? It's unlinkely that it's one of BankA or BankB. More likely,
>> some regulatory agency or an exchange or settlement firm (hereinafter
>> *an outside firm*). For the purpose of the outside firm, the formats
>> it's interested in to operate on are likely the formats it uses to
>> communicate with those Banks (i.e. same or with small customizations). A
>> drastically different formats that BankA and B use to communicate with
>> their clients will unlikely be needed by an exchange (they could be
>> needed by a surveillance agency; but then "my" code would become
>> respectively simpler, *always staying simpler* than the correspondent
>> GoF code).
>>
> So you never heard of anyone who happens to have accounts in multiple
> banks (even in multiple countries)? I suggest urgently to read up on
> Silicon Valley Bank, Signature Bank, First Republic Bank and Heartland
> Tri-State Bank ... your bank may be next.
Is your purpose to throw as many tangents as needed to get me confused?
If I have account in a couple of banks, am I suppose to write a program
to do my banking?
>
>>>
>>>> On the other hand, when the operation does *to significant
>>>> extent, a single thing* on different type, with 2-3 relatively small
>>>> type-dependent differences, these differences are often better expressed
>>>> in if statement, switch or in a number of different ways (including
>>>> calling combinations of virtual functions *from within the top-level
>>>> method implementing the operation*)
>>>>
>>> That is not use-case of visitor pattern.
Bareword again. Nowhere in Visitor pattern rationale and motivation does
it say that it should only be used when the behavior of an op on
different hierarchy types should not have commonalities. All it says
that operations are unrelated one to another.
One actually has to expect there will be commonalities on applying an op
to objects of different types of the hierarchy; else why are these types
are in this same hierarchy?
>> You say it more than once and never cared to provide any reasoning. I
>> cited the cause to use Visitor (from Wikipedia you should like). That
>> cause is fully applicable for the above setup assuming one needs to add
>> many operations. I do not intend to repeat this again unless you provide
>> good reasoning.
>>
> I did, you just do not read.
I read everything your wrote.
>
>>>
>>>>> Visitor is not meant as excuse to add those back, otherwise just use
>>>>> the switch case in function, do not manufacture visitors that do not use
>>>>> double dispatch for confusion.
>>>>
>>>> Ohh. Not again. If you want to use virtual functions, use them, you
>>>> don't need visitor for this. Add virtual void newOperation(context
>>>> parameters) to the base class and call it in some for_each. This is
>>>> exactly what I explained in the first post: Visitor itself is an
>>>> alternative to adding a virtual methods.
>>>>
>>> You add same number of virtual methods of visitor if you need several
>>> different classes of visitors to visit over same data hierarchies. The
>>> gain is that these are in one place, in particular visitor and that the
>>> classes in hierarchies are not changed by single letter.
>> Yes, not changing classes when adding an op is the one of the causes for
>> using Visitor. Both my and Wiki visitor implementation formally achieve
>> this cause. Plain and simple. BUT, Wiki implementation changes the
>> compilation units of every Element (aka creates compilation dependency).
>> Plain and simple. My implementation doesn't (except for the very 1st
>> operation).
>>>
>>>> The criteria which one to
>>>> choose are well known from the original works on Visitor, like GoF etc:
>>>> if your type hierarchy is big, stable, its model is well understood and
>>>> established (like GUI toolkit), and hence you are unlikely to change the
>>>> hierarchy often BUT you need to add new operations often, use visitors
>>>> (because you don't need to add thing to every class for every
>>>> operation). If your type hierarchy is dynamic and is expected to grow
>>>> often but the set of operation is stable, well establish, unlikely to
>>>> change often (like unit actions in a strategy game where you add and
>>>> enhance units but all they are going to do is to essentially move, shoot
>>>> and die), add a virtual method.
>>>>
>>>> The first thing to explain about Visitor (or any other design pattern)
>>>> is how to decide when to use it and when not to use it (the scope).
>>>>
>>> That is true, but you seem to suggest to misuse it, where unneeded.
>>>
>>>> If the decision to use Visitor is correct, we already know the class
>>>> hierarchy is stable and there is no harm for the code to depend on a
>>>> fixed set of these classes. The double dispatch for Visitor is just a
>>>> tool to dispatch to the code specific to the type after you dispatched
>>>> to the specific operation. Nothing more, nothing less, see definition of
>>>> multiple dispatch. The specific way to double-dispatch that many Visitor
>>>> examples (unfortunately, started with GoF book, and shown in Wikipedia)
>>>> show, is simply the *implementation detail*. You do not have to trust me.
>>>>
>>> Do not touch multiple dispatch yet. Going there now is like going into
>>> four dimensional math when you haven't yet got the planar 2D grid
>>> to work. Stay firmly within studying double dispatch until you understand
>>> what it is.
>> That's not I, that's you favorite ref source Wiki correctly says double
>> a dispatch is a special form of multiple dispatch and then, in all C++
>> examples it gives to show MD, it actually shows DD only. In fact, both
>> articles use same "Asteroid-Spaceship" example (DD) to exemplify either.
>>
> Wikipedia is correct, but multiple dispatch is way over head when you can't
> apparently figure what is double dispatch ... so go not into multiple.
Who does not read here? Trying once again: WIKIPEDIA C++ EXAMPLES ON MD
ACTUALLY DEMONSTRATE DD. How can a simple example of MD that is DD be
over head if you insist that another example of DD (actually,
implemented in a messier manner) has to be understood first?
>
>> Now IMO, here is nothing magical in dispatching on dynamic types of 3
>> parameters as compared to the 2. It is surely more boring to express as
>> there is usually more functions to write; but otherwise, nothing too
>> exciting.
>>
> You can't understand rectangle yet so parallelepiped is way too early.
Now your are convincing.
>
>>>
>>>> Says Wikipedia:
>>>>
>>>> "In software engineering, double dispatch is a special form of multiple
>>>> dispatch, and a mechanism that dispatches a function call to different
>>>> concrete functions depending on the runtime types of two objects
>>>> involved in the call"
>>>>
>>>> And, looking at same Wikipedia on multiple dispatch, C++ is *not* one of
>>>> the languages that natively support multiple dispatch and there are
>>>> multiple ways of implementing it, among them
>>>> - "using run time type comparison via dynamic_cast" (if statement method)
>>>> - "pointer-to-method lookup table"; and (misleadingly)
>>>> - "the visitor pattern"
>>>>
>>>> Why "misleadingly"? Because, looking at visitor pattern, you find
>>>>
>>>> "The visitor takes the instance reference as input, and implements the
>>>> goal through double dispatch."
>>>>
>>>> So, according to Wikipedia article on multiple dispatch one can
>>>> implement it (among the others) using visitor pattern, whereas visitor
>>>> pattern "implements the goal via double dispatch" whereas the double
>>>> dispatch is a special form of multiple dispatch.
>>>>
>>> Yes. you were confused by first sentence in wikipedia and stopped to
>>> read there. Double dispatch is multiple dispatch like 2D grid is special
>>> case of 4 dimensional room with 2 dimensions being of zero size.
>> Nothing like it. Implementing DD thru the manual scaffolding of what you
>> seem to believe *the one and the only correct implementation of DD for
>> Visitor pattern in C++" is boring and error-prone enough (see above
>> about maintenance) to not be the first choice in explaining Visitor.
>>
> Then you are out of luck
hah?
> as you talk about something that is not visitor
> pattern.
I do not talk about a particular implementation of it that you are so
fond of, correct.
> "In essence, the visitor allows adding new virtual functions to a family of
> classes, without modifying the classes. Instead, a visitor class is created
> that implements **all** of the appropriate specializations of the virtual
> function. The visitor takes the instance reference as input, and implements
> the goal through **double dispatch**."
>
> You argue with that and GoF not me.
where? You should have emphasized the word "appropriate" rather than
"all". Double dispatch is correctly emphasized, but your fondness to one
particular kind of it does not mean there is no others.
>
>>>
>>>> Some readers (which seem to include you) resolve this circular reference
>>>> ad-hoc: they see that *specific implementation* of multiple dispatch in
>>>> C++ that happens to illustrate the Visitor article (stemming from the
>>>> GoF book, unfortunately) and somehow decide that this is *the right way*
>>>> (TM) to implement double dispatch. This is despite:
>>>>
>>>> - that double dispatch is defined as a concept an there is no *the right
>>>> way* of doing it in C++ and Wikipedia itself illustrates two such ways
>>>> (both different from the one used in the visitor article and GoF book)
>>>> in the article on multiple dispatch.
>>>>
>>>> - that it must be obvious that it is Visitor that uses double dispatch
>>>> in its implementation, not other may around, because the part cannot use
>>>> the whole and Visitor is "bigger" than double-dispatch because it uses
>>>> both double dispatch and iteration (through the objects in a data
>>>> structure) but iteration is not a part of double dispatch.
>>>>
>>>> The problem with that (by and large, randomly-selected) implementation
>>>> of double dispatch is that it is a bad choice to illustrate Visitor
>>>> pattern specifically. Besides the mere distracting the reader to its
>>>> clever *implementation details* (not essential for understanding the
>>>> Visitor pattern), it also makes it difficult to emphasize the coherence
>>>> and cohesiveness of the newly added operation and does nothing to
>>>> explain how to organize the access of the new operation to all pieces of
>>>> data it may need. Taking into account that the whole Visitor pattern is
>>>> about adding a new operation to working on a collection or stream ("the
>>>> structure") of objects, it is a very unfortunate choice indeed.
>>>>
>>> The whole point of calling o.accept(v) that then calls v.doOp(o) is double
>>> dispatch. Otherwise why not to call v.doOp(o) right away and erase the
>>> accept()?
>> You could, the complete class of Visitor problems is still solvable
>> without the accept shim. It is an implementation detail as well. It is
>> sometimes useful for the purposes other than that particular DD
>> implementation, e.g. to limit the exposure of the element types to the
>> "object structure" participant.
>>
> IOW as you do something else but visitor the accept() is unneeded
> implementation detail, but for whatever cargo cult reason you added
> that accept().
>
>> For an example of a framework implementing Visitor without shims, see
>> e.g. std::visit.
>>
> There you go into another paradigm entirely, generic programming and
> specific tagged union implementation. Note if constexpr's used.
> So the dispatch is done compile time by generic lambda. Generic
> programming was invented also in seventies. C++ is multi-paradigm
> programming language. Don't confuse unrelated paradigms into
> visitor that is pattern of object-oriented programming and uses double
> dispatch.
They are only unrelated in your mind. Design patterns are not libraries,
they can be implemented in different ways and in different paradigms.
>
>>> Think a bit, wall of text how everybody around are confused
>>> does not make it true.
>> agree
>>>
>>>>>
>>>>>> The advantage is in having this logic in one place. If your visitor does
>>>>>> a coherent thing (which it is supposed to do; else why is it a single
>>>>>> visitor, there could be and usually would be common code for all or many
>>>>>> visited). The most pliant cases (that are exactly those gaining most
>>>>>> from applying a visitor pattern) may get by object virtual methods.
>>>>>> Simple example:
>>>>>>
>>>>>> If we wanted a green border around a shape of any of 10 classes derived
>>>>>> from convex polygon (ConvexTriangle, ConvexQuadrilateral etc), red
>>>>>> border around a shape of any of 10 classes derived from Oval and no
>>>>>> border around any of 30 other zero-area shape classes, all we need to do
>>>>>> is to change doOp to:
>>>>>>
>>>>>
>>>>> Unused cp in your code?
>>>> Yes, I started with taking bounding rectangle from every type separately
>>>> (which sometimes may be a valid micro-optimization if we use a fully
>>>> qualified non-virtual call; but then decided to keep it simple to
>>>> emphasize the points I wanted to emphasize)
>>>>> Can't post sane code from gg ... but also don't want to install newsreaders
>>>>> to all devices I use. It is drawing border to any shape and so it is unclear
>>>>> why you need visitor.
>> Because to decide whether to draw the border and (in the Example 2) its
>> color the element type is needed. I was about to introduce an
>> intermediate "ConvexArea" class but got lazy; regardless, Example 2
>> demonstrates the dependency on type.
>> Logical one place is either DrwawingContext's or
>>>>> Shape's nonvirtual method or free function:
>>>>>
>>>>> void Shape::draw_border(DrawingContext &dc) const {
>>>>> if (area() <= 25) return;
>>>>> const Rectangle br{boundingRectangle().increaseBy(0.1)};
>>>>> if (isConvexPolygon()) { dc.drawGreenRectangle(br); return; }
>>>>> assert(isOval());
>>>>> dc.drawRedRectangle(br);
>>>>> }
>>>> exactly, if you are ok to change the hierarchy every time you add an op,
>>>> you might not need the visitor, all you need is to call draw_border from
>>>> some for_each loop or high-order function.
>>>>
>>> Free function does no way change hierarchy. Adding non-virtual
>>> convenience method to base class of one of participants does
>>> it minimally.
>> See above about dependency and maintenance.
>>>
>>>> This might be a valid choice sometimes (my first post started with
>>>> this), but you pay by changing every class *every time you need a new
>>>> operation* and making all Shape classes dependent on DrawingContext (and
>>>> other contexts you might need for different operations (e.g. log file,
>>>> network socket, database connection etc)). In visitor pattern, on the
>>>> other hand, the concrete visitor is the only code that becomes dependent
>>>> on that op-specific context.
>>>>
>>> It is totally OK choice for your presented use-case, visitor is unneeded
>>> bloat there.
>> It *is* a Visitor as it has all the participants and solve the problem.
>>>
>>>>>
>>>>>>
>>>>>> And this is the complete code for adding a new operation (virtual cast
>>>>>> is to be only defined once for all operations)
>>>>>
>>>>> You forget that you have BorderingVisitor class of unclear life , inject
>>>>> DrawingContext to it (is it copy?), etc. result is
>>>>>
>>>>> BorderingVisitor bv(dc);
>>>>> s.acceptVisit(bv);
>>>>>
>>>>> instead one of:
>>>>>
>>>>> dc.draw_border(s);
>> dc is not supposed to know anything about a Shape, It draws a
>> rectangular body given a Rectangle only to specify the exact geometry of
>> the Shape. It is not a business of dc to compute. What if I want to,
>> instead of as a border of the size of the shape-cirumscribing rectangle
>> increased by 0.1 unit in all directions, add margins of 2 units at the
>> right and 1 unit at other 3 directions? That increaseBy call in my
>> example code is there for reason.
>>>>>
>>>>> s.draw_border(dc);
>>>>>
>>>>> draw_border(dc, s);
>>>>>
>>> See? Crickets are chirping,
>> no, they are strangely croaking, the border does not look like I wanted
>> it to, see above.
>>> no one is explaining why the bloat was even
>>> needed.
>> someone did but someone else did not like to read. Where's my increaseBy?
>>>
> I repeat:
>
> void Shape::draw_border(DrawingContext &dc) const {
> if (area() <= 25) return;
> const Rectangle br{boundingRectangle().increaseBy(0.1)};
>
> **Try reading above line extra carefully.**
>
> if (isConvexPolygon()) { dc.drawGreenRectangle(br); return; }
> assert(isOval());
> dc.drawRedRectangle(br);
> }
>
> Can easily transform into free function if Shape is bad place.
You do repeat.. my code. Where is your beloved DD now? All of a sudden
you are checking the shape type with isConfexPolygon(). Makes sense, you
are beginning to get it. Although it is unclear why assert(isOval()); is
not supposed to fire so you have some work to do.
>
>>>>>>> But we need no bloat from that pattern to do neither. These were the
>>>>>>> things we wanted to get rid of.
>>>>>> Care to show us the code for your "non bloated" "one-place" visitor
>>>>>> class for the above 2 problems? (like I did, you can omit the code that
>>>>>> is common for all operations).
>>>>>
>>>>> Yes, sorry for gg breaking all formating.
>>>>>>>
>>>>>>>> In practice this can be
>>>>>>>> anything, examples (non-exhaustive): a enum type discriminator, virtual
>>>>>>>> cast method, (in modern languages), pattern matching.
>>>>>>>>>
>>>>>>>>>> 3. add a single method to B, e.g. acceptVisit that takes [ref to]
>>>>>>>>>> Visitor and calls doOp on it. NOTE: only one such method is needed, no
>>>>>>>>>> matter how many ops you are adding.
>>>>>>>>>
>>>>>>>>> Not enough. Every type processed has to have that copy-paste
>>>>>>>>> acceptVisit method. Otherwise double dispatch does not work.
>>>>>>>>
>>>>>>>> Double dispatch works just fine without copy-pasting. Dispatch #1 (by
>>>>>>>> the operation type) is done by the virtual method doOp. Dispatch #2 (by
>>>>>>>> the object type) is done by using the actual type to call specific
>>>>>>>> methods and/or calling virtual methods, on the object ref.
>>>>>>>
>>>>>>> Of course huge switch or calling virtual method (or now even multiple)
>>>>>>> work without the whole pattern. Read up on "double dispatch".
>>>>>>> It is tricky like I already said.
>>>>>>>
>>>>>> Surely you are the most competent software engineer understanding
>>>>>> multiple dispatch. Care to point out a "huge switch" in either of the
>>>>>> above 2 examples?
>>>>>
>>>>> There are no double dispatch in your code so the whole acceptVisit, doOp
>>>>> add nothing.
>>>>
>>>> Yes it is, see above (or directly Wikipedia article on multiple dispatch
>>>> for the examples on how Double dispatch can be implemented in C++).
>>>>
>>> Do not consider multiple dispatch before figuring what double
>>> dispatch is.
>> not again. I already cited the definition of what DD is. You refer to a
>> particular implementation as ff it were the definition.
>>
> It is multiple dispatch, but do not go there as multiple dispatch you can't
> grasp yet.
very convincing. hominem unius libri timeo
>
>>>
>>>>> These
>>>> Unsure I understand what "these" refers to, will assume my doOp and
>>>> acceptVisit functions
>>>>> were entirely added to do double dispatch
>>>>
>>>> correct. acceptVisit dispatches by op (the 1st parameter of the outer
>>>> structure "visit" operation) and doOp dispatches by object type (in my
>>>> examples, either by using its base type's virtual function only or via a
>>>> combination of the same and `if' statement on virtual cast result)
>>>>
>>> Except on your case it does not. There is just single function that
>>> decides if to draw red or green border.
>> yes, depending on the type. Do you expect colors, styles, font foundries
>> and textures from an example on visitor? You are welcome to provide your
>> own code (the above does not work).
>>
> It was doing exactly same as your posted code.
What is that "it"? One more nonsense and plonk your posts.
>
>>>
>>>>> to add
>>>>> virtual methods to large potentially unrelated data hierarchies without
>>>>> changing any of classes in those hierarchies.
>>>> I assume, by "potentially unrelated" data hierarchy, you mean the new op
>>>> has to behave entirely differently on every type of the hierarchy and
>>>> does not need common data. The issue with this assumption is that the
>>>> very first op you add might relate to the classes like this with but the
>>>> second might benefit from exploiting the type similarities or grouping
>>>> them differently (not even necessary in groups compact in the hierarchy
>>>> graph).
>>>>> What you do is not even
>>>>> use case for visitor pattern.
>>>> Says Wikipedia:
>>>>
>>>> "
>>>> What problems can the Visitor design pattern solve?
>>>>
>>>> It should be possible to define a new operation for (some) classes of an
>>>> object structure without changing the classes.
>>>> "
>>>
>>> "When new operations are needed frequently and the object structure
>>> consists of many unrelated classes, it's inflexible to add new subclasses
>>> each time a new operation is required because "[..] distributing all these
>>> operations across the various node classes leads to a system that's hard
>>> to understand, maintain, and change."
>> You know what you did? You made me dig out my (licensed) CD-ROM with GoF
>> book. Thank you! As I suspected, the Wikipedia quoted GoF in absolutely
>> wrong context. There is nothing about "adding new subclasses" in GoF
>> book, it is all about changing Node hierachy classes. The complete
>> paragraph says:
>>
>> "
>> This diagram shows part of the Node class hierarchy. The problem here is
>> that distributing all these operations across the various node classes
>> leads to a system that's hard to understand, maintain, and change. It
>> will be confusing to have type-checking code mixed with pretty-printing
>> code or flow analysis code. Moreover, adding a new operation usually
>> requires recompiling all of these classes. It would be better if each
>> new operation could be added separately, and the node classes were
>> independent of the operations that apply to them.
>> "
>> (this paragraph is below the diagram depicting a part of Element
>> hierarchy, has nothing to do to Visitor classes or adding "new
>> subclasses". Unsure where this came from).
>>
>> Whoever wrote this page should be .. well, politely reprimanded.
>>
>> The quote above also emphasizes my point that adding compilation
>> dependency to the element type hierarchy shall not be taken lightly.
>>>
>>> Exactly. But if all you need is single little function then write single
>>> little function. Neither single dispatch (virtual functions) nor double
>>> dispatch (matching visitor classes to data hierarchy classes) are
>>> needed for single little function. How did your function being in
>>> visitor make anything easier to understand, maintain and change?
>> How many operations should I have exemplified? I showed 2 and stated the
>> Visitor is applicable when one is to add plenty -- what else do you
>> expect, not providing a single complete counterexample for the same problem.
>>
> The visitor pattern examples are present in GoF book and in Wikipedia
> article. You argue with those, what is the point of me adding one more?
[plonked]
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2023-08-21 06:18 +0200 |
| Message-ID | <ubuoig$1ons4$1@dont-email.me> |
| In reply to | #91309 |
You can better do this with functional programming. If you need
a performant solution you'd pass a function-object of a generic
type. If performance is not your concern or not necessary you'd
pass a function<>-object.
Take this example of a file-listing function for Win32:
void fileList( wchar_t const *path, function<bool ( WIN32_FIND_DATAW
const & )> const &fn, DWORD *pdwErr )
{
using namespace std;
if( pdwErr )
*pdwErr = NO_ERROR;
wstring fullPath = getAbsolutePath( path );
WIN32_FIND_DATAW fd;
HANDLE hFind = FindFirstFileW( fullPath.c_str(), &fd );
if( hFind == INVALID_HANDLE_VALUE )
{
if( GetLastError() == ERROR_FILE_NOT_FOUND )
return;
if( !pdwErr )
throw system_error( (int)GetLastError(), system_category(),
"FindFirstFileW() failed" );
*pdwErr = GetLastError();
return;
}
invoke_on_destruct closeFind(
[&]()
{
BOOL succ = FindClose( hFind );
assert(succ);
} );
wstring dir( extractDirectory( fullPath.c_str(), false ) );
do
if( wcscmp( fd.cFileName, L"." ) != 0 && wcscmp( fd.cFileName, L".." )
!= 0 && !fn( fd ) )
break;
while( FindNextFileW( hFind, &fd ) );
if( GetLastError() != ERROR_NO_MORE_FILES )
{
if( !pdwErr )
throw system_error( (int)GetLastError(), system_category(),
"FindNextFileW() failed" );
*pdwErr = GetLastError();
}
}
[toc] | [prev] | [next] | [standalone]
| From | Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> |
|---|---|
| Date | 2023-08-21 23:28 -0400 |
| Message-ID | <AXVEM.568386$SuUf.28925@fx14.iad> |
| In reply to | #91317 |
Bonita Montero wrote:
> You can better do this
I suspect we are talking about different "this"es. A design pattern is a
recipe for solving a problem of a defined class. What is the class of
problems that your code intends to demonstrate how to solve?
> with functional programming. If you need
> a performant solution you'd pass a function-object of a generic
> type. If performance is not your concern or not necessary you'd
> pass a function<>-object.
> Take this example of a file-listing function for Win32:
>
> void fileList( wchar_t const *path, function<bool ( WIN32_FIND_DATAW
> const & )> const &fn, DWORD *pdwErr )
> {
> using namespace std;
> if( pdwErr )
> *pdwErr = NO_ERROR;
> wstring fullPath = getAbsolutePath( path );
> WIN32_FIND_DATAW fd;
> HANDLE hFind = FindFirstFileW( fullPath.c_str(), &fd );
> if( hFind == INVALID_HANDLE_VALUE )
> {
> if( GetLastError() == ERROR_FILE_NOT_FOUND )
> return;
> if( !pdwErr )
> throw system_error( (int)GetLastError(), system_category(),
> "FindFirstFileW() failed" );
> *pdwErr = GetLastError();
> return;
> }
> invoke_on_destruct closeFind(
> [&]()
> {
> BOOL succ = FindClose( hFind );
> assert(succ);
> } );
> wstring dir( extractDirectory( fullPath.c_str(), false ) );
> do
> if( wcscmp( fd.cFileName, L"." ) != 0 && wcscmp( fd.cFileName,
> L".." ) != 0 && !fn( fd ) )
> break;
> while( FindNextFileW( hFind, &fd ) );
> if( GetLastError() != ERROR_NO_MORE_FILES )
> {
> if( !pdwErr )
> throw system_error( (int)GetLastError(), system_category(),
> "FindNextFileW() failed" );
> *pdwErr = GetLastError();
> }
> }
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2023-08-22 05:47 +0200 |
| Message-ID | <uc1b4g$292ev$1@dont-email.me> |
| In reply to | #91330 |
Am 22.08.2023 um 05:28 schrieb Pavel:
> Bonita Montero wrote:
>> You can better do this
> I suspect we are talking about different "this"es. A design pattern is
> a recipe for solving a problem of a defined class. What is the class of
> problems that your code intends to demonstrate how to solve?
Visitor pattern and inversion of control is basically the same.
>> with functional programming. If you need
>> a performant solution you'd pass a function-object of a generic
>> type. If performance is not your concern or not necessary you'd
>> pass a function<>-object.
>> Take this example of a file-listing function for Win32:
>>
>> void fileList( wchar_t const *path, function<bool ( WIN32_FIND_DATAW
>> const & )> const &fn, DWORD *pdwErr )
>> {
>> using namespace std;
>> if( pdwErr )
>> *pdwErr = NO_ERROR;
>> wstring fullPath = getAbsolutePath( path );
>> WIN32_FIND_DATAW fd;
>> HANDLE hFind = FindFirstFileW( fullPath.c_str(), &fd );
>> if( hFind == INVALID_HANDLE_VALUE )
>> {
>> if( GetLastError() == ERROR_FILE_NOT_FOUND )
>> return;
>> if( !pdwErr )
>> throw system_error( (int)GetLastError(),
>> system_category(), "FindFirstFileW() failed" );
>> *pdwErr = GetLastError();
>> return;
>> }
>> invoke_on_destruct closeFind(
>> [&]()
>> {
>> BOOL succ = FindClose( hFind );
>> assert(succ);
>> } );
>> wstring dir( extractDirectory( fullPath.c_str(), false ) );
>> do
>> if( wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
>> fd.cFileName, L".." ) != 0 && !fn( fd ) )
>> break;
>> while( FindNextFileW( hFind, &fd ) );
>> if( GetLastError() != ERROR_NO_MORE_FILES )
>> {
>> if( !pdwErr )
>> throw system_error( (int)GetLastError(),
>> system_category(), "FindNextFileW() failed" );
>> *pdwErr = GetLastError();
>> }
>> }
>
[toc] | [prev] | [next] | [standalone]
| From | Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> |
|---|---|
| Date | 2023-08-22 00:11 -0400 |
| Message-ID | <LzWEM.65654$VPEa.6307@fx33.iad> |
| In reply to | #91331 |
Bonita Montero wrote:
> Am 22.08.2023 um 05:28 schrieb Pavel:
>> Bonita Montero wrote:
>>> You can better do this
>> I suspect we are talking about different "this"es. A design pattern is
>> a recipe for solving a problem of a defined class. What is the class
>> of problems that your code intends to demonstrate how to solve?
>
> Visitor pattern and inversion of control is basically the same.
What represents the abstract element and concrete elements of different
types derived from the abstract element -- all of which are the
mandatory participants of a Visitor problem -- in your code?
>
>>> with functional programming. If you need
>>> a performant solution you'd pass a function-object of a generic
>>> type. If performance is not your concern or not necessary you'd
>>> pass a function<>-object.
>>> Take this example of a file-listing function for Win32:
>>>
>>> void fileList( wchar_t const *path, function<bool ( WIN32_FIND_DATAW
>>> const & )> const &fn, DWORD *pdwErr )
>>> {
>>> using namespace std;
>>> if( pdwErr )
>>> *pdwErr = NO_ERROR;
>>> wstring fullPath = getAbsolutePath( path );
>>> WIN32_FIND_DATAW fd;
>>> HANDLE hFind = FindFirstFileW( fullPath.c_str(), &fd );
>>> if( hFind == INVALID_HANDLE_VALUE )
>>> {
>>> if( GetLastError() == ERROR_FILE_NOT_FOUND )
>>> return;
>>> if( !pdwErr )
>>> throw system_error( (int)GetLastError(),
>>> system_category(), "FindFirstFileW() failed" );
>>> *pdwErr = GetLastError();
>>> return;
>>> }
>>> invoke_on_destruct closeFind(
>>> [&]()
>>> {
>>> BOOL succ = FindClose( hFind );
>>> assert(succ);
>>> } );
>>> wstring dir( extractDirectory( fullPath.c_str(), false ) );
>>> do
>>> if( wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
>>> fd.cFileName, L".." ) != 0 && !fn( fd ) )
>>> break;
>>> while( FindNextFileW( hFind, &fd ) );
>>> if( GetLastError() != ERROR_NO_MORE_FILES )
>>> {
>>> if( !pdwErr )
>>> throw system_error( (int)GetLastError(),
>>> system_category(), "FindNextFileW() failed" );
>>> *pdwErr = GetLastError();
>>> }
>>> }
>>
>
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2023-08-22 14:30 +0200 |
| Message-ID | <uc29p5$2dgb7$1@dont-email.me> |
| In reply to | #91332 |
Am 22.08.2023 um 06:11 schrieb Pavel:
> Bonita Montero wrote:
>> Am 22.08.2023 um 05:28 schrieb Pavel:
>>> Bonita Montero wrote:
>>>> You can better do this
>>> I suspect we are talking about different "this"es. A design pattern is
>>> a recipe for solving a problem of a defined class. What is the class
>>> of problems that your code intends to demonstrate how to solve?
>>
>> Visitor pattern and inversion of control is basically the same.
> What represents the abstract element and concrete elements of different
> types derived from the abstract element -- all of which are the
> mandatory participants of a Visitor problem -- in your code?
My code and the visitor-pattern have in common that
there's some callback with its own state, i.e. both
have some inversion of control
>>
>>>> with functional programming. If you need
>>>> a performant solution you'd pass a function-object of a generic
>>>> type. If performance is not your concern or not necessary you'd
>>>> pass a function<>-object.
>>>> Take this example of a file-listing function for Win32:
>>>>
>>>> void fileList( wchar_t const *path, function<bool ( WIN32_FIND_DATAW
>>>> const & )> const &fn, DWORD *pdwErr )
>>>> {
>>>> using namespace std;
>>>> if( pdwErr )
>>>> *pdwErr = NO_ERROR;
>>>> wstring fullPath = getAbsolutePath( path );
>>>> WIN32_FIND_DATAW fd;
>>>> HANDLE hFind = FindFirstFileW( fullPath.c_str(), &fd );
>>>> if( hFind == INVALID_HANDLE_VALUE )
>>>> {
>>>> if( GetLastError() == ERROR_FILE_NOT_FOUND )
>>>> return;
>>>> if( !pdwErr )
>>>> throw system_error( (int)GetLastError(),
>>>> system_category(), "FindFirstFileW() failed" );
>>>> *pdwErr = GetLastError();
>>>> return;
>>>> }
>>>> invoke_on_destruct closeFind(
>>>> [&]()
>>>> {
>>>> BOOL succ = FindClose( hFind );
>>>> assert(succ);
>>>> } );
>>>> wstring dir( extractDirectory( fullPath.c_str(), false ) );
>>>> do
>>>> if( wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
>>>> fd.cFileName, L".." ) != 0 && !fn( fd ) )
>>>> break;
>>>> while( FindNextFileW( hFind, &fd ) );
>>>> if( GetLastError() != ERROR_NO_MORE_FILES )
>>>> {
>>>> if( !pdwErr )
>>>> throw system_error( (int)GetLastError(),
>>>> system_category(), "FindNextFileW() failed" );
>>>> *pdwErr = GetLastError();
>>>> }
>>>> }
>>>
>>
>
[toc] | [prev] | [next] | [standalone]
| From | Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> |
|---|---|
| Date | 2023-08-24 01:56 -0400 |
| Message-ID | <niCFM.897824$GMN3.379171@fx16.iad> |
| In reply to | #91342 |
Bonita Montero wrote:
> Am 22.08.2023 um 06:11 schrieb Pavel:
>> Bonita Montero wrote:
>>> Am 22.08.2023 um 05:28 schrieb Pavel:
>>>> Bonita Montero wrote:
>>>>> You can better do this
>>>> I suspect we are talking about different "this"es. A design pattern is
>>>> a recipe for solving a problem of a defined class. What is the class
>>>> of problems that your code intends to demonstrate how to solve?
>>>
>>> Visitor pattern and inversion of control is basically the same.
>> What represents the abstract element and concrete elements of
>> different types derived from the abstract element -- all of which are
>> the mandatory participants of a Visitor problem -- in your code?
>
> My code and the visitor-pattern have in common that
> there's some callback with its own state, i.e. both
> have some inversion of control
IoC is very generic so a Visitor-pattern *framework* library can likely
be characterized as IoC; but then in your code the "template method" of
the framework (fileList) does not work on an object of type from some
non-trivial type hierarchy which is where the Visitor can be applied. Of
course you can argue that the type WIN32_FIND_DATAW represents a
degenerative hierarchy of one type but Visitor is not useful for working
on degenerative hierarchies. Your code is some custom framework to
iterate thru files; could be even useful but one cannot "do better" in
solving a problem by given class by solving some random problem of
entirely different class.
>
>
>>>
>>>>> with functional programming. If you need
>>>>> a performant solution you'd pass a function-object of a generic
>>>>> type. If performance is not your concern or not necessary you'd
>>>>> pass a function<>-object.
>>>>> Take this example of a file-listing function for Win32:
>>>>>
>>>>> void fileList( wchar_t const *path, function<bool (
>>>>> WIN32_FIND_DATAW const & )> const &fn, DWORD *pdwErr )
>>>>> {
>>>>> using namespace std;
>>>>> if( pdwErr )
>>>>> *pdwErr = NO_ERROR;
>>>>> wstring fullPath = getAbsolutePath( path );
>>>>> WIN32_FIND_DATAW fd;
>>>>> HANDLE hFind = FindFirstFileW( fullPath.c_str(), &fd );
>>>>> if( hFind == INVALID_HANDLE_VALUE )
>>>>> {
>>>>> if( GetLastError() == ERROR_FILE_NOT_FOUND )
>>>>> return;
>>>>> if( !pdwErr )
>>>>> throw system_error( (int)GetLastError(),
>>>>> system_category(), "FindFirstFileW() failed" );
>>>>> *pdwErr = GetLastError();
>>>>> return;
>>>>> }
>>>>> invoke_on_destruct closeFind(
>>>>> [&]()
>>>>> {
>>>>> BOOL succ = FindClose( hFind );
>>>>> assert(succ);
>>>>> } );
>>>>> wstring dir( extractDirectory( fullPath.c_str(), false ) );
>>>>> do
>>>>> if( wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
>>>>> fd.cFileName, L".." ) != 0 && !fn( fd ) )
>>>>> break;
>>>>> while( FindNextFileW( hFind, &fd ) );
>>>>> if( GetLastError() != ERROR_NO_MORE_FILES )
>>>>> {
>>>>> if( !pdwErr )
>>>>> throw system_error( (int)GetLastError(),
>>>>> system_category(), "FindNextFileW() failed" );
>>>>> *pdwErr = GetLastError();
>>>>> }
>>>>> }
>>>>
>>>
>>
>
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2023-08-24 09:15 +0200 |
| Message-ID | <uc703b$3cer6$1@dont-email.me> |
| In reply to | #91384 |
Am 24.08.2023 um 07:56 schrieb Pavel: > IoC is very generic so a Visitor-pattern *framework* library can likely > be characterized as IoC; but then in your code the "template method" of > the framework (fileList) does not work on an object of type from some > non-trivial type hierarchy which is where the Visitor can be applied. > Of course you can argue that the type WIN32_FIND_DATAW represents a > degenerative hierarchy of one type but Visitor is not useful for > working on degenerative hierarchies. Your code is some custom > framework to iterate thru files; could be even useful but one > cannot "do better" in solving a problem by given class by solving > some random problem of entirely different class. The visitor pattern is a IoC-pattern, but not every IoC-pattern is a visitor pattern. I like functional programming and I think that the visitor pattern can be enhanced in this way, making it more IoC-ish.
[toc] | [prev] | [next] | [standalone]
| From | Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> |
|---|---|
| Date | 2023-08-24 20:02 -0400 |
| Message-ID | <gcSFM.312013$Fgta.22420@fx10.iad> |
| In reply to | #91385 |
Bonita Montero wrote: > Am 24.08.2023 um 07:56 schrieb Pavel: > >> IoC is very generic so a Visitor-pattern *framework* library can >> likely be characterized as IoC; but then in your code the "template >> method" of the framework (fileList) does not work on an object of type >> from some non-trivial type hierarchy which is where the Visitor can be >> applied. >> Of course you can argue that the type WIN32_FIND_DATAW represents a >> degenerative hierarchy of one type but Visitor is not useful for >> working on degenerative hierarchies. Your code is some custom >> framework to iterate thru files; could be even useful but one >> cannot "do better" in solving a problem by given class by solving >> some random problem of entirely different class. > > The visitor pattern is a IoC-pattern, but not every IoC-pattern is a > visitor pattern. I like functional programming and I think that the > visitor pattern can be enhanced in this way, making it more IoC-ish. > You threw an interesting tangential idea (that IoC-ness is a numerical property of a pattern) that I am willing to explore but let's put the main issue to bed first: Are you saying that, for whatever cause (like making Visitor more IoC-ish) you are willing to drastically narrow the class of problems that the pattern commonly-known as Visitor is believed to solve? Specifically, can there exist an IoC framework that you would be ready to characterize as correctly implementing Visitor pattern if that framework could visit elements of only one type?
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2023-08-25 05:13 +0200 |
| Message-ID | <uc9696$3rp9i$1@dont-email.me> |
| In reply to | #91400 |
Am 25.08.2023 um 02:02 schrieb Pavel: > Are you saying that, for whatever cause (like making Visitor more > IoC-ish) you are willing to drastically narrow the class of problems > that the pattern commonly-known as Visitor is believed to solve? I suggested a way to make the vistitor pattern implementable with much less code and more maintainable. The visitor neither need an addtional base-class and implementation class.
[toc] | [prev] | [next] | [standalone]
| From | Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> |
|---|---|
| Date | 2023-08-26 01:05 -0400 |
| Message-ID | <2KfGM.827216$TPw2.761320@fx17.iad> |
| In reply to | #91402 |
Bonita Montero wrote: > Am 25.08.2023 um 02:02 schrieb Pavel: > >> Are you saying that, for whatever cause (like making Visitor more >> IoC-ish) you are willing to drastically narrow the class of problems >> that the pattern commonly-known as Visitor is believed to solve? > > I suggested a way to make the vistitor pattern implementable with much > less code and more maintainable. The visitor neither need an addtional > base-class and implementation class. Visitor does not need anything, it simply solves a particular class of problems that happens to include a structure of objects of multiple types. Your visitor implementation is invalid if it cannot solve a valid visitor problem.
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2023-08-26 09:24 +0200 |
| Message-ID | <ucc9bg$g167$1@dont-email.me> |
| In reply to | #91423 |
Am 26.08.2023 um 07:05 schrieb Pavel: > Visitor does not need anything, it simply solves a particular class > of problems that happens to include a structure of objects of multiple > types. Your visitor implementation is invalid if it cannot solve a valid > visitor problem. Either I didn't describe the difference to what you imagine correctly or you just didn't understand me. In the Visitor pattern, the Visitor needs a base class with a virtual method to derive from, and the derived object is passed by reference to the Visitor function of the objects being visited. However, the derived and the base class are superfluous with functional programming. Since C++11 you can simply request a function<> object and conveniently you just pass a lambda that when called, a temporary function<> object is created that represents the interface to the object that can be visited.
[toc] | [prev] | [next] | [standalone]
| From | Muttley@dastardlyhq.com |
|---|---|
| Date | 2023-08-26 08:01 +0000 |
| Message-ID | <uccbgu$gd6k$1@dont-email.me> |
| In reply to | #91425 |
On Sat, 26 Aug 2023 09:24:35 +0200 Bonita Montero <Bonita.Montero@gmail.com> wrote: >Am 26.08.2023 um 07:05 schrieb Pavel: > >> Visitor does not need anything, it simply solves a particular class >> of problems that happens to include a structure of objects of multiple >> types. Your visitor implementation is invalid if it cannot solve a valid >> visitor problem. > >Either I didn't describe the difference to what you imagine correctly >or you just didn't understand me. In the Visitor pattern, the Visitor >needs a base class with a virtual method to derive from, and the derived >object is passed by reference to the Visitor function of the objects >being visited. However, the derived and the base class are superfluous >with functional programming. Since C++11 you can simply request a >function<> object and conveniently you just pass a lambda that when >called, a temporary function<> object is created that represents the >interface to the object that can be visited. This sort of programming is the equivalent of touchscreens in cars - looks great and impresses the "Ooo shiny!" crowd but adds little to the main functionality of the car and just adds needless complexity with more scope for failure.
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2023-08-26 11:29 +0200 |
| Message-ID | <uccglr$h6lp$1@dont-email.me> |
| In reply to | #91426 |
Am 26.08.2023 um 10:01 schrieb Muttley@dastardlyhq.com: > This sort of programming is the equivalent of touchscreens in cars - looks > great and impresses the "Ooo shiny!" crowd but adds little to the main > functionality of the car and just adds needless complexity with more scope > for failure. My solution is less complexity because you don't need to declare a base class and a derived class. Just declare the parameter as a function object and pass a lambda which is automatically crated. That's for sure at maximally the tenth of the code. You can have functional programming in C++ since 2011. That's a long time you didn't notice its potential.
[toc] | [prev] | [next] | [standalone]
| From | Muttley@dastardlyhq.com |
|---|---|
| Date | 2023-08-26 15:00 +0000 |
| Message-ID | <ucd42s$kc9j$1@dont-email.me> |
| In reply to | #91430 |
On Sat, 26 Aug 2023 11:29:34 +0200 Bonita Montero <Bonita.Montero@gmail.com> wrote: >Am 26.08.2023 um 10:01 schrieb Muttley@dastardlyhq.com: > >> This sort of programming is the equivalent of touchscreens in cars - looks >> great and impresses the "Ooo shiny!" crowd but adds little to the main >> functionality of the car and just adds needless complexity with more scope >> for failure. > >My solution is less complexity because you don't need to declare >a base class and a derived class. Just declare the parameter as >a function object and pass a lambda which is automatically crated. >That's for sure at maximally the tenth of the code. >You can have functional programming in C++ since 2011. That's a >long time you didn't notice its potential. People who say you can have functional programming in C++ don't understand functional programming. Its more than just no cost recursion , its also polymorphism by *VALUE*. That is not and probably never will be possible in C++.
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2023-08-26 17:08 +0200 |
| Message-ID | <ucd4gs$ke1h$1@dont-email.me> |
| In reply to | #91439 |
Am 26.08.2023 um 17:00 schrieb Muttley@dastardlyhq.com: > People who say you can have functional programming in C++ don't understand > functional programming. Its more than just no cost recursion , its also > polymorphism by *VALUE*. ... That's what I described and you didn't notice it. A function<>-object is always polymorphic. I bet you never used function<>-objects in C++.
[toc] | [prev] | [next] | [standalone]
| From | Muttley@dastardlyhq.com |
|---|---|
| Date | 2023-08-26 15:15 +0000 |
| Message-ID | <ucd4ug$khfd$1@dont-email.me> |
| In reply to | #91440 |
On Sat, 26 Aug 2023 17:08:15 +0200 Bonita Montero <Bonita.Montero@gmail.com> wrote: >Am 26.08.2023 um 17:00 schrieb Muttley@dastardlyhq.com: > >> People who say you can have functional programming in C++ don't understand >> functional programming. Its more than just no cost recursion , its also >> polymorphism by *VALUE*. ... > >That's what I described and you didn't notice it. A function<>-object >is always polymorphic. I bet you never used function<>-objects in C++. Given they're necessary for the STL what do you think? Which part of "by value" is confusing you? But if you insist on claiming its possible show us how you'd do polymorphism for a function with a single int param for say the values 0, 2, 3, 10 -> 100 and a default.
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2023-08-26 17:24 +0200 |
| Message-ID | <ucd5ee$kjhn$1@dont-email.me> |
| In reply to | #91441 |
Am 26.08.2023 um 17:15 schrieb Muttley@dastardlyhq.com: > On Sat, 26 Aug 2023 17:08:15 +0200 > Bonita Montero <Bonita.Montero@gmail.com> wrote: >> Am 26.08.2023 um 17:00 schrieb Muttley@dastardlyhq.com: >> >>> People who say you can have functional programming in C++ don't understand >>> functional programming. Its more than just no cost recursion , its also >>> polymorphism by *VALUE*. ... >> >> That's what I described and you didn't notice it. A function<>-object >> is always polymorphic. I bet you never used function<>-objects in C++. > > Given they're necessary for the STL what do you think? > > Which part of "by value" is confusing you? But if you insist on claiming its > possible show us how you'd do polymorphism for a function with a single int > param for say the values 0, 2, 3, 10 -> 100 and a default. You can't describe what you want and you don't understand function<> -objects. Stick with C.
[toc] | [prev] | [next] | [standalone]
| From | Muttley@dastardlyhq.com |
|---|---|
| Date | 2023-08-26 15:36 +0000 |
| Message-ID | <ucd665$ko3j$1@dont-email.me> |
| In reply to | #91442 |
On Sat, 26 Aug 2023 17:24:00 +0200 Bonita Montero <Bonita.Montero@gmail.com> wrote: >Am 26.08.2023 um 17:15 schrieb Muttley@dastardlyhq.com: >> On Sat, 26 Aug 2023 17:08:15 +0200 >> Bonita Montero <Bonita.Montero@gmail.com> wrote: >>> Am 26.08.2023 um 17:00 schrieb Muttley@dastardlyhq.com: >>> >>>> People who say you can have functional programming in C++ don't understand >>>> functional programming. Its more than just no cost recursion , its also >>>> polymorphism by *VALUE*. ... >>> >>> That's what I described and you didn't notice it. A function<>-object >>> is always polymorphic. I bet you never used function<>-objects in C++. >> >> Given they're necessary for the STL what do you think? >> >> Which part of "by value" is confusing you? But if you insist on claiming its >> possible show us how you'd do polymorphism for a function with a single int >> param for say the values 0, 2, 3, 10 -> 100 and a default. > >You can't describe what you want and you don't understand function<> >-objects. Stick with C. Translation: I can't do it because its not possible but don't have the balls to admit I was wrong. Go back to writing your next piece of lame shit code to post on here in order to show off.
[toc] | [prev] | [next] | [standalone]
Page 2 of 3 — ← Prev page 1 [2] 3 Next page →
Back to top | Article view | comp.lang.c++
csiph-web