Archived

This forum has been archived. Please start a new discussion on GitHub.

Documentation

Hello,

I'm a complete newbie to Ice and am having trouble making sense of Chapter 4 (The Slice Language) of Distributed Programming with Ice. When it comes to reading documentation, I've long since determined that "my brain doesn't work the way most of yours do;" as a result, I get hung up on literally dozens of "sticking points" in whatever I read -- ambiguities, seeming self-contradictions, etc. that most of you wouldn't even notice.

That makes reading / learning slow, painful, and infuriating; and unfortunately, I am also in somewhat of a hurry. By the time I am moved to "seek help," I'm too angry to do it here lest I offend, upset, and/or acquire a negative reputation, and yet I tend to need answers right away. Yes sir, it's a miserable experience all the way around.

Therefore -- can someone direct me to an appropriate place to discuss this material without offending a whole community? Perhaps one-on-one "offline" discussion with someone who a) knows this material very well, b) can explain it in minute and subtle detail, and c) has a huge amount of patience for someone who nitpicks everything to death?

Thanks in advance,

Chris the extremely candid

Comments

  • xdm
    xdm La Coruña, Spain
    Ice Programming book

    I don´t agree with you, I think that Ice Programming book is a good book an it´s easy to read and undertand. But i think that a base knowledge of object oriented programming is needed. in the other hand The forum is a well pace for concrete problems and there are a lot of threads in this forum with the most commom problems solved. Ice examples are very extensive a the newsleter explain other iteresting topics realted with Ice and Distributed systems.

    I using ice from version 1.0 and i think that the learning curve is very plain
    What you don´t understand in chapter 4?

    Wellcome to Ice
  • ChrisC wrote:
    Therefore -- can someone direct me to an appropriate place to discuss this material without offending a whole community? Perhaps one-on-one "offline" discussion with someone who a) knows this material very well, b) can explain it in minute and subtle detail, and c) has a huge amount of patience for someone who nitpicks everything to death?

    Right here is a perfect place to air any questions and criticisms. We are always keen to get feedback on the doc, so I wouldn't be shy about discussing things here.

    Cheers,

    Michi.
  • michi wrote:
    Right here is a perfect place to air any questions and criticisms. We are always keen to get feedback on the doc, so I wouldn't be shy about discussing things here.

    Cheers,

    Michi.

    Michi is right, but please update your signature before starting the discussion :) See this thread:

    http://www.zeroc.com/vbulletin/showthread.php?t=1697
  • Okay, here comes the first cannonball

    Wow! :) Thanks for your detailed comments. I'll address them as best as I can...
    I.A.1.b. "... requires objects that want to have persistent state to inherit from PersistentObject." Related to I.A.1.a: within "this design," which seemingly fails to "place[...] persistence functionality" anywhere at all, it would seem to do other objects no good whatsoever "to inherit from PersistentObject." Is the intent, again, to imply that "at least, that's what would have been true, had the designer done things correctly" ? That's the only viewpoint from which I am able to make sense of this part of the text.

    What I mean here is that the intent of the design is that, in order to become persistent, objects simply derive from PersistentObject, which somehow adds the persistence. So, the implementation of PersistentObject does the work of "persistifying" the objects.
    I.A.2.a. "The above inheritance hierarchy is used to add behavior..." As opposed to what? The way this is worded, seems to imply that a lot is being left unsaid.

    Because the interface is empty, it doesn't add anything but a base type, so it cannot add anything but behavior: the designers make the promise of "inherit from this, and your object will become persistent."
    I.A.2.b. "However, in a strict OO model, behavior can be invoked only by sending messages." Apparently the text is attempting to point out something "not right" about the example design -- but I don't get it. What's the connection between the preceding sentence and this one? Between both these sentences, and the point they're trying to make?

    Because PersistentObject has no operations, I cannot send a message to it, so if the object adds some behavior, then it does so by means that are not made explicit in the contract.
    I.A.3.a. "... presumably, B]PersistentObject[/B knows something about the implementation ... of Thing1 and Thing2..." I'm troubled by the fact that it was necessary to state this; that implies that I am mistaken in thinking that this would automatically be true of any collection of base-and-derived classes; in order for generic behavior in the base to be customized in the derived classes, surely the base has to make some set of tacit assumptions about the nature of the derived classes, always...? If not, I am definitely missing a subtle language / OO point, here.

    OK, we've established that PersistentObject cannot be spoken to by an object using "sanctioned" OO means, because it has no operations. And the derived interfaces have no operations for persistence either. Yet, PersistentObject promises to make these objects persistent merely by being derived from. That means, in order to do its job, PersistentObject must communicate with its derived objects using some sort of "illicit" means, that is, means that are not expressed in Slice.

    Now, as far as a base class making assumptions about the nature of its derived classes is concerned, there is really only one way in which a base class can do this: it can provide operations that the derived classes override. The derived classes are expected to maintain any invariants that the base class relies on (if any). That is the only OO way in which a base class can make assumptions about its derived classes.

    But, for PersistentObject, this is not the case, because PersistentObject has no operations that could be overridden.

    I'll follow up in another post because I'm also starting to run into the 10,000 character limitation...
  • Dang... I accidentally used "Edit" instead of "Reply" and wiped out your previous post. Please accept my apologies--I don't think there is any way to get back your previous post :(

    At least I have the remainder of your post still in a cut buffer here...
    I.A.3.b. "PersistentObject, Thing1, and Thing2 can no longer be implemented in different address spaces." What can I say about this statement, besides "why the heck not? " I just completely fail to grasp the reasoning that leads to this conclusion from the premises given in the preceding sentence.

    The only way things can be in different address spaces and communicate with each other is by invoking operations. But, because there are no operations via which PersistentObject and its derived classes could communicate, they cannot possibly be in different address spaces. Basically, if PersistentObject indeed does what it promises to do, it must get at the state of its derived classes by means other than invoking Slice operations. That means sharing implementation state in the same address space or, alternatively, communicating across different address space via means other than Ice (which is out of scope for this problem).
    I.A.3.c. (Same thing, subsequent paragraph. Everything from "Alternatively..." to the end of the paragraph (wrapping onto page 117 at this point). I don't follow the logic, at all.)

    What I'm suggesting here is that PersistentObject might have a helper method of some kind, such as makeMePersistent that it's base classes could invoke. But, if they do, they again will have to be in the same address space because there are no Slice operations the derived class can invoke.
    I.A.3.d. (Same thing, if we leapfrog ahead a bit to the very bottom of p.117: "you cannot place an address space boundary between interfaces that share hidden state." That seems like more of an arbitrary restriction in Ice -- "we got tired when we reached that point; maybe in a future release" -- than any kind of logical necessity in the... er... "logical underpinnings" of OO theory, or whatever. You guys merely say that it's impossible -- but then, I would have said that two-thirds of what Ice actually does, was effectively impossible... so I'm skeptical! Again -- what fundamental notion am I missing?)

    The finest grain of distribution in Ice is an interface or, to be more precise, the operations that a client can invoke on an interface. Any state that is to be exchanged between client and server must be passed as parameters of these operations. So, if two object implementations share hidden state (that is, state that is not exchanged via parameters on operations), then they cannot be separated into different address spaces, because that would make it impossible for them to share that state (again, assuming that they don't use some other remoting mechanism other than Ice).
    (I suppose ordinary people would simply take all of this "as stipulated," and forevermore just blindly "follow the rule" that "if your design exhibits such-and-such pathological conditions -- as illustrated in this example -- you have to put the entire implementation into a single address space." Never mind why; "it's a rule; just do it." Well, I don't work that way. I must actually understand what I'm doing, in order to do it.)

    That's a fair-enough comment. Basically, the above line of reasoning should make it clear. Whatever state is exchanged among objects must be exchanged via parameters on invocations. If any state is to be exchanged among object implementations not via parameters, they must live in the same address space and share memory (or communicate by some means other than Ice, which I'm assuming is not the case here).
    I.A.4.a. One of my handwritten notes in the margin of p.116 from a week or so ago: "Q: if PersistentObject doesn't have any operations, it's not clear to me how using it can be of any use at all, let alone for persistence. This seems to contradict the prior statement that 'this design places persistence functionality into the PersistentObject base.' What's going on here?" Just to give you a sense of my immediate reaction at the time I read it.

    Well, that was exactly my reaction too the first time I saw this approach. For what it's worth, the approach is really being used. For example, the initial CORBA transaction service was done exactly in this way: an object "became" transactional by deriving from an empty TransactionalObject interface. And Java does exactly the same thing by having things derive from Serializable.

    I've seen the same thing done many times over in other contexts. Whenever someone adds behavior by derivation in this way, what we have is an abuse of inheritance and lack of clear thinking. This is true for CORBA, Java, or any other kind of system that does this. (As an aside, for Java, the reason for Serializable is that, at the time it was thought of, it was understood that it really should be a keyword but, by that time, the language and the VM were frozen, and it was too difficult to do the job properly...)
    I.A.5.a. My other handwritten note on p.116 from that same day: "what does 'a single address space' have to do with it? An interface IS-A Object, and is going to end up being instantiated SOMEWHERE. If it happens to be a PersistentObject, it will have all the internals OF a PersistentObject, and if it happens to be a Thing1 or Thing2 it will have all the internals associated with THAT... So I don't see the problem...?"

    Just because something derives from something else does not mean that they have to be implemented in the same address space. For example, you can have Slice interfaces Base and Derived and implement Base objects in one server, and Derived objects in another server, no problem. But, if the implementation of Derived wants to reuse the implementation of its Base part, then either Base and Derived live in the same address space and share implementation state, or Derived must simulate inheritance with delegation: the implementation of Derived in that case has a has-a relationship with a Base instance to which it delegates the base functionality.
    (Remember, all of this springs from merely the second two-thirds of one page; imagine what the experience of reading the foregoing forty pages has been like...)

    Well, I did the best job I could trying to get my idea across. And, yes, these topics are complex and raise a lot of questions (not all of which I could possibly have answers to).
    Now on to page 117.

    I.A.6.a. (About two-thirds of the way down the page...) "... you cannot pass a persistent object to something that expects a non-persistent object, even if the receiver of the object does not care about the persistence aspects...". My handwritten comment here is simply, "Why not?" I.e., once again I fail to follow the logic. (I'm on the verge of understanding this, in that it seems to be a consequence of "strong typing" in C++. Frankly, though, this is an area in which I have always been somewhat frustrated with C++ (and the last several decades' worth of "improvements" to C): "strong typing."

    To return to the CORBA example, the way to make something transactional was to derive from TransactionalObject. CORBA has a naming service, which defines an interface of type NamingContext. Now, it's reasonable to want both transactional and non-transactional implementations of the naming service: for some applications, transactions might be essential whereas, for others, they might be too expensive and not needed.

    Now, suppose I want to pass a object reference for a naming context to some operation:
    interface MyHelper {
        void doSomethingWithNamingContext(in NamingContext c);
    };
    

    So, we have a formal parameter of type NamingContext. Now, I can pass a non-transactional naming context to this helper, no problem. But I cannot pass a transactional naming context. Why? Because, by deriving from TransactionalObject, the transactional version of the naming context has become a completely different and unrelated type. A transactional naming context is-not-a non-transactional naming context, and a non-transactional naming context is-not-a transactional naming context. The two types are forever unrelated and not exchangable with this "add behavior by inheritance" design: the design splits the type system into incompatible halves, and never the twain shall meet. Clearly, that's a Bad Thing (TM).
    I am forever having to "fight with the compiler" to get it to let me pass, say, an int to a function that takes a const int, or somesuch simple-seeming thing. I avoid the use of const in my code, therefore, because all I've ever seen it "do for me" is get in the way of coding things the way I want them to actually work. But I digress.))

    I would actually recommend against this practice, because it weakens the type system and removes a layer of protection against my own errors. But I digress too ;), and quite famous people (such as Rob Pike) would disagree with me on this point.

    I'll post the remainder of my reply in yet another follow-up.
  • Now we get to the topic that originally impelled me to post here in the first place. "Slice is an interface definition language that has nothing to do with implementation," and subsequent related statements. My general "take" on this is that an interface is the primary determining factor in determining the nature of the implementation: the operations it must provide, the interrelationships among them, and -- by implication, at least, if not explicit specification -- the internal state information that must exist in order for the implementation to "hang together" and function as desired. This being the case, it seems odd to embark on a lengthy text whose theme seems to be that interface definition "has nothing to do with" implementation. I don't find that to be the case at all! I am, apparently, one of those C++ programmers to whom "this ... comes as a surprise." More than a surprise, I'd say it was a complete contradiction of years of Conventional Wisdom.

    It is the job of Slice to define the interfaces of a system. It is the job of the implementation (that is, the code) to provide the semantics of the interfaces and operations. True, the design of the interfaces will typically imply something about the intended implementation (as we saw in case of PersistentObject), but that does not mean that Slice defines implementation. As far as Ice is concerned, all Slice does is define black boxes with buttons that you can push. (The black boxes are Ice objects that have a specific interface type, and the buttons are operations.) Ice also defines type substitutability rules for interfaces, classes, and exceptions. These follow the usual Liskov substitution principle: you can supply a derived thing where a base thing is expected, and no-one will be the wiser. But, beyond that, Ice assumes nothing about implementation, and Slice has no business specifying implementation detail.

    In case of PersistentObject, inheritance is used to state something about implementation, which is an abuse of inheritance in the context of a an interface definition language (and many OO experts would agree that it is also an abuse of implementation inheritance, if there are no operations on the base class).
    I.B.1.a.-- the entire paragraph on "Interface Versus Implementation Inheritance" is one big tangle, to me. Referring back to my earlier argument that an interface definition is the primary influence on an implementation, I would also therefore argue that relationships between interface definitions would inevitably influence relationships among the corresponding implementations. How can it possibly be otherwise?

    Interface inheritance does not necessarily imply implementation inheritance. As I pointed out, a Derived can implement interface inheritance without actually using implementation inheritance, by delegating to a base instance via a has-a relationship. Or Derived could avoid using implementation inheritance by simply implementing both base and derived operations in a single class.
    The only way I can picture "interface" inheritance being at all decoupled from "implementation" inheritance, in practice, is to violate several of what I was taught to believe were fundamental OO design principles: code reuse and inheritance itself.

    I disagree. For example, in Java, we do this routinely: we can't have multiple implementation inheritance so, when needed, we simulate multiple implementation inheritance by delegation. (The tie mapping for Java in Ice exists to facilitate this.)
    Basically, I can envision coding up e.g. Thing1 and Thing2 as two completely separate and distinct pieces of code having nothing more in common than a #include <slice2pp_generated_header> at the top. But that would go against most things I was taught about how to "do inheritance," in that I should have put all the "behaviors/state that the two have in common" into a single implementation (of PersistentObject) from which two no-longer-so-separate-and-distinct implementations of Thing1 and Thing2 now simply inherit, precisely as do their interface definitions in Slice. By my lights, as I mentioned, it seems inevitable that interface inheritance simply must call for implementation inheritance, as a logical consequence of "good programming habits."

    Again, I disagree. Just because there is a common base class does not mean that the derived classes must use implementation inheritance. They might, but might not. It's common to use implementation inheritance. But there is also a school of thought that says that all base methods should be abstract. (Scott Meyers is one proponent.) With that school of thought, there is no implementation inheritance, only interface inheritance.
    That's about it for pp. 116-118. My last comment is that perhaps I am simply misunderstanding a special, jargon-style usage of the words "interface" or "implementation" or "inheritance," though I don't get that impression from the text.

    No, no hidden meanings here ;)
    I look forward to your collective comments on all of this. Are you sure you don't mind my posting? There's a lot more where this came from. I did try to warn you...

    No problem, post as much as you like. What I can promise is that I will read all your comments and act on all those I agree with. (Your comments in this posting prompted me to change the wording on page 116/117 to make a few things clearer -- thanks for that!) What I cannot promise is that I will reply to all of your posts, especially in as much detail as for this one. That simply takes more time than I can spare. But rest assured, I will look at everything you report and take it seriously, and act on it if I think it will improve the documentation.

    Cheers,

    Michi.
  • Thanks

    Thanks, Michi, for such a rapid and thorough response. I'm impressed.

    I'm not sure I yet understand everything you said, but I'll chew on it awhile before I post any followup questions.

    Onward!

    Chris :)
  • Quick note: 5 days after encountering Ice for the very first time, I had learnt enough to write a pretty complex proof of concept in C# .NET that (surprise surprise) worked, and did what it was supposed to.

    The learning curve was ok - I ended reading through the relevant parts of the user manual, watched the screencasts, examining the demos, asking a few questions on the forum to get to the implementation stage.

    Hats off to the guys that designed the ICE framework, I'd say its very well designed, and the docs are great in my opinion. Could do with more screencasts though - they are magic.