Latest type checks

View: New views
20 Messages — Rating Filter:   Alert me  
< Prev | 1 - 2 - 3 | Next >

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Consider:

  class A {
    constant x = -1;
    string f() {
      return ([1: "foo"])[x] || "bar"; // Warning: Indexing on illegal type. Got int(-1..-1)
    }
  }

  class B {
    inherit A;
    constant x = 1;
  }

  int main() {
    werror ("%O\n", B()->f());
  }

This gives a warning on the indicated line. I guess the assumption is
that x is constant there, but that's not necessarily true as the
example shows.

I guess the problem here again is that constants lack explicit types.
If it was possible to type it then one would normally write "constant
int x = -1;" and the situation wouldn't occur.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Isn't the problem rather that the constant isn't constant?  I'd expect
a (non-abstract) constant to actually be constant, and thus nomask.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Both constants are constant, but the expression x isn't constant since
it resolves to different constants in different classes. That's just
the result of overloading and is a very useful feature. You can make
your constants explicitly nomask or local if you want to, or you can
resolve them statically using local::x to get a constant expression.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

>Both constants are constant, but the expression x isn't constant since
>it resolves to different constants in different classes.

Then the problem is that x doesn't bind statically.  If A::x is
constant, and the use of "x" is statically bound, it should be
statically bound to the constant value -1.  Otherwise we have some
other kind of binding for constants.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

I'm not sure I understand exactly what you mean with 'the use of "x"
is statically bound'.

x is looked up using the standard rules, and the result happens to be
a constant (in A or B, depending on the class instance). That doesn't
mean that the expression x itself is constant. It would be truly odd
if the result of the lookup would affect the lookup process itself.
The current behavior in that regard is compatible, highly useful and
imo quite natural.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

>I'm not sure I understand exactly what you mean with 'the use of "x"
>is statically bound'.

I mean that "x" in the expression '([1: "foo"])[x] || "bar"' is
associated with a declared object (preferably 'constant x = -1;')
during compile time.  If the association to a declared object is done
at runtime, then it is not static binding.

If, for example, I write

int foo()
{
  int y=17;
  return y;
}

then the use of "y" is statically bound to the declaration "int
y=17;".  It can not later be persuaded to suddenly refer to some
different "y", of a different type (or value).

Is this a "truly odd" case of a "result of the lookup process
affecting the lookup process itself"?  Because the lookup process
during compilation yielded a local variable, a stating binding was
used.  I don't see why this could not be the case with constants.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Ok, then the expression "x" isn't statically bound in my example.

The situation is different for a local variable inside a function
body. If the lookup process for an identifier resolves such a
variable, then it will always resolve to the same variable, regardless
of the (static or dynamic) context of the function body. Thus the
identifier expression is indeed statically bound in that case.

That is no longer (necessarily) true when the lookup process starts to
search for declarations in a class scope, because class declarations
can be overloaded. If it were somehow possible to inherit function
bodies too and overload specific parts inside them then it wouldn't be
true for local variables either.

I think it's right that the "constant" keyword controls read-onlyness
without affecting binding.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

>If the lookup process for an identifier resolves such a
>variable, then it will always resolve to the same variable, regardless
>of the (static or dynamic) context of the function body. Thus the
>identifier expression is indeed statically bound in that case.

Exactly.  And that's how my intuition thinks a "constant" should
work as well.

>I think it's right that the "constant" keyword controls read-onlyness
>without affecting binding.

"constant" isn't just a modifier, it's its own type of entity.  If it
had been "constant int x=7;" then I agree that it should only differ
from "int x=7;" in read-onlyness.  But since it is different from an
instance variable, I don't see any problem with it having different
binding from an instance variable, just like a local variable has a
different binding from an instance variable.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

By the way, it appears that constant _already_ have different binding
rules than instance variables.  Consider this code:

class foo {
  constant x=3;
  int f() { return x; }
}

class bar {
  inherit foo;
  constant x=7;
  array(int) g() { return ({ foo::x, bar::x, x, f() }); }
}

Here bar()->g() will return ({ 3, 7, 7, 7 }).  However, change the
"constant" to "int", and you'll instead get ({ 7, 7, 7, 7 }).  So a
constant is clearly already different in more whats than read-
onlyness.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

I think being able to inline values during compilation is rather
useful, actually.  I don't mind having a "read-only property" _as
well_ though.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

While your reasoning is logically flawless, Martin's is IMO a much
more useful behaviour. And making constant a property on normal typed
variables (changing their variability to constantness instead), would
be even better still.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

What you're seeing there is a peculiar result of how variable
overloading works: When a variable (without "local" modifier) is
overloaded, both the overloaded and the overloading definition refer
to the same storage.

