Scoping versus Dereferencing

7 views
Skip to first unread message

Michael Muller

unread,
Aug 12, 2014, 10:25:57 AM8/12/14
to Crack Language Dev

Crack has a problem in the lack of distinction between scoping and
dereferencing. Scoping is used to reference a symbol that is part of an
external namespace, dereferencing is used to access fields or methods on an
object.

Scoping example:

class A {
class B {}
}

# Access class B which is scoped to A.
b := A.B();

Dereferencing example:

class A {
void f() {}
}

# Apply method f() to an instance of A.
A().f();

The first example is notable in that it doesn't work: in fact, there's
currently no way to access inner classes except through the typeof() operator.
The only places where we can really currentlly do scoping is when invoking
static methods and when explicitly accessing a member for a base class:

class A {
void f() {
cout `in A.f()\n`;
}
}

class B {
void f() {
cout `in B.f()\n`;
A.f(); # Call A's f() method directly.
}

@static g() {}
}

B.g(); # Invoke a static method.

There is a semantic distinction between the two uses: scoping is simply
accessing a definition within a namespace, dereferencing is accessing a
definition for a specific target object. In the case of virtual methods, the
definition can vary depending on the runtime value of the target object.

Our approach of using the dot-operator for both scoping and dereferencing was
motivated by languages like Java and Python, which do not make a distinction
between scoping and dereferencing. However, in both of these languages there
are mitigating factors: in java, namespace constructs like packages and
classes cannot be deferenced, so the dot notation unambiguously refers to
scoping when used with these entities. In python, scoping is implemented via
dereferencing, so there really is no distinction.

But in Crack, there is some degree of ambiguity between the two forms because
classes are both namespaces and objects. Classes have attributes (e.g. "name"
and "bases") and methods (e.g. "isSubclass()"). So we get weird behavior like
this:

import crack.io cout;
class A {
@static bool isSubclass(Class other) { return false; }
}
cout `$(A.isSubclass(Object))\n`; # Prints "false"
Class a = A;
cout `$(a.isSubclass(Object))\n`; # Prints "true"

The first 'cout' calls the static isSubclass(), the second calls the
isSubclass() method on the class.

As stated earlier, one of the main use-cases for class scoping is explicitly
identifying a base class variation of a virtual function. But this only works
in a method defining the "this" variable, and can only be applied to the value
of that variable. There is no direct way to do this on any other value.
To do so, the user needs to explicitly provide a method to expose the base
class method:

class A {
void f(A other) {}
}

class B : A {
void __A_f() {
return A.f();
}

void f(A other) {
other.__A_f();
}
}

Beyond all of this, as suggested earlier, the implementation of scoping in
Crack is fundamentally broken. The dot operator doesn't have any magic to
support scoping for classes, the only reason scoping works at all is that we
copy method definitions to meta-classes. This works for some subset of
features, but not universally.

There are several ways we could fix all of this.

1) Just fix scoping. Make the dot operator first do a scoping lookup when
applied to a class, then a do a referencing lookup on failure. Nested
class resolution would work, users could disambiguate between scoping and
dereferencing a class by enclosing the class in parenthesis: (A).f() (Not
currently supported.)

There would still be no way to specify an explicit base class variation of
a virtual function, we'd have to sacrifice that.

2) Provide an explicit scoping operator '::'. Use the scoping operator
exclusively for scoping, the dot operator exclusively for dereferencing.

This is probably easier to implement than #1. It also has the advantage
of very clear semantics and it fixes all of the problems I've identified.
Explicit base class variations of virtual functions would be accessed as
follows: other.A::f().

The disadvantage is that users need to fully understand the difference
between the two operators. It also creates dissonance with the way that
imports are done: to be fully consistent, we would have to specify
module names as scoping operator separated: import foo::bar baz;

Finally, this approach breaks backwards compatibility. Some of our code
would have to be rewritten.

