Re: Lahman, how ya doing?
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Fri, 13 May 2005 19:09:48 GMT
Responding to Hansen...
I think I understand that in the context of the real controller. The high priority stuff gets done first. Then control passes to the low priority queue. And I suppose that as each low priority event is processed, the
time is checked, and if a tick has passed, the low priority queue is left as it is and another run is done through the high priority queue.
(Otherwise, if you complete the low priority queue before returning to the high priority one, you may as well have a single queue.)
But what I don't get is how to translate that to a simulation if stuffing a
timer.add_event(&timer, 420/60, kDelayEvent, kLowPriority);
isn't quite what you had in mind. In a simulation, you have to tick the clock some how.
The difference is that one does not delay the event; it goes out on schedule in my solution, just like the hardware clock in the real controller. One delays the results selectively so that they emulate the delays in the real controller that led to the skipped (deferred) processing in a tick. IOW, it is the processing in response to the tick event that is prioritized and delayed.
So, uh... it's like a clear queue command? Or it's like a command to a specific object, "Whatever your new result is, don't tell anyone for two ticks"?
Not exactly. The results get done when they are done. That may not be on the tick that they were scheduled to be done because of the competition for the CPU. That is, everybody starts on schedule but not everyone finishes on schedule. When that skip happens, then the software needs to deal with it.
Aside from faithfully emulating the "witching" ticks in the real controller (as I understood them), this keeps Timer's responsibilities simple. It just understands the schedule. One implements the delay emulation with a second event queue in a different thread with a different priority. That isolates those simulation concerns in the design so that neither Timer nor the feedback processing need to know anything about skipped ticks.
Well, in the real controller, when the processing is delayed, it's resumed again as soon as possible and with the correct time. E.g. if something is to occur every 7 ticks the trigger times might look like 7, 14, 22, 28..., and on that third iteration the call to the appropriate function would pass 8 ticks rather than 7.
I should have said, "the real controller, if it has been designed by an R-T/E person." B-)
What you seem to be describing in the same scenario is 7, 14, 28..., and that third iteration would be processed with a time parameter of 7 ticks rather than 14.
What I am saying is that I wouldn't pass a time parameter at all. I would try to compensate for the skips using something intrinsic to the processing. That is, one addresses the root effect of the skip.
For example, suppose the thing that will skip every 420 ticks is a temperature sample scheduled for each single tick. The statistic computation on the 60th tick will then be computing on 59 samples rather than 60 whenever a skip occurred and that would affect the standard deviation. So the easy way to deal with that is for Thermometer to record an actual sample count which is incremented when a sample is taken, regardless of what tick it is in. Then the standard deviation is correctly computed and the sample count is cleared.
The reason I prefer that is because the compensation is mapped into the semantics of the Thermometer processing. Thermometer doesn't need to understand why it has only 59 samples rather than 60. It just needs to do the right thing with 59 samples. Thermometer is providing a count that is equivalent to the tick count but it is in terms of Thermometer's semantics, not Timer's.
My point in this context was that if one needs to emulate those skipped
ticks in the simulation, the easy way to do it is the same way the real
controller does it by putting the processing in a lower level thread and tinkering with the priorities. (It's easy because you just need a
second instance of the event queue manager and an adjustment to Timer to send the 1 and 7 tick events to one queue instance while pushing the 60 and 420 tick events to the second queue instance.) But that is only
easy if you already have the true event-based infrastructure in the
simulation.
In contrast, if your simulation implementation is synchronous, then you
have to worry about other things like call graph loops that get back to
Timer, which might suck everything into one thread. Since you are doing simulation ticks, that is actually fairly likely because somebody at the tail end of the processing has to <synchronously> tell Timer it is time
for another tick. IOw, you may have foot-shooting opportunities
because
the threads don't conveniently terminate like an empty event queue
would.
Speaking of telling Timer it is time for another tick, I have to say
I'm
not sure I get that, either. Somebody at the tail end of the
processing
has to tell Timer it is time for another tick, but nobody should know
that
Timer exists? (And actually, the way I've imagined the design, nobody should really know they're at the tail end, either.)
Nobody is supposed to know that you compute temperatures rather than polling them from the hardware either. However, there have to be join points between the simulation infrastructure and the feedback logic processing. So Thermometer is going to have to access the temperature values as an attribute rather than from a register as in the real controller. That is part of the simulation infrastructure design.
Another join point is invoking Timer.tick. If you don't scale the system clock for the simulation, then you need some more direct mechanism for letting Timer know it is time for the next tick _in the simulation_ and calling Timer.tick will do that. Without scaling that will have to be done from the point where you are sure the <necessary> tick processing is done. (In the real controller it would be invoked
from some polling loop for hardware interrupts or OS events.)
However, I only suggested that because Timer.tick() was already in place in the discussion. The more elegant way is to make Timer itself have a state machine and...
I can see it in terms of Timer telling Timer it is time for another tick. E.g. after all objects have been declared, their relationships defined,
their order of operations given to timer, the last event to add is
timer.add_event(&timer, 1, kTimerTickEvent);
Then when control goes to the queue manager to start popping events, the last one brings us back to Timer.
Very good. This event-based processing stuff isn't that tough after all, is it? B-)
Alas, one problem with this solution is that the Timer state machine is not very interesting:
[Events Generated] <--------+ | | | Timer Tick Event | +-------------------+
From a purist viewpoint object state machines with a single state arefrowned upon because they don't capture any life cycle constraints in their transitions. Often the decision between abstracting the problem space in terms of knowledge vs. behavior responsibilities is tough to make. One criterion for that distinction is on the basis of whether there are constraints on when the object can perform its responsibilities. So if there are no constraints or only one responsibility, one usually opts for a synchronous knowledge accessor. In this case Timer.tick can be regarded as an accessor of the eventList data.
I don't know. How do you stop? It seems Timer would have two states, run and stop. When it counts through the specified number of iterations it should do something like put a "Go to the next part of the program" event in the queue.
OK, but that Stopped state is pretty trivial and doesn't really capture any sequencing constraints for the simulation processing, which is why it exists in the first place. The real issue is whether the state machine captures sequencing constraints that are important to the problem in hand.
We've been focusing on going through one cycle of the simulation. But in a larger problem we could complete a cycle, fit a curve to the data, make a decision about the next set of parameters to try (e.g. for the temperature controller), and do it again. Or graph the data and let the user click little buttons to change parameters. And continue until the simulated behavior is close to some desired behavior, like a critically damped response. Then Timer (and other objects) would be repeatedly reset, started, and stopped.
That's fine, but it an additional suite of <important> requirements for Timer. I was addressing just the basic simulation issues. But you're right that this objection isn't a very strong. It's a review checklist sort of thing. You see a state machine with one state and you have to ask for a justification. If you get a satisfactory one, move on.
Another problem from a purist viewpoint is that the Timer Tick Event is self-directed (i.e., Timer generates it to itself). That will also tend to get the reviewers to haul out crucifixes and garlic cloves. There is one camp of state machine design that argues one should /never/ use self-directed events because they violate FSA theory. I don't subscribe to that because object state machines are not pure FSAs since the transition alphabet includes state variables (attributes). Sometimes a self-directed event is the simplest and cleanest way to synchronize state variables with state machine states. Nonetheless they should be used rarely because they often represent a procedural mapping onto OO development.
I have read a warning against trying too hard to create a theoretically ideal program at the expense of a simpler implementation. I think it was in that UML/OOD book I have. The author has seen much time spent and complication created by programmers trying harder than they should to do things the "proper" way.
But I suppose, like a great author breaking grammar rules, you should first know what the rules are.
Exactly. Alas, one of the problems I have with a lot of methodology books is that they present the methodology in Sermon On The Mount mode and don't provide a lot of explanation about /why/ the methodology wants things done a certain way. That makes it tough to use proper judgment when an exception is really needed.
A classic example is Normal Form in UML Class Diagrams. Every OOA/D book provides a bunch of guidelines and techniques for normalizing a Class Diagram. But usually they will only mention NF in passing or in an appendix -- if they mention it at all! I guess they don't want to confuse their audience with formal set theory definitions. As a result, though, a lot of experienced OO developers today don't even realize that when they apply those guidelines they are normalizing the model. If they don't understand how object identity and NF are related, how are they supposed to make close decisions?
[You have probably noticed that in my posts I talk a lot more about esoteric things like peer-to-peer collaboration and logical indivisibility than most OOA/D books do. That's because I want to emphasize the underlying First Principles that make things like abstraction and polymorphism useful.]
What is FSA theory?
Finite State Automata -- the basis for finite state machines (FSM) that actual do something useful outside academia.
Knowledge and behavior responsibilities are described using different
object properties. Knowledge is captured in attribute properties, which
are Abstract Data Types (ADT) that are essentially state variables. But
because of the ADT nature the attribute may have arbitrarily complex
structure. (I have seen data structures with ~10**8 individual data
elements abstracted as a single scalar class attribute.)
I just have to interrupt and say that's a lot of data elements!
Lots of bitmaps, often with odd-bit fields. B-)
I spent years doing automated test equipment for digital electronics. Our testers were pattern-based, which means that the test was divided into time slices and stimuli/response were specified for each time slice on each tester pin. In each test there would be up 6K test pins sending or receiving or both. Drive states were just logic {0,1} but detect states could be any of 18 possible states (e.g., Ring Ending High). Within a pattern each pin was specified with drive and detect windows, defined by relative offsets from the start of the pattern. Each test pin also had to have voltage levels, bias current, return state, and a flock of other characteristics specified for the pattern. Typically a single digital test burst would have 128K patterns in it. An entire digital test would have have multiple test bursts. That's a whole lot of data to load into the register bits. [Getting it into the hardware was challenging because the hardware ran an order of magnitude faster than the embedded P6 running the test program. When the tester costs megabucks the customer wants a lot of throughput. Hence some of my devotion to cycle counting.]
At a low level of abstraction where one literally loaded the hardware RAM all that stuff was modeled with classes like Test, Burst, Pattern, Pin, Pin State, etc. However, in the higher level subsystems where one dealt with testing at the level of load/run/fetch/diagnose, the content of a digital test was not of interest. So in that subsystem we had a Test entity, one of whose attributes was burstData. In that subsystem the burstData attribute was a scalar "value" that was just passed through to subsystems that understood how to parse it into the humongous data structure it actually was. (IOW, it was just a kind of handle to the data.) In intermediate subsystems we might have a Burst class with an attribute like patternData that was also a scalar ADT because, at that subsystem's level of abstraction, we didn't care about the individual patterns but we did care about loading one burst at a time and, perhaps, rerunning it for diagnosis.
An ADT may also have rather complex operations associated with it.
Behaviors are abstracted as method properties that perform some activity to satisfy the responsibility. The activity involves some set of rules, policies, practices, laws, etc. that exist in some problem space and are necessary to solve the problem in hand. Essentially behaviors execute problem space rules in the specific problem context.
Alas, the OOPLs tend to confuse these differences in several ways. Since the method signature is essentially the message, there is no separation of message and method. That's because the OOPLs are all still 3GLs and they employ procedural block structuring, stack-based flow of control, and procedural message passing. But in OOA/D one constructs designs as if messages were completely decoupled from the methods that respond to them. (In fact, in UML the interface to a class, which describes the messages class members will accept, is a different model element from the class definition where the responsibilities are defined.)
Are there any candidate 4GLs?
Now that UML has defined a semantic meta model for abstract action languages, UML itelf is a bona fide 4GL. Actually, UML has just standardized one. The technology for 4GL execution models has been around since the '80s and there are several tools on the market that support such 4GLs. (Pathfinder is one such vendor.) Once OMG standardized UML and the action semantics, all the existing vendors dropped their proprietary notations and jumped on the UML bandwagon.
It took nearly two decades to evolve decent optimization because one faces a much larger scope than a 3GL compiler does. But the technology has been stable since the late '90s. A couple of years ago Ivar Jacobson gave a conference keynote address where he predicted that in a decade writing 3GL code would be as rare as writing Assembly today. I don't think it will happen quite that quickly, but I am as sure as he is that it is inevitable.
[All of the traditional translation vendors that used 4GLs for a decade or more have been bought up in the past few years by people like IBM, Mentor, CA, etc. as they try to establish position in the market. Pathfinder and Telelogics I think may be the only ones left as independent vendors. However, with the MDA initiative new vendors are popping up on a monthly basis.]
Lots of niche precedents have been around since the '80s. RAD IDEs today for the CRUD/USER pipeline market are very close to 4GLs because the VB used is so specialized and much of the infrastructure is "canned". One also thinks about the application almost exclusively at screen <--> RDB level of abstraction, so one doesn't see a lot of that icky 3GL stuff. The ATLAS test requirements specification language (IEEE 716) has been around forever and very complex test programs are routinely generated from it completely automatically.
The OOPLs also make no distinction between operations that modify knowledge and operations that capture behavior responsibilities. Thus an attribute getter looks exactly like a method to execute a behavior.
"Accessor function"?
I'm not sure if you have something specific in mind. Traditionally attribute getters and setters have been called "knowledge accessors" and the getters are technically functions at the 3GL level.
<snip>
In this case...
The only one I have defined right now is the ChartRecorder, and I stick a pointer into it when it's initialized,
ChartRecorder chart(&heater);
That's placed in a private member which I've imaginatively called
Object * obj;
No problem. This just instantiates a relationship for subsequent collaborations.
And when an averaging event is sent, that is where it gets its data.
void ChartRecorder::evolve(float dt) { num++; float datum = obj->get_output();
Right. The context of collaboration is confident that it is getting to exactly the right object. IOW, setting the pointer to the right object is a problem that the collaboration context doesn't need to worry about.
I actually wondered if I should generate an event, "send output to chart". But then I thought that's silly because it's not really an action, it's just reading an already-computed parameter. It could have been
float datum = obj->output;
reading directly from the variable, except they say that's poor design. I suppose that's synchronous-- whatever is in there is immediately useable to anyone that needs it.
It's regarded as poor design because of the OOPLs. In OOA/D notation there are no getters and setters; one accesses attributes directly. That's because in OOA/D they are ADTs (abstract data types). But in an OOPL knowledge is mapped directly to memory storage. As soon as you declare datum to be float you are defining an implementation that is inextricably tied to datum at the 3GL level. So anyone who accesses ChartRecorder.datum in the traditional 3GL notation will have to be recompiled if that implementation changes.
It gets worse if one changes the semantics. For example, suppose one has Employee with a Salary attribute. The original semantics of Salary is the fully burdened salary. But suppose during maintenance one decides one needs two attributes -- baseSalary and burdenRate. Now the fully burdened salary is computed as baseSalary * burdenRate. But what about all those clients who invoked Employee.salary when they wanted the fully burdened rate? Now they are broken and one has to make a change to its implementation. With a getSalary getter that is no problem as one just changes to getter to do the computation, making the change transparent to the client.
<historical perspective>
This is something the early OOPLs did not get right. The direct access in the form of <object>.<attribute> notation could not be implemented in the caller without knowing the owner's implementation because that implementation was part of the declaration in 3GL land. Developers quickly realized that was Not Good and adopted the practice of making all knowledge private and only using getter/setter methods to access them.
Unfortunately, like many kludges, that had its own problem. Now one couldn't directly distinguish between public knowledge responsibilities and private ones unless one looked at the accessor methods and, even then, one had to rely one naming conventions (getXxxx) to avoid looking at the implementation of the accessor.
Some modern languages have addressed the problem explicitly in that they have a notion of 'default accessor'. If the developer does not provide an implementation for the default accessor, the <object>.<attribute> access will default to the traditional direct invocation. However, if the developer does provide an implementation, then the accessor call will be lexically substituted for the access syntax. That allows attributes to be defined publicly while still shielding the clients from the implementation. (Alas, if one changes one's mind an provides an implementation the world will have to be recompiled, but only that one time.)
</historical perspective>
sum += datum; sum2 += datum*datum; tt += dt; }
What I'm talking about is making a fairly minor change, e.g. so that the chart is declared as
ChartRecorder chart(&heater, &HeatInput::get_output);
And then the member function can be chosen as something other than get_output().
This is potentially stickier. To think about this note that the obj->get_output() expression in an OOA/D sense is sending a message identified as "get_output" to the object whose address is provided in "obj". In principle that is the same thing that Timer.add_event did for event messages in eventList; it defined the address and the event identifier.
That's fine so long as get_output is a synchronous service, as <presumably> in this case, or one is only identifying the message to send, as in the Timer.add_event case. In both cases one is just parameterizing the identification of the message. However, at the OOPL level that is not clear because the invocation of get_output is synchronous and the OOPL makes no distinction between behavior and knowledge accessors.
There are three possibilities for what get_output is:
(A) It is an event identifier, as in the Timer.add_event. In that case the event infrastructure would not allow a return value so it is a pure message definition. That is, evolve just generates it with no concern about what happens as a result. All is well.
(B) It is a knowledge getter, as this case seems to be. It's fair to get a value return because knowledge is accessed synchronously. More important, the value returned is defined by the public responsibilities of the "obj" object (i.e., what values it knows). That is, all one needs to know is what the knowledge responsibility is, not how it is implemented. In addition, we can be certain due the methodological constraints discussed above, that whatever get_output does to produce that value, it does not involve any unique rules for the problem context. IOW, it just extracts data in a manner that is realized (i.e., invariant with the problem context). All is still well.
(C) It is a behavior responsibility being accessed synchronously. By the definition in the discussion above, that behavior enforces problem space rules that are unique to the problem in hand. Since such rules affect the solution we cannot ignore them in the specification of evolve. IOW, the returned value is not just a value the object is responsible for knowing; it is produced by executing part of the problem solution. That makes evolve dependent on what those rules are. That's not so good.
So what's my point? One never gets into the (C) situation if one approaches the OOA/D properly by making the distinctions above, using the right communication models, thinking in terms of announcement messages, and following the methodological rules for forming things like synchronous services. However, one may very well get into a situation where one needs to parametrically define messages.
I would have to say get_output() is a (B)-- it's a single-line accessor function {return output;}.
That's what I thought. My dissertation was just to emphasize the differences.
That may lead to OOP code such as your example. From the code fragment itself one cannot tell if the design is well-formed unless one relies on things like naming conventions or external documentation. To be sure one would have to look into the guts of the implementation of get_output and whoever is passing it to evolve. But if one did the OOA/D properly, one doesn't need to look; it will Just Be Right.
Bottom line: however stodgy, verbose, unintuitive, etc. the OOA/D approach may be, it will make the OOPL code safe for maintainers.
All this because you asked about attributes vs. behaviors! B-)
And I haven't even finished my beer.
Slacker. Back in my misspent youth I would have finished the whole six-pack by this point.
************* There is nothing wrong with me that could not be cured by a capful of Drano.
H. S. Lahman hsl@xxxxxxxxxxxxxxxxx Pathfinder Solutions -- Put MDA to Work http://www.pathfindermda.com blog: http://pathfinderpeople.blogs.com/hslahman (888)OOA-PATH
.
- Follow-Ups:
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- References:
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- Prev by Date: Re: A Java Brainteaser - a Static Factory method Narrative
- Next by Date: Re: Fat Interface vs. Type Switch
- Previous by thread: Re: Lahman, how ya doing?
- Next by thread: Re: Lahman, how ya doing?
- Index(es):