David Haley said:
Quote: David, in no way am I sending a function object. It can look that way to the user, in this Lua version, because it's intuitive.
Again, I never said you were actually sending function objects. It doesn't matter what you're sending under the hood as long as the interface is that you pass a function object. For all intents and purposes, as far as the user is concerned, you are sending function objects -- and your API must deal with receiving function objects as parameters to be sent around.
The API does handle this, it just stores them in the same PPI table as any other value. The only special treatment they get is in relation to serialization, where they get their own identifier and are added to the table of callable methods.
David Haley said:
Quote: Lua is the one we're already discussing.
No it's not -- it's the language you happen to have implemented this in so far. But a constantly recurring them is language independence, and the ability to reimplement in any scripting language. Therefore Lua's particularities aren't really relevant.
PPI, to me, has two distinct parts, which I touched on in my last post: the protocol, and the implementation. The protocol is the backbone of PPI, no matter what language you're working in. The implementation strives to expose the protocol data in as native a way as possible. It doesn't matter how it does it, it's specific to whatever language we're dealing with.
David Haley said:
Quote: However, I still stand by my reasoning that there is no valid use case for tables as keys.
You've basically just asserted this, but when other people have stated that they don't really see valid use cases for other things, you've basically just asserted that they're wrong. This makes it hard to make forward progress. Clearly if people aren't convinced, then you need more to be more convincing or reevaluate your position. I'm not really sure how else to say it. (And this isn't meant to mean offense!)
I've tried to give use cases as best as I could. When it comes to serializing 'graphs', I'm not really understanding the opposition. It adds no complexity on its own, because most bits it would need have other reasons for being implemented. It just seems completely incorrect, wrong, and unintuitive to serialize the same table twice, resulting in an incorrect model after deserialization. It would would actually add to the user's burden as well as the code if I were to add this restriction.
David Haley said:
Quote: I could include them if I wanted, simply by adding another conditional to the serialization routines - if you think I should, it's an easy enough addition - but I don't see the point.
I don't see the point either, it's added complexity for extraordinarily infrequent utility. The point was only that there seems to have been a somewhat arbitrary line drawn when it comes to supporting this or that generality.
The difference between table keys (which I don't want) and table 'graphs' (which I do want) is that in both cases, changing it in the opposite direction would add more code and an extra burden on the user.
David Haley said:
Quote: We differ at a fundamental level here: I don't believe this is complicated. It rests on three core messages (each with a very clear purpose), a serialization protocol (aimed to be language-agnostic and extensible), and certain allowable data types. Everything else just drives it.
Complicated doesn't mean hard to understand (even though you have said yourself that some things are confusing, like that double-storage thing Nick discussed). It means that stuff is happening that doesn't necessarily need to happen.
And we've already seen that we're actually not all that language-agnostic anyhow.
The double-storage thing is an implementation detail specific to Lua so that I can get at the private data from within __index, based on the PPI table being accessed. It's not that confusing once you understand that.
I'm not sure what stuff is happening that doesn't really need to happen, though. Like I've said, everything is based around a core system of three messages, serialization, and saving exposed values so clients can reach them. Function paramters/returns use the same system as PPI-exposed functions, even.
David Haley said:
Quote: I think it is perfectly reasonable to think that you might send a list where the same table appears twice. Are you saying there are no occasions when this might be warranted?
I will not say that one would never, ever want to do this. But then again, I could say that about a very great deal of things: including tables as keys. My argument is not that you would never want to send complex tables; my argument is that you would want to so rarely that it does not justify increasing the general complexity to deal with this.
It doesn't increase the complexity, though. I tried to write the serialization routines as simply as possible. Everything in them has its place. I could remove the caching functionality for tables, but that would add to the complexity of the serialization routines.
David Haley said:
Quote: We're not sending function values, as you noted, just an identifier. It makes sense, for this Lua version of PPI, to provide a natural, feels-like-Lua interface.
We're talking about a language-agnostic protocol, in which one plugin can call some arbitrary code in another plugin. The convention that all functions must be passed by their actual name is simple to understand and the most likely to be as portable as possible. Furthermore, if I'm calling out to another plugin, I don't call FunctionWithRandomIdentifierXYZ, I call a specific name. Passing callbacks (or any other function) by name (again, not random identifier) is consistent with calling functions by name.
You can only ever get at a function through the identifier passed from the ACCESS message (i.e. tbl.someMethod). If you really, really wanted me to, I could change it so that it would use its real name out of the PPI table. But that would, again, create more complexity in the code just to make it look nice. The protocol's not supposed to be readable, it's supposed to be easy to parse and deserialize. Given an identifier - any identifier, be it name or number - you simply store the identifier somewhere and return a proxy of some kind. (This is assuming Lua-style semantics; a PPI in VBscript would undoubtedly have an entirely different user interface) The proxy later takes that same identifier and makes a REQUEST with it. That's all there is to it.
ACCESS and REQUEST (the latter of which should really be called INVOKE) have direct analogues in the __index and __call metamethods. One gets a value from a table, and the other executes a function. __index does not return a name, nor does it execute the function. It just returns something you can use to invoke that function, whether now or later. It's the same idea.
David Haley said: Now, as a convenience, you could accept functions by value and use debug.getinfo to introspect a useful name, and complain if one cannot be found.
But this is a good example of the distinction to be found between the core of a design and conveniences, or syntactic sugar if you will, built on top of it.
This convenience requires an extremely simple type check followed by a debug.getinfo call, and entirely obviates all the complexity of generating and storing random function identifiers.
Why? This is needless complexity on its own; I'm doing pretty much exactly the same thing as __index and __call (see above).
David Haley said:
Quote: I can't answer that properly without an actual use scenario.
This isn't meant to be sarcastic or facetious, but how can we possibly design a reasonable, practical API without having actual use scenarios? It's very hard to design things in a void, from theory alone.
No, heh, I meant that your question is hard to answer without the context of a specific scenario. It's your question that's in the void.
David Haley said: It's possible that you're trying to be too general here, which is I think the point that Nick was making. Complexity is warranted if it's addressing real problems. Complexity is not warranted if it's solving problems simply because they can be solved (even if those problems are relatively easy to solve, because it's extraordinarily rare that you can make one change without impacting the rest of the system).
It's a funny fact of CS in general that often, solving more can actually provide less value. There's a very fine line between solving the right amount, and solving too much. After years of doing this, I still find this line hard to navigate. One thing I do know is that as people accumulate years, they tend to get more and more wary of complexity unless it's necessary.
See my previous answers. Everything I've written in PPI has a distinct purpose and reasoning. It's probably not obvious to everyone reading it, that's why we're all here right now.
I'd like to mention, by the way, that normally if someone says something is wrong with my code or design, I'm very reasonable about it. More often than not, I make lots of changes based on their feedback. I'm certainly not as defensive as I appear here! The problem, as I feel it, is that you're not understanding my reasoning in PPI (and I've put a lot more thought and reasoning into it than many of my previous projects, for what that's worth). I think you may have a point with the graphs, but I've also given my reasoning on why I think it's best that way. It's just frustrating because everything makes so much sense to me, and when I read your and Nick's posts, I (rhetorically) wonder if you've (a general 'you') read over the code, tried what I've explained, or seen my working examples (ATCP and roomname). I understand that Nick has, and that you've looked over PPI at least, but again... we're back to not understanding the logic in the code. (That's probably partially my fault.)
... Eh, no offense meant! I just really want to make sure you understand my point of view. Not trying to start a flame war or anything. =/
(EDIT: What I'm trying to say, I think, is: please don't think I'm throwing a tantrum or something because I'm not getting my way. That's absolutely not it. :( )
David Haley said:
Quote: 4. Because there are some resources which simply cannot or should not be accessed by multiple threads. ATCP data is one. If multiple plugins try to pull ATCP data from the packets, all but one will starve. As another example, you have the typical MUD prompt. If multiple prompt triggers gag it and try to re-echo it out differently, you suddenly have multiple prompts. Having a prompt plugin would allow you to centralize your prompt modifications, using PPI to communicate with the plugin. There are many other examples of resources like this.
These are generally good examples (although the prompt one sounds like you're doing all the stuff in one plugin, so I'm not sure where the cross-plugin communication comes in). To be clear, I also completely believe that being able to communicate between plugins is a Very Useful Thing. The point of the exercise was to discover the essence of what cross-plugin communication is really about. The ATCP example, for instance, only needs to shuffle around very simple data. Similarly for the prompt plugin. So far, we haven't really justified anything more complex than numbers, strings, and maybe basic key:value maps from numbers/strings to numbers/strings. We have perhaps also justified simple callbacks, so that the centralized handler of a shared resource can notify its (potentially multiple) listeners that new data is ready.
In relation to the prompt one, my concept is more about allowing other plugins to contribute bits of data to the prompt plugin, and allowing the user to pick and choose what data to display. Something along those lines, at least - it's one of those ideas that I never really got a chance to work with.
David Haley said:
Quote: Of course, special resources aren't the only use case. Another guy I know wants to create a combat system broken up into plugins, using PPI to communicate with a central core (which might have an event loop, for example). The client plugins could be 'class' plugins, which implement triggers specific to certain class attacks. The core would be the driving force.
Why does stuff need to be separated into several plugins, as opposed to one plugin with several modules? If the other plugins cannot exist without the core, and the core is useless without the others, it's hard to justify the existence of several plugins. (The existence of several modules is however easily justifiable.)
It's easier to unload or mix/match plugins than it is modules, IMO. The core, by the way, is by no means useless without the extra plugins. That's sort of the point. You have outer modules which represent functionality specific to responding to a certain class's afflictions/abilities. You disable/enable them depending on what classes you're fighting (yes, multiple, especially in raid situations), which can be important for anti-illusion mechanisms (illusions allow you to room-emote text, which can mess up your system if they illusion various attack/affliction messages).
It also helps if you want to support addons, i.e. plugins that can access and interact with the core. The popular Vadi System (a proxy system rather than a client-specific one) allows you to create separate DLLs and load them as addons.
David Haley said:
Quote: PPI - specifically my version, with functions as valid parameters - would immensely clear up his code to no end.
It could also be that his code is just poorly designed, and could be refactored without bringing this considerable tool to bear.
We spoke earlier about encouraging behavior that we might not like. This could be an example. By allowing plugins to send completely arbitrary data, we make it easy (or at least easier) to break stuff into separate plugins when modules might be more appropriate. If the cross-plugin transport layer has clearly defined goals (of which arbitrary communication is not one) people need to think more. In other words, a clear and concise design can propagate upwards to some extent. It's not a good idea, IMO, to force a more-complex API simply because some users are lazy and don't feel like refactoring stuff.
Quite possible, even perhaps probable. But see above for why I don't think it's a bad design.
David Haley said: Perhaps some (non-exhaustive) specific questions:
- Why decide that some generality is desired, like complex tables, but other isn't, like more types as table keys? (Why not bools? What about (light)userdata? Where does it stop?)
Well, I could add bools. The problem with table keys is that those keys are the only copy in that whole environment, because all parameters are copied. As I mentioned previously, you'd have to do a pairs() loop just to get at the keys to use them, which is rather pointless - and sending a table key -back- to the source plugin would be useless, because it's yet another copy, so the source plugin wouldn't be able to access the values it's supposed to. Booleans don't suffer this issue.
David Haley said: - Why do we need to support arbitrary function objects at the implementation (not interface) level?
I'm not sure what you're saying. I always saw PPI as having two levels: protocol and implementation. The implementation is necessarily locked to a specific language. Other languages could take a completely different approach to implementing a PPI interface in order to give that particular language a more intuitive feel. As I have it, the implementation is tightly coupled with the interface for that very reason.
The concept of a function identifier, at the protocol level, is fairly language-agnostic. Any language should be able to represent it in some manner, even if it's nothing like the Lua approach.
David Haley said: - Are we truly language-agnostic, or have we discovered that maybe we like some things about Lua in particular? If we want to be language-agnostic, how much Lua-specific stuff have we added? How much complexity has been added to other languages' implementations to deal with this stuff? What about things that other languages might like to do, such that new support must be added to Lua? (If your expertise is in Lua, and not Perl/Ruby/Python, how do you really know that you are language-agnostic? How do we know that, with this large system, we won't be adding even more maintenance work as we start expanding to other languages?)
Again, the implementation itself is necessarily locked to a language. The only part that you absolutely have to have is a compatible set of serialization routines, which act as the decoupling filter between implementation and protocol.
David Haley said: (That second point speaks to the problem of interface vs. implementation: if your interface wants to be convenient that's fine, but that shouldn't necessarily affect the general implementation of the core protocol.)
See above. (I'm only being brief because I don't want you to think I'm ignoring parts of your post by not including them.)
David Haley said:Regarding the last point, this statement greatly worries me:
Quote: As PPI implements such a full-featured interface, its semantics are nearly identical with Lua's own, providing a much more intuitive method of communicating between plugins.
This sounds like it will make implementing PPI semantics in other languages very difficult if Lua's semantics don't map well to those other languages. A simple example is that you have no guarantee whatsoever that other languages can even conceive of passing around function values to be called. The PPI API and its very capabilities would then be remarkably different if you are working in Lua vs. VBScript, for instance.
Wrong, as I see it. The serialization routines themselves can be written in pretty much any supported MUSHclient language, IIRC. The actual representation of the protocol data will necessarily be different depending on the language in use, and as such, the implementation/interface would necessarily be different. The API might be remarkably different given a different language, and likely the implementation too (in a language such as VBScript, for example), but the only important part is that they can understand the protocol.
I would write a Ruby port if I understood Ruby well enough, but I haven't used Ruby enough and probably would do a shoddy job of it. |