3) The compromise. Fix scoping as in #1, and _allow_ (but do not require)
the use of the scoping operator to allow users to force scoping in cases
where it would otherwise be inaccessible.

This fixes all of the problems and is backwards compatible, but it's less
straightforward than either of the other options. Users must be aware
that there are edge cases where the scoping operator is necessary, and
they have the option to use the dereference operator in cases where it is
inappropriate.

I'm currently leaning toward option #1. If anyone can cite a compelling case
for any of the other options, please let me know.


=============================================================================
michaelMuller = mmu...@enduden.com | http://www.mindhog.net/~mmuller
-----------------------------------------------------------------------------
There is no way to find the best design except to try out as many designs as
possible and discard the failures. - Freeman Dyson
=============================================================================

Conrad

unread,
Aug 12, 2014, 7:52:37 PM8/12/14
to crack-l...@googlegroups.com
Hi Michael,

Since 'it may be hard to implement' is not an impediment in Crack, #1 sounds like the best option :-)

Conrad

Arno Rehn

unread,
Aug 13, 2014, 8:52:34 AM8/13/14
to crack-l...@googlegroups.com
Hi,

On 12/08/14 16:23, Michael Muller wrote:
> There are several ways we could fix all of this.
>
> 1) Just fix scoping. Make the dot operator first do a scoping lookup when
> applied to a class, then a do a referencing lookup on failure. Nested
> class resolution would work, users could disambiguate between scoping and
> dereferencing a class by enclosing the class in parenthesis: (A).f() (Not
> currently supported.)
>
> There would still be no way to specify an explicit base class variation of
> a virtual function, we'd have to sacrifice that.
I'd go with this as well. However, enclosing the class in paranthesis to
change behaviour doesn't look like such a good idea to me. I always
think of parantheses as either part of function calls or changing the
precedence of operator expressions.
But enclosing classes in parantheses to switch between
scoping/dereferencing feels just unintuitive for me..

> 2) Provide an explicit scoping operator '::'. Use the scoping operator
> exclusively for scoping, the dot operator exclusively for dereferencing.
>
> This is probably easier to implement than #1. It also has the advantage
> of very clear semantics and it fixes all of the problems I've identified.
> Explicit base class variations of virtual functions would be accessed as
> follows: other.A::f().
>
> The disadvantage is that users need to fully understand the difference
> between the two operators. It also creates dissonance with the way that
> imports are done: to be fully consistent, we would have to specify
> module names as scoping operator separated: import foo::bar baz;
>
> Finally, this approach breaks backwards compatibility. Some of our code
> would have to be rewritten.
>
> 3) The compromise. Fix scoping as in #1, and _allow_ (but do not require)
> the use of the scoping operator to allow users to force scoping in cases
> where it would otherwise be inaccessible.
>
> This fixes all of the problems and is backwards compatible, but it's less
> straightforward than either of the other options. Users must be aware
> that there are edge cases where the scoping operator is necessary, and
> they have the option to use the dereference operator in cases where it is
> inappropriate.


--
Arno Rehn

Michael Muller

unread,
Aug 13, 2014, 9:31:03 AM8/13/14
to Conrad, crack-l...@googlegroups.com
Ha, thank you for the vote of confidence ;-). I'm assuming that you prefer
#1, can you elaborate on why?

>
> Conrad
>
> --
> You received this message because you are subscribed to the Google Groups "crack-lang-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to crack-lang-de...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>


=============================================================================
michaelMuller = mmu...@enduden.com | http://www.mindhog.net/~mmuller
-----------------------------------------------------------------------------
Lokah Samasta Sukhino Bhavantu - May all beings everywhere be happy and free.
And may my own thoughts and actions contribute to that happiness and freedom.
=============================================================================

Michael Muller

unread,
Aug 13, 2014, 10:19:23 AM8/13/14
to Arno Rehn, crack-l...@googlegroups.com

