From: Chris Dollin said:
There are only values, so if assignment assigns to something, it
must be a value.
A value cannot exist unless it exists somewhere. There's no such
thing as just existing without being anywhere. The location where a
value exists is not itself a value, it's a place, a.k.a. location.
Consequently, if there are values, then there are also places,
which are two distinct types.
At the abstract level there are also equivalance classes of values.
Given any equivalence relation on values, such as bitwise and
structural identity, there are equivalence classes of values modulo
that equivalence relation. For example, the 16-bit integer
1010010101010111 and the 16-bit integer 1010010101010111 are two
distinct objects, both structurally strings of ASCII characters in
binary notation, with identical bit patterns, hence in the same
equivalence class modulo such structural comparisons, but located
in two different places, at the left margin and about 2/3 of the
way toward the right margin. (And the copies of those
string-numbers that you see on your machine are two additional
instances of that equivalence class, on *your* machine instead of
mine, which are probably in locations miles apart from my two
instances.)
In a machine, when talking about assigning a value, it's a copy of
a value that is assigned, not the original value, hence we're using
the word "value" to mean an equivalence class rather than a single
instance. The precise definition of such an equivalence relation
would involved causality, that if a value is copied directly it's
the "same" (modulo the equivalence relation) whereas if an equal
value happens to arise independently it's not the "same" value even
though it's an "equal" value.
When you think of it, that's why you need runtime equality
checking, to determine, in the case of two values which are not
"same", nevertheless whether they happen to be "equal" anyway. If
you copied a value directly and then tested for equality,
long int x; long int y=42;
x = y;
if (x == y) ...equalaction...; else ...diffaction...;
the compiler could recognize the tautological nature of the
comparison and optimize it to be done at compile time and generate
a runtime constat TRUE value
long int x; long int y=42;
x = y;
if (TRUE) ...equalaction...; else ...diffaction...;
instead of a runtime comparison to compute a TRUE/FALSE value which
is always TRUE. In fact the optimizing compiler could eliminate the
test entirely and not even compile the unrerachable code.
long int x; long int y=42;
x = y;
...equalaction...; /* Unreachable code ...diffaction...; */
But if two values are generated independently, there's no way the
compiler can know whether they will be equal or not a runtime, so
an actual runtime comparison must be compiled.
long int x; long int y=42;
scanf("%d", x);
if (x == y) ...equalaction...; else ...diffaction...;
I don't. (But is has some of the same behaviours.)
I've recently decided that due to the C-biassed definition of
"pointer", then perhaps "effective address" is the correct jargon
to use. But on the other hand, so-called "lvalues" in C have a size
and intentional type as well as just a machine address, so maybe
"effective address" doesn't really capture the whole nature, so
maybe "effective datatyped pointer" or "datatyped effective
address" is more accurate jargon. The idea is you have three things
intertwined:
- The machine address of the first addressible unit of storage;
- The number of addressible units, or contrarywise the byte
position within a single addressible unit, such as 7-bit ASCII
byte within 36-bit addressible unit on PDP-10 or 16-bit UCS-16
character in Java 1.3 on a 32-bit or 64-bit computer;
- The meaning of that storage, whether 2's-complement integer, or
unsigned integer, or IEEE floating point value, or ASCII
character, or EBCDIC character, or IBM-PC character, or Latin-1
character, or UCS-16 character, or 32-bit Unicode character, or
UTF-8 byte which may be a full ASCII character or just part of
any other UniCode character, or a bitmask, or a bitmask
representing a subset of a particular universal set, or a
boolean where all but one bit are ignored, or a boolean where
all bits zero means FALSE and any other combination is treated
as if TRUE, etc.
That entity is either a loadtime constant (compiletime constant
subsequently adjusted i.e. offset by loader), such as for a static
variable, or a runtime-offset value, such as stack-allocated
variable which is compiletime constant offset from stack pointer,
or fully runtime computed value, such as array element or
dereferenced pointer. But whatever the nature of generating that
entity, what you end up with (just before you fetch the value there
or store a new value there or just pass the entity as a parameter
to some other function), the entity has those three properties the
same in any case, however it was generated. I'm leaning toward that
last jargon I invented "datatyped effective address" to clearly
show that it isn't necessarily a compiled constant (hence the
"effective"), that it has an address as part 1 (hence the
"address"), and that it has both size and meaning (hence the
"datatyped" which includes both concepts together, which is why
both sizeof(type) and static_cast (C++ jargon which clarifies
what C casting usually does) work).
This is not the time nor the place for a tutorial on the
fundamental concepts of programming languages. So I won't
give one. Do your own homework.
The purpose of asking for you to explain what precisely you mean is
that you have been ambiguous and I don't understand what you are
saying but if you explain precisely what you mean then perhaps I
will finally understand which of several possible interpretations
of your previous utterances is correct. Unless my homework involves
kidnapping you and using torture or sodium pentathol to force you
to reveal what you mean, there's no way my "homework" can resolve
the question as to your intended meaning. It's up to you to either
explain what you meant, as I requested, or refuse, and have me
dismiss everything you ever said that wasn't fully
clear/unambiguous. If you choose to post ambiguous garbage and then
refuse to clarify, i.e. if you refuse to communicate meaingfully,
that's your choice.
, happens when/where? In the compiler during
compilation? Or at runtime?? Or some other time?
It doesn't matter when, so long as the right [defined by
specification] answer is arrived at.
The so-called specification (C1999) is not a specification at all
because it's ambiguous, leaves some issues totally unresolved,
undefined, really basic things like "lvalue" which is used all over
the place. My choice at the moment is to dismiss those parts of the
so-called specification as garbage and simply ignore them and
substitute my own personal explanation of what happens with an
"assignment".
It's the store, the place where values live:
update( store, lvalue, rvalue )
delivers a new store which is the same as `store` except the
value at `lvalue` is now `rvalue`. Perfectly straightforward
denotational semantics.
Ah, thanks for explaining the notation you were using in that
example. Such is actually implementable using a BST to
emulate/implement the store, where for efficiency (both space and
time) the BST-store object you get back shares all structure with
the original BST-store object except for the log(n) path down to
the single location which was updated between the old and new
BST-store objects. (I've actually written code like that, in lisp
of course. Someday maybe I'll try the same functionality in
PHP/perl... I wonder if there's any relational-database system that
supports such functional-language-style updates.)
lvalues are identifiers for places to put things
I really like that way of putting it!! I'm going to pounce on you
and not let you retract that! Like I've been saying all along, an
<cJargon>lvalue</cJargon> is very much like a
<lispJargon>place</lispJargon>, and the lisp jargon is less
confusing, and I'm pleased you've essentially adopted the lisp
jargon "place" here. As for the distinction between the identifer
for a place and the place itself, that's too nitpicky to be worth
distinguishing in most of this discussion. Technically an lvalue is
the place itself, not the identifier of that place, but that's too
nitpicky a distinction here.
I don't know how you'd know that.
Because what they say *about* lisp is so grossly wrong that nobody
who knew beans about lisp beyond the look of the syntax would ever
say such a thing about lisp. It's like if somebody told you that C
can't be used for anything except device drivers and operating
systems, you'd know the person didn't have any clear idea of C,
right? Way back in the days of MacLisp, timing tests showed that
lisp used for numerical calculations was slightly faster than
fortran, generating identical machine instructions within each
individual function, but having more efficient linkage when one
function calls another. Yet people who knew next to nothing about
lisp still claimed, to this day, that lisp is horrendously slow,
totally unsuitable for tight calculations, a totally false claim
from know-nothings. Likewise people are still saying that lisp is
suitable only for A.I., not for day-to-day data-processing
applications, again totally false from know-nothings.
Even the fact that people use c for applications that would be much
more appropriate in lisp show widespread lack of understanding of
lisp. Like why would somebody spend a month writing a new one-time
application in c, and never getting it fully working, when it would
take three or four days in lisp and be fully working from the
get-go, except if they really didn't understand lisp.
I haven't dismissed /any/ Lisp jargon.
Yes. I misunderstood you previously.
I'm interested in understanding how that is possible, but it's
getting seriously off-topic here. Would you be willing to explain
it to me privately, or in an appropriate newsgroup?
Yes: by calling that function's updater. The expression
V -> F(X) is compiled as (F.updater)(V, X)
There's only one rewrite rule, not one per "setf method".
OK, so F isn't a function, it's an <oop>object</oop>, where the
getvalue method and the update method are both contained in the
object, and so presumably getting a value also involves a rewrite
rule
F(X) -> V (F.getvalue)(X) -> V
I presume, much the same as java obviously has a rewrite rule for
automatically calling the getString method whenever some object
appears in a string-needing context, such as parameter to
system.out.Println, or operand to the operator that does string
concatenation, or the way C++ has a boolean-needing context such as
inside an 'if' statement which causes a boolean-producing method
instead of the default value-producing method, which is why
if(!cin) tests for EOF on stdin instead of null pointer (and why
if(cin) tests for not-EOF instead of non-null pointer).
The rewrite is built in to the compiler. I would say "not user
redefinable", but Pop11's pretty open; given enough effort you
can pretty much redefine whatever you like. More to the point,
you don't change any rewrite rules and you don't create any
macros when you define Pop11's updaters.
Well, I would simply say it's a single built-in (vendor-supplied)
macro so utterly powerful that no additional macros are needed by
ordinary Pop11 users so they don't miss the ability to define more
macros of their own. In essence it's the same as a macro, or
contrarywise a macro is really a rewrite rule by a different name.
Since the word "macro" has another entirely different meaning in
common language, namely "large", I'm thinking now that "rewrite
rule" is the better, more clear, jargon. Thanks for the enlightenment.
</OT>