Immutable definitions, i.e. both functions(*) and constants, don't
behave that way; they retain their original values (quite naturally,
since they wouldn't be immutable otherwise).

This behavior of variable overloading is not self-evident. Long ago
overloaded variables actually didn't share storage - each class got
its own and the normal binding rules were applied to the identifier.
Then the variable variety of your example would behave exactly as the
constant case.

That was changed, if I recall correctly, mostly on pragmatic grounds:
The only reason to overload a variable is either to modify its type or
(more commonly) to override the initializer. In those cases, retaining
the storage for the overloaded variable would just cause confusion
(and hence bugs) and waste space in the objects.

Changing the type of an overloaded variable shouldn't really be
allowed from a strict typing perspective either (the overloading type
should only allowed to be more restrictive to work when the variable
is queried, and may only be more lax when the variable is set).

So the overloading paradigm doesn't really work that well with
variables in several perspectives. Strictly speaking it shouldn't be
allowed, but it is useful to override initializers.

These problems does not apply to immutable defintions though, to which
both constants and functions belong.

*)  One can replace the constant x with a function returning the value
    in your example - the function behaves in the same way as the
    constant.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

You ignore the main point of my argument, namely that the binding
being static for a local variable simply is a result of the fact that
there can't ever be any alternatives in that case. Or put another way,
the question whether the binding is static or not is moot for local
variables; one can just as well regard it as non-static and arrive at
exactly the same result. In the case of class declarations that's
clearly no longer true.

As for the looks of the constant construct, I don't think the
syntactic difference is really relevant as to whether it should be
orthogonal wrt to binding or not. But anyway:

I my view "constant" is a modifier. Then the type is left out
altogether from a constant declaration. I believe the reason for that
has been that it can be inferred from the value. That approach is
however not without problems since the type can get overly narrow (my
original example in this thread shows one problematic case, but there
are others). Due to those problems, it's just a matter of time before
constant declarations will allow explicit typing.

Getters & setters

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Interestingly, it looks like getters and setters have inherited the
variable behavior when it comes to overloading:

  class A {
    int `x() {return 3;}
  }

  class B {
    inherit A;
    int `x() {return 7;}
    int f() {return A::x;}
  }

Here B()->f() prints 7, i.e. B::`x is called even when A::x is
queried. Is that correct? Spontaneously I'm inclined to regard a
getter only as syntactic sugar for the corresponding function syntax:

  class A {
    int get_x() {return 3;}
  }

  class B {
    inherit A;
    int get_x() {return 7;}
    int f() {return A::get_x();}
  }

which correctly prints 3.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

How come

 >  class A {
 >    constant x = -1;
 >  }
 >
 >  class B {
 >    inherit A;
 >    constant x = 1;
 >  }

doesn't give a warning, but

   class A {
     int(-1..-1) x = -1;
   }
 
   class B {
     inherit A;
     int(1..1) x = 1;
   }

doesn't even compile?

I'm sure there's a good explanation, but it's probably related to this
exact problem.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

>You ignore the main point of my argument, namely that the binding
>being static for a local variable simply is a result of the fact that
>there can't ever be any alternatives in that case.

You say that it "can't" be any alternatives, but what you mean is that
we have constucted the language such that no alternatives base on
dynamic environment are considered.  In the same way, we can make
binding of constants not consider alternatives based on the dynamic
environment.

To show what I mean, consider:

class X {
  constant a=1;
  int foo() { int b=7; return a+b; }
}

class Y {
  inherit X;
  constant a=2;
  int foo() { int b=14; return ::foo(); }
}

The variable "int b=14" _could_ be an alternative to "int b=7", when
X::foo() is invoked in an object of class Y (dynamic context).  But it
isn't, because we don't want it to be.  In the same way, whether
"constant a=2" is an alternative to "constant a=1" is purely a matter
of choice.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

For that to become an issue we'd have to introduce function body
inheritance first. Indeed there is choice, but the choice is - to
begin with - whether to introduce that concept or not (my vote on that
matter is no).

For the class level, inheritance and overriding is already a fact, and
there are observable effects whether or not "constant" should imply
static binding. I think it should not: 1) It'd unnecessarily break the
orthogonality principle, 2) it'd introduce a spurious difference wrt
binding of function identifiers, 3) we'd have to introduce a new
"nolocal" modifier, and 4) it'd massively break compatibility.

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

How do other languages handle the same situation? Maybe they even
prohibit redefining a const-flagged member variable in a subclass?

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

>For that to become an issue we'd have to introduce function body
>inheritance first. Indeed there is choice, but the choice is - to
>begin with - whether to introduce that concept or not (my vote on that
>matter is no).

And mine as well.  And for the same reason that I don't want that (it
is unintuitive), I don't want constants that work like they do now.
Overriding of constants is also a concept that we had to introduce
(after all, we had to introduce the concept of constants in the first
place)...

But it appears that this behaviour has been implemented for a long
time, so changing it would indeed be a compatibility problem, I can
buy that argument.

As for the "nolocal" modifier, maybe this would be cool to have
anyway?  You could apply it to function scope variables to give them
object lifetime.  (And twice to achieve what I scetched in 16636824?
:)

Latest type checks

by Johan Sundström (Achtung Liebe!) @ Pike (-) developers forum :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Java (approximating "constant" with "final"):
  Works like I want, the constant is statically bound.  Defining a new
  constant with the same name (and a different type!) in a subclass is
  allowed, but does not alter the behaviour of inherited methods.
  The behaviour is the same regarless of wheteher I declare the
  constant as "static" or not.

C++ (approximating "constant" with "static const"):
  Like Java.  Here the "static" is needed though, because I'm not
  allowed to give an initializer otherwise.
< Prev | 1 - 2 - 3 | Next >