Arno Rehn wrote:
> Hi,
>
> On 12/08/14 16:23, Michael Muller wrote:
> > There are several ways we could fix all of this.
> >
> > 1) Just fix scoping. Make the dot operator first do a scoping lookup when
> > applied to a class, then a do a referencing lookup on failure. Nested
> > class resolution would work, users could disambiguate between scoping and
> > dereferencing a class by enclosing the class in parenthesis: (A).f() (Not
> > currently supported.)
> >
> > There would still be no way to specify an explicit base class variation of
> > a virtual function, we'd have to sacrifice that.
> I'd go with this as well. However, enclosing the class in paranthesis to
> change behaviour doesn't look like such a good idea to me. I always
> think of parantheses as either part of function calls or changing the
> precedence of operator expressions.
> But enclosing classes in parantheses to switch between
> scoping/dereferencing feels just unintuitive for me..

Yeah, I'm not wild about this either.

I have to say, the more I think about it the more I like option 3. If
anything, it seems like the dot operator should default to dereferencing,
which is the opposite of what the parenthesized notation implies. If we
introduced the scoping operator, A.f() would first try to dereference the
class object and then fall through to scoping, A::f() would unambiguously
scope to the class. Users don't usuaully encounter the need for this, so
those with less in depth knowledge can happily use the dot operator for most
of their work.

>
> > 2) Provide an explicit scoping operator '::'. Use the scoping operator
> > exclusively for scoping, the dot operator exclusively for dereferencing.
> >
> > This is probably easier to implement than #1. It also has the advantage
> > of very clear semantics and it fixes all of the problems I've identified.
> > Explicit base class variations of virtual functions would be accessed as
> > follows: other.A::f().
> >
> > The disadvantage is that users need to fully understand the difference
> > between the two operators. It also creates dissonance with the way that
> > imports are done: to be fully consistent, we would have to specify
> > module names as scoping operator separated: import foo::bar baz;
> >
> > Finally, this approach breaks backwards compatibility. Some of our code
> > would have to be rewritten.
> >
> > 3) The compromise. Fix scoping as in #1, and _allow_ (but do not require)
> > the use of the scoping operator to allow users to force scoping in cases
> > where it would otherwise be inaccessible.
> >
> > This fixes all of the problems and is backwards compatible, but it's less
> > straightforward than either of the other options. Users must be aware
> > that there are edge cases where the scoping operator is necessary, and
> > they have the option to use the dereference operator in cases where it is
> > inappropriate.
>
>
> --
> Arno Rehn
>
> --
> You received this message because you are subscribed to the Google Groups "crack-lang-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to crack-lang-de...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>


=============================================================================
michaelMuller = mmu...@enduden.com | http://www.mindhog.net/~mmuller
-----------------------------------------------------------------------------
Government, like dress, is the badge of lost innocence; the palaces of kings
are built on the ruins of the bowers of paradise. - Thomas Paine
=============================================================================

Conrad Steenberg

unread,
Aug 13, 2014, 12:30:24 PM8/13/14
to Crack Language Dev

On Wed, 2014-08-13 at 09:28 -0400, Michael Muller wrote:
> Conrad wrote:
> > Hi Michael,
> >
> > Since 'it may be hard to implement' is not an impediment in Crack, #1
> > sounds like the best option :-)
>
> Ha, thank you for the vote of confidence ;-). I'm assuming that you prefer
> #1, can you elaborate on why?
>

I work with Ruby every day which uses the A::b notation and it feels
inconsistent to me: having the class name is enough to denote scoping in
most cases. So it's a stylistic argument :-)

Of course having an optional explicit scoping operator like in #3
doesn't do any harm for the cases where there's no other way to make the
syntax unambiguous.

> -----------------------------------------------------------------------------
> Lokah Samasta Sukhino Bhavantu - May all beings everywhere be happy and free.
> And may my own thoughts and actions contribute to that happiness and freedom.
> =============================================================================

I like that!
Conrad

Reply all
Reply to author
Forward
0 new messages