From: Oren Ben-K. <or...@ri...> - 2002-05-09 13:50:46
|
Clark C . Evans [mailto:cc...@cl...] wrote: > 1. Distinction of !str, !float, and !int objects > with respect to aliases are not preserved. Thus, > the following are equivalent: > > --- > - &001 one > - *001 > --- > - one > - one > > 2. Emitters should, as a general guideline, use > aliases for !str copies over 31 characters; > not use aliases for !float, !int, and !str > 31 characters and under. Hmmm. This is a good point. I think that in general the rule should be that as far as he YAML models are concerned, aliases to scalars are allowed but may not be preserved. I'd modify (2) above to "use aliases for scalars if their representation is longer than 31 characters". This covers the !int and !flt cases, as well as !binary etc. Have fun, Oren Ben-Kiki |
From: Andrew K. <ku...@sf...> - 2002-05-10 04:00:17
|
This seems dreadfully wrong to me: > From: "Clark C . Evans" <cc...@cl...> > Subject: [Yaml-core] alias usage > > I was thinking about the following rules for how our > built-in family objects deal with aliases: > > 1. Distinction of !str, !float, and !int objects > with respect to aliases are not preserved. Thus, > the following are equivalent: > > --- > - &001 one > - *001 > --- > - one > - one > > 2. Emitters should, as a general guideline, use > aliases for !str copies over 31 characters; > not use aliases for !float, !int, and !str > 31 characters and under. To me, the point of alias/anchors is to preserve the structure of the object being dumped. They're not just a convenience for saving storage. They're addresses (what self-important languages call references). They contain the *structure* of the data, which is more than just the leaf values. If a YAML dump doesn't preserve this, then it's much less useful. Talk about the graph model, you'd never be able to dump a graph at all, if you left out leaf addresses. Yes? Andrew |
From: Clark C . E. <cc...@cl...> - 2002-05-10 11:54:41
|
| To me, the point of alias/anchors is to preserve the | structure of the object being dumped. They're not | just a convenience for saving storage. They're | addresses (what self-important languages call | references). Agreed. The problem is purely pragmatic on two fronts: 1. In many languages, the equivalent of the "int" and "float" are stored using a binary representation without an address. Thus, there isn't a way to distinguish from the integer 3 found in two different locations. This same approach is often taken with small "immutable" strings. Some languages "intern" strings without asking without a way to disable the feature; not only that, it is often the behavior you want. 2. When strings are used as map keys or small enumerated values, such as a task status (complete, in-progress, etc.), and the strings are "interned" or assigned from a single location, the YAML serialization is mostly all references; which hurts readability in common business cases... person: - &1 given: Clark &2 family: Evans &3 gender: &4 M - *1: Oren *2: Ben-Kiki *3: *4 - *1: Brian *2: Ingerson *3: other Examples like this one become very unreadable when you add 10's of attributes (many of them flag like fields like gender) and more data... 100's of objects. So. I was thinking that a few of our core types may represent this "normalization" equivalency. However, this equivalency would be only propery of the !str, !int, !float types. It would _not_ be a property of objects in general. An alternative, is to allow for a more flexible comment usage: - *1 # given : Oren *2 # family : Ben-Kiki *3 # gender : *4 # M This isn't quite as readable, but it does preserve the structure. But still this doesn't solve problem #1; that is, depending on the language, small scalar values are not always distinct (esp. numerical types like !int and !float). | They contain the *structure* of the data, which is | more than just the leaf values. If a YAML dump doesn't | preserve this, then it's much less useful. Oh I agree here. I wasn't proposing a general case; but just a few basic behaviors to describe for the !str, !int, and !float cases. | Talk about the graph model, you'd never be able to | dump a graph at all, if you left out leaf addresses. --- &1 [*1] # ;) Does the above sound resonable. Perhaps this doesn't go in the spec; mabye it is just eratta for the Python binding. Although, it's quite clear to me that I cannot preserve the following: --- - &1 23 - *1 So, perhaps this should be considered more of a general limitation of the "core" data types? I only say this since most languages work like Python and since i'm not proposing a general case. Best, Clark |
From: Andrew K. <ku...@sf...> - 2002-05-11 03:19:30
|
> From: "Clark C . Evans" <cc...@cl...> > Subject: Re: [Yaml-core] Re: alias usage > > | To me, the point of alias/anchors is to preserve the > | structure of the object being dumped. They're not > | just a convenience for saving storage. > > Agreed. The problem is purely pragmatic on two fronts: > > 1. In many languages, the equivalent of the "int" and "float" > are stored using a binary representation without an address. > Thus, there isn't a way to distinguish from the integer 3 > found in two different locations. > > This same approach is often taken with small "immutable" > strings. Some languages "intern" strings without asking > without a way to disable the feature; not only that, it is > often the behavior you want. > > 2. When strings are used as map keys or small enumerated values, > such as a task status (complete, in-progress, etc.), and the > strings are "interned" or assigned from a single location, the > YAML serialization is mostly all references; [ . . .] Everything you say sounds just slightly weird to me: Suppose we're talking about the dump direction. For example: (1) I say "I'm going to give you an array" (2) "Here's the first element: It has type !int and value 3" or I say (2) "It has type and value AND anchor 1004" That is, it's up to me, as I push stuff out to tell the emitter whether to use an anchor or not. He doesn't have any descretion. He just does it. This is right, of course. If I'm dumping an interned language it will take some finesse on my part to know whether the thing I'm dumping *needs* an anchor or not. But, it's not up to YAML. Same sort of thing happens in the other direction: YAML tells me whether there's an anchor or alias. Then, my loader has to do something about it. So you see, I don't see that there's any problem. What have I missed? > > | Talk about the graph model, you'd never be able to > | dump a graph at all, if you left out leaf addresses. > > --- &1 [*1] # ;) > Oh, *that* graph. I don't think gentlemen should discuss *that* sort of graph, at least not in mixed company. > Perhaps this doesn't > go in the spec; I agree, not in the spec. But somewhere. I feel too much of this discussion is being lost. It really should be preserved in the philosophy document. The spec should be brief and precise. The phil doc explains the motivation, making it both an introduction for outside experts and insurance, so that errors in the spec can be corrected to their intended meaning. > mabye it is just eratta for the Python > binding. ?? 'Errata' means 'errors'. 'Errors in the Python binding'? > Although, it's quite clear to me that I > cannot preserve the following: > > --- > - &1 23 > - *1 > This seems like a show-stopper to me. If that's the structure the user wants to convey, who are we to say 'no'? Again, this sort of question needs to refer to the underlying purpose of YAML, (as laid out with crystal clarity in the phil doc). Once we know what YAML is *for*, then we know what it is required to do. So here I am again, feeling that I'm not quite on the same wave-length as you 3 caballeros. Enlightenment, SVP. Andrew |
From: Clark C . E. <cc...@cl...> - 2002-05-11 04:24:52
|
On Fri, May 10, 2002 at 08:19:26PM -0700, Andrew Kurn wrote: | I feel too much of this discussion is being lost. It really | should be preserved in the philosophy document. The spec | should be brief and precise. The phil doc explains the | motivation, making it both an introduction for outside | experts and insurance, so that errors in the spec can | be corrected to their intended meaning. Would you be willing to make a first cut? Perhaps even it could be added to the beginning of the spec right after the "tutorial" and before the "model"? I'm not quite certain how this could be done... so a boostrap document would be very helpful. | ?? 'Errata' means 'errors'. 'Errors in the Python | binding'? Yes, in that it can't properly round trip particular structures. They would be permanent errors though. | > Although, it's quite clear to me that I | > cannot preserve the following: | > | > --- | > - &1 23 | > - *1 | > | | This seems like a show-stopper to me. If that's the structure | the user wants to convey, who are we to say 'no'? See my last message, that is defining the behavioral interaction with aliases at the type family level, for example, a statement in the !int type family that the above need not be preserved by all bindings. In this way we arn't saying "no" to the user; as much as we are saying that the user can't depend upon this particular type family supporting that distinction. Of course if this distinction was important, the user could make up their own type family which did. yea? Or is this just wishful thinking? | Again, this sort of question needs to refer to the underlying | purpose of YAML, (as laid out with crystal clarity in the | phil doc). Once we know what YAML is *for*, then we | know what it is required to do.a Yes... I do agree, this could help out. How about this: - to make common place serialization easy and readable - to make uncommon serialization possible. ;) Clark |
From: Clark C . E. <cc...@cl...> - 2002-05-14 11:37:27
|
Andrew did a first pass at a philosophy document. | OK. How's this? I'll dash off whatever I can in half an | hour right now. If this is no use, no-one will have lost | much. Cool. Thanks for the boostrap! | ------------------------------------------------------ | The purpose of YAML is to provide a standard text file | format for general computer data. | | In order of importance, the goals of this format are as follows: | | 1. The file must represent the data exactly, so that the | data may be reconstructed from the file. (See #A) | | 2. Sometimes, YAML allows several formats to represent data. | For a given datum, at least one of the allowed formats | must be easy for a man to read. (See #B) | | 3. YAML facilitates automatic transformation between the data and | the file, and facilitates a good automatic choice of file format | for man-readability. 4. YAML enables, as best as possible given the other constraints, the communication of information between computer processes using different platforms and languages. | The above, as you see, is meant to be pure. | OK, now I want to mix the particular with the general. | | #A Of course, much of the detail of the data as they appear | in memory is irrelevant. The actual values of pointers | don't matter, usually, but only what they point to. Thus, | it is up to the user-interface part of the dump (emit) | direction to decide which parts of the data being dumped | are important. In an interned language, the fact that | all pointers to a unique string are identical is guaranteed, | so the dumper would omit that information in | the dump, since the data can still be reconstructed | (given this understanding) and the pointer information | would clutter up the text file, reducing man-readability, | which is the second goal. | | This is to say, an 'exact' representation of the data | depends to a certain degree on the universe, the context | from which they come. I like the wording here. | Now, let's take the case that's concerning us at the | moment, the pointer to int. Suppose, particularly | that I'm trying to dump a C array | | int * arr[100]; | | Without sketching too much of the context, you can see | that I wouldn't have declared them pointers unless I | needed them, since I could as easily have declared | | int arr[100]; | | So, take it that I need this. What do I do? Two approaches: (a) let the user provide their own "dump" layer which calls the emitter directly, this would probably be the approach for a "C" structure; and (b) let the automatic dumper (which doesn't exist for "C") use the !ptr type. int val[3] = { 8,33,22} int *arr[3] = { &val[0], &val[1], &val[2] } The arr could be written (there arn't any anchors or references in this case): --- - !ptr =: 8 - !ptr =: 33 - !ptr =: 22 | We agree that we don't want to clutter up the YAML with | a lot of anchors, but we simply can't pre-judge whether | a given pointer is wasted. | | Also, I don't want to try to reflect the pointer-y-ness | of the data with plain YAML types, since, presumably | all we'd get is an extra node between each sequence | entry and the int leaf. Avoid clutter. Right, that is the approach above; the !ptr type. | I'd say the solution must be a user-type . . . at the | *sequence* node. There's nothing funny about the ints. | They're just plain ints. There's no need to design a | type for them. | | Use the native !int . . . but that means we can't | forbid the use of anchors on ints. It's the user's | call, part of his type design. | | Reiterating: We don't insert an extra node into the YAML | because it creates clutter, against goal 2. Instead, we | serialize the structure just the same as an array of ints, | only distinguishing it by a user-type, adorning some | (fairly high) branch node. In "C" goal #3 is not attainable and a "C" programmer must use the emitter directly. However, with a language that supports reflection, it could be detected that the items above are pointers, and thus an automated dump strategy could be developed. So, to be consistent with goal #3, the !ptr mechanism would be used since an automated dumper is possible without requiring user intervention. And further, #4 is supported since !ptr is documented in the specification and may be recognizable by other languages. | (The reason we put the type high up in the tree is the | same: Reduce clutter. The fewer times the type-declaration | appears, the better for readability. Also, it reflects | the user's view of the data better.) The type-declaration clutter reduction is probably best done with the ^ "cut&paste" operator. I think being more general (with each leaf properly tagged) is a more straight-forward option. | We trust the user to tell us when to put in anchors/aliases. | His advice is also needed about what string to use for the | anchor. | | --------------- | | #B This begs the question, how is the best format chosen? | | I guess the obvious choice is to get it from the user. Makes | it easy on us. Can we offer an alternative? Something that | would take some of the load off his shoulders? Yes. The current emitter that I'm working on uses some fairly simple heuristics to "guess" which style to use based on the content of the data. It works fairly OK now and will improve over time. At this time, the escaped block is the default... with single, double, and simple used when possible to fit within 76 columns. | I don't have any quick ideas . . . also, time's up for | tonight. Time to take a break. Thank you so much for the start; perhaps this is enough of a bootstrap and we can start a collaborative thread to mold the goals into place. ;) Clark |
From: Andrew K. <ku...@sf...> - 2002-05-17 04:21:08
|
On the topic of 'identity' of YAML nodes: As so often before, much of what you say sounds a little weird to me . . . Oren: > The question is, what is YAML's position on this. Are YAML nodes > identity-based (read: require aliases structure to be preserved), value > based (read: allow aliases structure to change), or what? It seems obvious to me that it's not up to YAML. If identity is allowed at all, it must be possible to make anything identical. It's the user's discretion. I think what you *must* be thinking of is the *automatic* dumper. You hand it an object and say 'Dump this' and it must figure out what to do on its own. To me, this seems obviously unreasonable in the case where the object *might* contain pointers for the purpose of creating identity. (Or, they *might* be there just to save storage for some string like "Value unknown".) I think you have to let the user decide how to treat pointers. It's true that you might offer him some policies that you think are likely to be useful most of the time, but you must allow him to choose anything at all in some cases. This is an implementation problem: giving the user his choice of policies. I can think of a model for type structures (must be something like the DTD) that the user can tag with 'identity' or 'value' for each pointer. This would be pretty general. Also, quite difficult to explain to folks . . . However, the point is not to put restrictions on YAML arising out of *implementation* problems in the dumper. ---------------------------------------------- How about this? Let's say that anything composed of purely basic data types *never* has pointers for creating identity. That's only found in user types. Then there is NO REASON to use the !ptr data type. The appearance of an alias implies the pointer. It does not appear explicitly in the dump. Also, the user knows where to expect pointers, since they're part of his user-type. If the automatic dumper runs across a pointer in a purely native structure, it simply dereferences it and forgets it ever saw it. How's that for a solution? (If the dumper encounters a circular chain of references, it simply dies. This is time-honored behavior. I have seen it in Lisp, Prolog, and one or two others.) ----------- Oren: > - We want to be able to write long scalars - specifically, strings (and > binary data) - only once in a YAML file. I don't think this is important enough to justify keeping aliases if my argument above persuades you. Thus, my vote is for Oren's #1: > 1. We just "keep it simple": > - Say that an alias means sharing, pure and simple; > - And give up on emitters being able to insert aliases for "equal scalars" > unless they are also "shared" in the graph model. > - This is elegant to specify and implement. > with the added understanding that aliases only appear inside user types. ------------- Brian: > YAML.pm currently does not preserve the relationship of scalars in the native > model, when dumping it. It does honor aliases between scalars when loading > them. This is the most useful, in the general case, IMVHO. (BTW, you *can > preserve the relationship using hard references which YAML.pm dumps as !ptr) > > However, soon I will offer the nondefault option to dump all scalars > with the same pointer value, as aliases of the first. That is, if an object contains 2 hard references to the same scalar, you will notice this and dump them as aliases. (presumably using a hash to hold all the pointers you've ever seen, so that you can find it) > > This seems like a great compromise. From Y->N we must honor the aliases. From > N->Y we should be *able* to preserve the relationships. But making it the > default is the wrong choice from where I stand. ^^^^^^^^^^^^ Why? This decision is important. What are the reasons? Anyhow, As I say above, I think it's necessary to give the user a finer choice than the proposals (Never sharing. Sharing on all except leaves. Always sharing.) Not that the proposals aren't good 99% of the time (if the user can choose between them), but *sometime* he'll need to make finer-grained choices. Andrew |
From: Clark C . E. <cc...@cl...> - 2002-05-17 17:56:39
|
| I think what you *must* be thinking of is the *automatic* | dumper. You hand it an object and say 'Dump this' and | it must figure out what to do on its own. This is the common use case; preserve the in-memory structure via a native->yaml->native round-trip. | I think you have to let the user decide how to treat | pointers. It's true that you might offer him some | policies that you think are likely to be useful most | of the time, but you must allow him to choose anything | at all in some cases. Python doesn't have pointers; it only has references. Perl has both, but pointers are rarely used and need only be preserved (thus the !ptr construct). "C" only has pointers. References are implemented with pointers, but this is done via specific conventions and/or intermediate code which implements pointer integrety, etc. This pointer/reference distinction is necessary since most of the languages we work with it make the distinction. References differ from pointers in that a reference is symmetric. Once you have a refernece to an object you have no more / no less rights than any other process which has a reference. This symmetry isn't true with pointers in general, pointers can be invalid; references by definition are never invalid. | This is an implementation problem: giving the user | his choice of policies. I can think of a model for | type structures (must be something like the DTD) | that the user can tag with 'identity' or 'value' | for each pointer. This would be pretty general. | Also, quite difficult to explain to folks . . . | However, the point is not to put restrictions | on YAML arising out of *implementation* problems | in the dumper. Yes; ideally the schema language (when it gets developed) will help keep all of this stuff clear. And yes, we don't want YAML itself to make the decision, although it should, IMHO still provide a reference mechanism. So, I was proposing a limitation on a few distinct scalar types (!int, !str, !float) and their behavior. There is nothing then stopping a user from defining an equivalent type with different behavior if the default behavior here isn't acceptable. | How about this? Let's say that anything composed | of purely basic data types *never* has pointers | for creating identity. That's only found in | user types. I didn't grok this. | Then there is NO REASON to use the !ptr data type. | The appearance of an alias implies the pointer. | It does not appear explicitly in the dump. Yes; but an alias(reference) is very different from a pointer. The following are equivalent... --- one: &001 value two: *001 --- two: &001 value one: *001 Which key "value" was provided via an anchor and which key is the alias is *not* informational. The distinction between the two structures above are identical in the generic model since alias position and key order need not be preserved (i.e. a process cannot count on these two items being preserved). However, by constrast, the following are different... --- one: &001 value two: !ptr =: *001 --- two: &001 value one: !ptr =: *001 Since in the first example, "two" is a pointer to "value", while "one" is "value". In this way you can think of everything as using reference semantics; and for YAML purposes a "shared" pointer is implemented through the reference mechanism. Of course, the first one above, is equivalent to... --- two: !ptr =: &001 value one: *001 | Also, the user knows where to expect pointers, | since they're part of his user-type. In Python you don't have pointers, one only has references. In Perl, the runtime knows where the pointers ("hard references") are, beacuse it has a different syntax and stores them differently. Java is like Python, no references. "C" and a few other languages have pointers, and in this case the user would have to write their own dumper/loader pair. | If the automatic dumper runs across a pointer in a | purely native structure, it simply | dereferences it and forgets it ever saw it. | | How's that for a solution? In Perl, this solution would prevent hard references (aka pointers) from being preserved; and thus I think it is a horrible idea. A "C" language, the loader/dumper is custom anyway, so it is up to the programmer. As for C++, I doubt that the C++ reflection is strong enough to do a proper "generic" save program. Thus, this is also up to the programmer. | (If the dumper encounters a circular chain of references, | it simply dies. This is time-honored behavior. I have | seen it in Lisp, Prolog, and one or two others.) This might be a good solution for C/C++ custom YAML loader/dumper. In general, cycles occur in data, I have cycles in many of my python structures and automatically saving them is what YAML is all about. Collections have pointers to their objects, and quite often objects refer back to their collections. | > - We want to be able to write long scalars - specifically, strings (and | > binary data) - only once in a YAML file. | | I don't think this is important enough to justify keeping | aliases if my argument above persuades you. XML's lack of graph support causes it no end of grief. Each application does it their own way, and thus making generic tools that operate at the graph level are impossible. The requirement of dealing with graphs is part of YAML's core and it is one of those assumptions that is not subject to review. | As I say above, I think it's necessary to give the user a finer | choice than the proposals (Never sharing. Sharing on all except | leaves. Always sharing.) My suggestion (and I'm still thinking about it...) isn't as broad a brush. It breaks down into two categories: (a) Atomic objects, such as int and float, are often stored using a value and not using an "object" with an identity. As such, in the graph model we specify that aliases applied directly to such atomic objects may be dropped. In the YAML specification we name only two such families which have atomic behavior -- !int and !float. (b) String objects, which are often treated as an "immutable" object and frequently not compared by address. For this case we specify that strings may be treated as atomic objects, and this may gain/loose aliases in the generic model. Thus, according to the generic model, the two documents below will be considered identical, since aliases are not significant: --- - 23 - &001 23 - *001 - Hello - &002 Hello - *002 --- - &001 23 - *001 - 23 - &002 Hello - *002 - Hello The advantages of this proposal a) Part a supports native int, float types in most languages without requiring that address (a group of objects sharing the same alias) need be maintained. Otherwise, to meet the requirement of the graph model; all !int and !float types with anchors would have to be wrapped. b) Supports readability by specifying that strings can be treated as atomic values. In most languages, the specific address of a string is rarely used. Thus, this is a good 95% compromise. We loose the string's identity, but gain greater readability. For python, the rules of identity for atomic objects are more or less random. Two integer objects of less than 999 will _always_ share the same memory address, but integer objects greater than 999 may be different depending on the parser state; it may not even be deterministic. Strings are somewhat similar, most of the time identity is preserved, although there may be cases where identity isn't preserved. | Not that the proposals aren't good 99% of the time (if the | user can choose between them), but *sometime* he'll need | to make finer-grained choices. This is what we are trying to do; the above "restrictions" on the base !float, !int, and !str type are probably what we want 99% of the time. And in the cases where you don't want this property... you can make your own custom types. --- Anyway. The alternative is to be "strict" by default, and allow a "dumper" option to "de-normalize" the graph by duplicating alias values, and allowing a "loader" to "normalize" strings by interning strings. This would give the greatest readability; but would not be the default. I suppose we could keep "strict" as the default option, but let the loader/dumper use a relaxed model as a non-default option... perhaps this is the principle of least suprises? Best, Clark |
From: Clark C . E. <cc...@cl...> - 2002-05-20 02:40:04
|
Thoughts on references and pointers. I think this just comes down to "implicit" vs "explicit". In the implicit case the programming system manages the pointers, in the explicit case, it is the programmer which has to do a "dereference". In YAML we must preserve both cases. For example, Perl has "three" types of references. The first type, soft references, seems to be the default, is implicit, and works exactly like Python's reference mechanism. With soft references, multiple links to an object can be made without the programmer worrying about the details or using any special operator. It is all done "automagically". Note that implicit isn't strictly true for Perl since the programmer must use $, @, %, or other type sigals. But these sigals are explained more as types and uses everywhere, so a novice user can be completely oblivious to the de-referencing aspect of these operators. I view view $x as similar in style to something like *((scalar_t*)x) and @x like *((array_t *)x) Since it is the "normal" way of doing things, let's call the perl soft references, "implicit". Perl also has hard references where you need an need to use an "address" operator (\) to get a pointer. You then use one of the sigals to dereference the address that is returned. This is the "explict" form since the user must remember that a given variable is a refernece to de-reference it. In my mind, $$x is somewhat like **((scalar_t **)x). Python does not have an explicit reference mechanism. Perls' third mechanism, "symbolic references" is just cute way to access members of the current namespace, which is an implicit hashtable. "C" on the other hand, uses an explict mechanism (&*) and lacks an implicit reference handling technique. While, for contrast, C++ seems to have a bit of both if you use classes with counted pointer wrappers. Also, I like to think of C++'s pass-by-reference function call type as more or less "implicit". Unfortunately, this distinction is very fuzzy. However, for YAML, the alias/anchor "reference" system is meant to model an native implicit reference system. The !ptr type family is meant to handle an underlying explicit system. Thus, a Perl serialization may use both alias/anchor and !ptr. If it were possible to have "reflection" for "C", then it would use primarly !ptr, with an alias as the value of the pointer for the first occurrance; luckly "C" doesn't have reflection so we don't have to worry about this and can leave it up to the application to decide. As for Python, it will only use the alias/anchor representation, but will use a list with one item to emulate !ptr constructs. From YAML's perspective, evertyhing is a link to another node, the first link in the serialization is labeled so that other links to the node can re-use the node's description. Since we must preserve the "explicit" hard references of Perl, we use the !ptr mechanism as well. The !ptr is a single-position container (emulated as a map with a single = key) which simply holds a node or an alias. In this way, the level of indirection which the user requires (to preserve the explicit nature) can be serialized and thus not break code since a perl person needs to distinguish between $x and $$x. Ok. I hope this helps to explain better. Some of this is new to me. The implicit vs explicit is a new insight for me and may not quite explain the situation well; but it is better than my previous understanding. Thank you Andrew for prodding me to come up with a better explanation... does this help? Clark |