Newbie initial comments on D language - scope
Michel Fortin
michel.fortin at michelf.com
Mon Feb 4 05:21:47 PST 2008
On 2008-02-03 10:42:03 -0500, Edward Diener
<eddielee_no_spam_here at tropicsoft.com> said:
> Michel Fortin wrote:
>> On 2008-02-03 08:20:32 -0500, Edward Diener
>> <eddielee_no_spam_here at tropicsoft.com> said:
>>
>>> I am fully cognizant of a dynamically typed language since I program in
>>> Python also. I agree there is no fixed dividing line. But the
>>> difference between static typing and dynamic typing is well defined in
>>> a statically typed language like D. My argument was that for 'scope' to
>>> be really effective it needs to consider the dynamic type at run-time
>>> and not just the static type as it exist at compile time.
>>
>> Considering the dynamic type at runtime means you need to check if
>> you're dealing with a reference-counted object each time you copy a
>> reference to that object to see if it the reference count needs
>> adjusting. This is significant overhead over the "just copy the
>> pointer" thing you can do in a GC. Basically, just checking this will
>> increase by two or three times the time it take to copy an object
>> reference... I can see why Walter doesn't want that.
>
> I am not knowledgable about the actual low-level difference between the
> compiler statically checking the type of an object or dynamically
> checking the type of an object, and the run-time costs involved.
>
> Yet clearly D already has to implement code when scopes come to an end
> in order to destroy stack-based objects, since structs ( user-define
> value types ) are already supported and can have destructors.
Yes, and this is implemented in a simple and naive way: by adding an
explicit call to the destructor at the end of the scope. The scope
object cannot exist outside the scope, and thus no reference counting
is needed in the way it's implemented currently.
> So the added overhead goes from having to identify structs which must
> have their destructor called at the end of each scope to having to also
> identify 'scope' objects which must have their reference count
> decremented at the end of each scope and have their destructor called
> if the reference count reaches 0.
Well, identifying structs can be done at compile time since you know
exactly the type of the struct at that time. Classes are polymorphic,
so it'd be a costly runtime check to know that, and that check is
almost as costly as doing the reference counting itself. Given that,
you should probably not bother at runtime and decide at compile time to
just treat any class which has the potential to be a scope class as if
it were one and actually do the reference counting.
>
>
>>
>> Beside, the overhead of actually checking the type of the class will be
>> approximativly the same as doing the reference counting. Given this,
>> it's much better to always just do the reference counting than checking
>> dynamically if it's needed.
>>
>>
>>> class C { ... }
>>> scope class D : C { ... }
>>>
>>> [...]
>>>
>>> This may make things much easier for the compiler, but it requires the
>>> end user knowledge of 'scope', which has been specified at the class
>>> level, to be applied at the syntax level. Intuitively I feel the
>>> compiler can figure this out, and that 'scope' should largely be
>>> totally transparent to the end user above at the syntax level.
>>
>> Well, if the compiler is to be able to distinguish scope at compile
>> time, then it needs a scope flag (either explicit or implicit) on each
>> variable. This is exactly what Walter has proposed to do. He prefers
>> the explicit route because going implicit isn't going to work in too
>> many cases. For instance, let's have a function that returns a C:
>>
>> C makeOne() {
>> if (/* random stuff here */)
>> return new C;
>> else
>> return new D;
>> }
>>
>> Now let's call the function:
>>
>> C c = makeOne();
>>
>> How can you know at compile time if the returned object of that
>> function call is scoped or not? You can't, and therfore the compiler
>> would need to add code to check if the returned object is scope or not,
>> with a significant overhead, each time you assign a C.
>>
>> If however you make scope known at compile time:
>>
>> scope C makeOne() {
>> if (/* random stuff here */)
>> return new C;
>> else
>> return new D;
>> }
>>
>> scope C c = makeOne();
>>
>> Now the compiler knows it must generate reference counting code for the
>> following assignment, and any subsequent assignment of this type, and
>> it won't have to generate code to dynamically everywhere you use a C
>> check the "scopeness".
>
> Would you agree that all you are doing here is specifically telling the
> compiler that an object is 'scope' when it is created rather than
> having the compiler figure it out for itself by querying the dynamic
> type of the object at creation time ?
The compiler isn't knowleadgeable of what happens whithin every
function call. So it can only check at runtime if the function returned
at C or a D.
> If you do, then a much simpler, and to the point, example would be
> based on my initial OP:
>
> scope class C { ... }
>
> scope C c = new C(...);
>
> I specified that the scope keyword for creating the object is
> redundant. The compiler can figure it out. The major difference in
> opinion is that I think the compiler should figure it out from the
> dynamic type of the object at run-time and not from the static type of
> the object.
You're prefectly right: it is redundent in *this* case, and you could
have the compiler implicitly understand that C is a scope class in
*this* case. But consider this example:
Object o;
if (/* random value */)
o = new C; // c is a scope class
else
o = new Object; // Object is the base class of C but isn't scope
Now, should o be automatically reference-counted because you *could*
later create a C object and assing it to o, or should line 3 gives an
error since the type Object isn't scope and C must only be assigned as
scope? I'd say it should be an error.
This however could be made legal without too much difficulty:
scope Object o;
if (/* random value */)
o = new C; // c is a scope class
else
o = new Object; // Object is the base class of C but isn't scope
Basically, you're declaring a scope Object. While Object isn't
necessarly a scope class, you are telling the compiler to treat it as
scope, and thus an instance of C, which must be scope, *can* be put in
this variable. If o wasn't scope, it'd be an error to put an instance
of a scope class in it.
But there are still many holes in this scheme in which scope now means
reference-counted. Take this example:
class A {
void doSomething() {
globalReferences ~= this;
}
}
scope class B { }
A[] globalReferences;
scope B b = new B; // Scope could be made implicit here, but it's
irrelevant to my example
b.doSomething();
This last statement would call A.doSomething which would put a
non-scoped reference to globalReferences, which would fail to retain
the object. There are two ways around that: ignore the problem and let
the programmer handle these cases (basically, that is what
boost::shared_ptr would do in such a situation), or introduce a new
keyword to decorate parameters for functions that do not keep any
reference beyound their own call so that you don't need to duplicate
all your functions for a scope and non-scope parameter (much like const
is the middle ground between mutable and invariant).
(Sidenote: this keyword could be useful to implement something like
"unique" as it was discussed in another thread, as it'd allow functions
to be called with a unique parameter and guarenty that no external
references are kept after the call, thus perserving uniqueness.)
--
Michel Fortin
michel.fortin at michelf.com
http://michelf.com/
More information about the Digitalmars-d
mailing list