[Ns-developers] ns-3-names comments
craigdo@ee.washington.edu
craigdo at ee.washington.edu
Sat Feb 7 23:41:53 PST 2009
More long email. But there are reasons that I did what I did and I'm going
to explain and justify the design decisions I made ...
> I was asked to review this ns-3-names code, so here are some thoughts.
>
> static std::string FindShortName (Ptr<Object> object);
> static std::string FindFullName (Ptr<Object> object);
>
> I think this was already discussed but I'm too lazy to search
> the archives.
>
> I think I would prefer Get instead of Find here because I
> associate a "find"
> operation as something returning an iterator for the location
> of something,
> and that iterator can be null if the object is not found.
We've gone over this a number of times, and the interpretation I think we
generally agreed on is that Get refers to one item. It does not return an
error. Find looks for an item among some number of things and it can fail.
Find doesn't necessarily have anything to do with an iterator, just
selecting one of n items based on some criterion. Unfortunately, as quite
recently discussed elsewhere, this convention is not generally observed, and
we often just use Get irrespective of what is happening.
I think Find* is the correct use based on our often discussed and debated
convention. If we want to just bail out on using the verb Find, based on
the many, many instances of the verb Get and the several instances of Find,
I could be convinced. But I think this is how we agreed to do it.
Oh, and all of our requirements authors wanted to call the functions Add and
Find.
> These methods,
> however, return always a string (by the way, the documentation should
> indicate that an empty string is returned if the object isn't
> found). But
> the scenario of returning an empty string is uncommon (I
> think...), in which
> case the Get becomes a stronger alternative to Find.
I would argue that Find becomes a stronger alternative to Get because it is
returning an empty string that indicates that there is no name to Find among
the possibly many alternatives.
> I would argue that
> returning an empty string is uncommon because I don't find it
> normal for the
> programmer to ask for the name of an object if he did not
> previously had
> assigned human readable names to objects of interest.
I'm not sure I understand what you mean. Sure, a user writing correct
programs will only ask for the name of an object that has a name. Asking
for the name of an object you haven't named will hopefully be an uncommon
thing to do for a script author, but we should indicate this somehow, no?
It may not be an uncommon thing to happen, however, if we change the pcap
code to look for a name. It would do that so it could create a file name
like, "server-eth0.pcap". In this case, there might be dozens of devices
with no name, but only one with a name. In that case it would not be
uncommon to return an empty string (or indicate an error some other way).
I'm really not sure what you're getting at ...
> Another aspect is regarding terminology. ShortName vs
> FullName. Perhaps it
> would be simpler to call these just Name and Path?
Well, in the Config code, Objects have Attributes and Attributes that have
names. A path in a config namespace means that there are some number of
corresponding Objects that are actually linked together using Attributes.
There are rules for what is legal in a path and what correspondences there
must be with Attributes. Path means something that is apparently hard for
users to understand.
In the name code, there are no Attributes involved at all to have names.
There are shortnames which aren't connected with Attributes in any way.
Objects do not have to be linked together in any way to make a path
reachable from some root namespace Object. There are no wildcards allowed
in a fullname, and no automatic index parsing. A fullname is actually
semantically different than a path so I called it something different.
Understanding the structure of a config path has been difficult for
beginning users. I've seen lots of questions about how it works. It seemed
to me that introducing another kind of "path" that is superficially similar,
but subtly different and has different rules for regular expressions and how
it is added to the system would just add to confusion.
The names system is different in this respect, so I refer to the pieces by
different names. To me it makes more sense.
> My ideal
> naming for
> these methods would then be something like this:
>
> static std::string GetObjectName (Ptr<Object> object);
> static std::string GetObjectPath (Ptr<Object> object);
>
> Idem for FindObjectFromFullName => GetObjectFromPath.
I'm not sure about GetObjectXxx. See below.
> This is just silly:
>
> * @comment This method is identical to
> FindObjectFromFullName, but has a
> * short signature since it is a common use and we want it
> to be easy to
> * type.
> */
> template <typename T>
> static Ptr<T> Find (std::string name);
I don't think it's silly at all, of course. Depending on what level you are
using the system, you may have different ideas about how to think about the
name service and the pieces that make it up. From a high-level perspective,
you will probably just be wanting to look up Object names and use them in
config paths or log prints. You probably will never care about the fact
that there are Object contexts and probably won't ever think about breaking
these strings up and matching them with Config path segments. You won't
worry about whether or not you provide a context, or not, and what the
remaining name string has got to look like (single end segment or fully
qualified name).
If you are mixing and matching calls, especially in a place where the term
context is meaningful, I think it is helpful to associate the fact that a
shortname goes with a call requiring an object context. It's a shortname --
it needs a context.
FindObjectFromShortName (Ptr<Object> context, std::string name);
However, if you are providing a fully qualified name, you don't need the
context, because the act of providing the qualification provides the
context. It's a longname -- it doesn't need a context because the name
provides the context.
Names::FindObjectFromFullName (std::string name);
I found the verbosity and symmetry helpful in keeping track of what call did
what; and what was expected in the name parameter in which situation.
As I said, users operating in a script will probably not need to concern
themselves at all about Object contexts, shortnames or fully qualified
names. They probably just want to get an Object Ptr by providing a string.
All of this other stuff will just confuse the situation. So this user wants
a simple, straightforward, short uncomplicated function. I gave it to them
Find (std::string name);
This makes perfect sense to me and the overhead is once subroutine call
which seems quite worth it.
> Make up your mind and choose one method name.
I'm not confused or unable to make up my mind. I deliberately chose to use
two method names for what could have been one API method in order to help
out two kinds of people operating in very different environments.
> My pick would
> be to have
> just one GetObject (std::string path) method.
Wow. It almost makes my brain explode trying to conceive of explaining the
subtleties of this to someone working at a scripting level who doesn't even
understand the basics of aggregation. Think about,
Ptr<AggregatedType> at = otherObject->GetObject<AggregatedType> ();
Ptr<Object> o = node->GetObject<Object> (TypeId::LookupByName
("ns3::Node"));
Ptr<Node> n = Names::GetObject<Node> ("/Names/client/eth0");
and answering questions about why this or that doesn't work. e.g., 'I tried
to do node->GetObject<NetDevice> ("eth0") but it didn't work and suggested I
provide a TypeId. But TypeIds seems to have something to do with classes,
not object names. GetObject is an object method. Someone told me I need to
use a static function because this isn't really Object aggregation. I don't
get it. All I want to do is get the eth0 device.'
It has a certain attractiveness, though, but it's one of those things were
it subtly leads you into thinking that one thing is going on but in reality
something entirely different is happening. In this case, I think it would
astonish people that node->GetObject<NetDevice> ("eth0") wouldn't work
because they have been subtly misled into thinking that Names::GetObject is
something else than it is.
I think in order to be not entirely confusing, naming would have to be
integrated down at the GetObject level in class Object so the previous thing
would just work. At the other end, I'm a bit worried about having to
present GetObject at a very high (scripting) level. That said, eventually,
though, someone is going to figure out that you can do,
Ptr<Node> n = CreateObject<Node> ();
Ptr<Mobility> mob = CreateObject<Mobility> ();
n->AggregateObject (mob);
Names::Add ("node", n);
Ptr<Mobility> m = Names::Find<Mobility> ("\Names\node");
and find your aggregated mobility object using the name service to look up
the node since there really is a GetObject under the sheets. Eventually it
may be seen in some scripts, but at least you don't have to introduce
GetObject at the beginner script level.
So, I'm a bit conflicted on this one. I had not considered integrating
names down to that level in the system for this go-around. Myself, being
comfortable with GetObject, I don't have a problem with integrating names
down at this level. I'm really not so sure about newbie users, though --
getting them to think of GetObject as a name lookup and then throwing object
aggregation into the mix.
I think we need to think carefully before just jumping in on this one ...
> template <typename T>
> static Ptr<T> FindObjectFromShortName (Ptr<Object> context,
> std::string
> name);
>
> I'd also rename this to: GetObject (Ptr<Object> context,
> std::string name);
> C++ overloading will take care of this nicely, and I think
> this is one case
> where overloading does make complete sense.
I think this makes the situation _very_ confusing -- this clearly isn't like
GetObject since it involves another object, but it's called the same thing:
Ptr<NetDevice> nd = Names::GetObject<NetDevice> (n, "eth0");
Sorry, I think this would be horribly confusing. If you're going to make it
work like GetObject, make it work like GetObject.
Ptr<NetDevice> nd = n->GetObject<NetDevice> ("eth0");
> And instead of 'context'
> perhaps just calling the parameter 'parent', or
> 'parentObject', makes it
> easier to understand.
This is something that comes from the config code. I actually thought that
the term context was confusing there when I first saw it as well (in
config), but since that is what we call it, it made sense to me to re-use
the term. People will have to figure it out in the Config "context" so lets
just borrow the term since it really does look the same down there.
In the Config (trace) code
/NodeList/0/DeviceList/1/ Rx
^^^^^^^^^^^^^^^^^^^^^^^^ ^^
This is the context This is the attribute
In the Names code, providing a string context in an add
/Names/client/ eth0
^^^^^^^^^^^^^^ ^^^^
This is the context This is the name
When using Names and Config together in a Config path for a trace hook
/Names/client/eth0 Rx
^^^^^^^^^^^^^^^^^^ ^^
This is the context This is the attribute
So the context in Names and Config are essentially the same thing from the
tracing point of view; so I used the same term. It makes more sense when
you look at the string versions of the methods and relate them to the Config
alternatives. It doesn't make as much sense if you just focus on the object
version and what it does under the sheets.
It can get a bit confused too at the boundary cases
/NodeList/0/eth0 Rx
^^^^^^^^^^^^^^^^ ^^
This is the context This is the attribute
of a trace.
This can be used as
a mixed path in Config
calls.
This is not a strictly
legal config path.
This is not a legal
context in the Names
system.
I think it all depends on how you are looking at the system -- from which
vantage point -- whether one term or the other is marginally clearer.
Should it be context, path or parent? Since the place where we are
currently using these low-level functions is actually in the config code
where the term context is used, it makes good sense to call these things
contexts. It ties it all together nicely in the place where the name and
config code is tied together. It makes perfect sense in all but the
degenerate cases. If you look at it from outside that perspective, you
could argue differently; but the place where the thing is used is where the
term context makes sense.
So, calling it context made the most sense to me, even though I originally
had a problem with the word context, too.
> Documentation somewhere says:
>
> * Given a fullname string, look to see if there's an
> object in the system
> * with a that associated with it. If there is, do a
> QueryObject on the
> * resulting object to convert it to the requested typename.
>
> Of course QueryObject does not exist (sigh), and you mean GetObject.
Sigh indeed. I still catch myself talking about QueryInterface
periodically. Good catch.
More information about the Ns-developers
mailing list