gracelang / language

Design of the Grace language and its libraries
GNU General Public License v2.0
6 stars 1 forks source link

Can var x and method x:=(_) coexist? #143

Open apblack opened 6 years ago

apblack commented 6 years ago

Can var x and method x:=(_) coexist? Minigrace allows this:

var x := 5
method x:=(new) { print "assignment to x blocked" }
x := 3
print "x = {x}"

It seems to me that the same identifier should not be permitted in a var declaration and in an assignment method declaration in the same scope, and that the above program should not be allowed.

Does the situation change if the var x is inherited rather than declared directly? Does the method x:=(new) then override the writer for x, but leave the reader still available?

kjx commented 6 years ago

It seems to me that the same identifier should not be permitted in a var declaration and in an assignment method declaration in the same scope, and that the above program should not be allowed.

yes I agree. In terms of rules for names and scopes, the rules for def d should be the same as for method d is confidential and for var v be the same as for method v is confidential and method v:=(_) is confidential.

Does the situation change if the var x is inherited rather than declared directly?

It shouldn't. The def/var vs method equivalence should always hold, and rules for clashes, redeclaration, shadowing should flow from that.

Does the method x:=(new) then override the writer for x, but leave the reader still available?

Yes it would. This is part of the reason why initialisation is not done by calling the setter. I'm not sure there's much point to doing that overriding - although I guess you could alias the setter to some other name so it's still around for those who know. Excluding the setter would be better style.

apblack commented 6 years ago

This is part of the reason why initialisation is not done by calling the setter.

Do we say that anywhere? This means that

var x := 7

is not equivalent to

var x
x := 7

Of course, I understand your point. In the presence of

method x:=(new) { print "assignment to x blocked" }

it's impossible to initialize x if var initialization is equivalent to assignment. But that should not be surprising; if one overrides the writer method for x to do nothing, then perhaps one wants it to be impossible to initialize x?

If one wants a constant and an eponymous writer method that does nothing, then one can get that effect with a def.

On the other side of the argument, def initialization is clearly not equivalent to assignment.

kjx commented 6 years ago

On the other side of the argument, def initialization is clearly not equivalent to assignment.

Right, and I think def and var initialisation should be the same. For example, what should this do:

class sup {
    var x := 3 
}

class sub { 
   inherit sup
      alias y = x
      alias y:=(_) = x:=(_)
      exclude x
      exclude x:=(_)
}

(neither amg not kernan seem to do the Right Thing here, as far as I can tell; both say the setter exclude clause is erroneous. But I don't see why it is).

If x is initialised by a setter, this should break — but that seems weird. If x is initialised 'like a def` then this is fine.

kjx commented 6 years ago

if one overrides the writer method for x to do nothing, then perhaps one wants it to be impossible to initialize x?

So if you want something like that, exclude the variable without aliasing, and replace it with a def or methods or whatever. The question is: if you do that, does it break the superclass in a way you can't fix? The problem with def style initialisation is that you can declare a bunch of object fields that are inaccessible in subclasses, but you still have have to run the initialisation code (if any), even if you could subsequently throw the results away because those object fields are now dead.

kjx commented 6 years ago

My preferred semantics lead to a distinction between initialisation and assignment.

That's a semantic reason to write var x = 4 rather than var x := 4; field initialisation is denoted by =, variable assignment is denoted by :=; these things are different for the reasons we're discussing, so they should look different too.

In this model, var x = 3 declares and initialises x and does not call the setter, while var x; x := 3 declares x, doesn't initialise it, but then assigns by calling the setter. If you exclude x:=(_) the first will still work, the second would not.

apblack commented 6 years ago

I see (I think). Your reason for wanting var initialization to be different from var assignment is that if they are the same, an inheritor can break the superobject.

But an inheritor can always break a superobject. What's special about this particular kind of breaking that we should outlaw it?

Of course, if we adopt C++ semantics and run the initialization code before doing the overridings, then this problem goes away. (Then the inheritor can still break any other aspect of the superobject, but not its initialization.)

apblack commented 6 years ago

(neither amg not kernan seem to do the Right Thing here, as far as I can tell; both say the setter exclude clause is erroneous. But I don't see why it is).

We have never been clear on whether the equivalence between var x and the pair of methods exists only to clients, or also inside the object with field x. Minigrace takes the position that the equivalence is external, and inside an object treats them differently. For example, as Kim's recent problems with thisDialect expose, it doesn't actually compile the reader and writer method if the field is confidential and there can be no inheritors.

This doesn't, of course, mean that minigrace is right (it's usually not), just that the Spec is not clear on this point (as far as I'm aware). We should make it so.

kjx commented 6 years ago

I see (I think). Your reason for wanting var initialization to be different from var assignment is that if they are the same, an inheritor can break the superobject.

partly. I also think initialisation and assignment are different concepts (this is the argument you raised against aligning request syntax and encapsulation semantics :-). In particular, both defs and vars can be initialised, but only vars can be assigned. The other model is that only defs can be initialised, vars can only be assigned, and the var x := 3 really is just a shorthand for var x; x := 3.

What's special about this particular kind of breaking that we should outlaw it?

It's not really breaking the superobject. It's that if we initialise vars via their setters then we can never really exclude the setter: doing that is always, everywhere, a mistake, even if there are other aliases out there. Consider this:

class top {
   var x := 3 
}
class bot {
   inherit top 
      alias y = x
      alias z = x 
      exclude x 
      alias y:=(_) = x:=(_)
      alias z:=(_) = x:=(_) 
      exclude x:=(_) 
      method yy:=(v) { print "YY"; y:=v }
      method zz:=(v) { print "ZZ"; z:=v }
}

this works fine under my semantics, bot has two pseudo-vars y and z that both write to the same field. Duh: although (and perhaps this is what you meant) its works fine until top tries to access x -- because our aliasing is shallow the code will still try to call x or x:=(_) which isn't there any more.

The other argument is pragmatically, that supporting these semantics costs very little, because we already need all the machinery to initialise the defs at the right time in the right context. My version of initialisation semantics just does exactly the same for vars.

Hmm: so I guess another option is that we could consider thatdefs are also initialised by setter methods, but those setter methods are somehow them magically excluded or cannot be called by any user code. In other words: are vars extensions of defs or defs extensions of vars? Initialisation semantics say to me that defs are more basic, and thatvars are the same as defs but additionally support assignment. Assignment semantics say to me that vars are more basic, and defs are just vars that somehow lose their assignment method. I do prefer the first description!

KimBruce commented 6 years ago

I think this is a BAD idea. I really want to think of var x := 4

as being equivalent to var x x := 4

I can’t think of any reasonable way of explaining to novices why they should be different. I’d rather have some slightly surprising behavior in inheritance than have to explain the difference.

On Jan 9, 2018, at 12:08 PM, kjx notifications@github.com wrote:

My preferred semantics lead to a distinction between initialisation and assignment.

That's a semantic reason to write var x = 4 rather than var x := 4; field initialisation is denoted by =, variable assignment is denoted by :=; these things are different for the reasons we're discussing, so they should look different too.

In this model, 'var x = 3declares and initialisesxand does not call the setter, whilevar x; x := 3declaresx`, doesn't initialise it, but then assigns by calling the setter. If you exclude x:=(_) the first will still work, the second would not.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/gracelang/language/issues/143#issuecomment-356399170, or mute the thread https://github.com/notifications/unsubscribe-auth/ABuh-lXEtwQpLXj4hGtTJLgwESyuojVVks5tI8cogaJpZM4RXN6F.

KimBruce commented 6 years ago

Though I haven’t really thought through the static typing implications of alias and exclude, my gut feeling would be to block exclude unless there is something to replace the declaration/definition.

This kind of example is just too crazy to distort the language over!

Kim

On Jan 11, 2018, at 5:17 PM, kjx notifications@github.com wrote:

I see (I think). Your reason for wanting var initialization to be different from var assignment is that if they are the same, an inheritor can break the superobject.

partly. I also think initialisation and assignment are different concepts (this is the argument you raised against aligning request syntax and encapsulation semantics :-). In particular, both defs and vars can be initialised, but only vars can be assigned. The other model is that only defs can be initialised, vars can only be assigned, and the var x := 3 really is just a shorthand for var x; x := 3.

What's special about this particular kind of breaking that we should outlaw it?

It's not really breaking the superobject. It's that if we initialise vars via their setters then we can never really exclude the setter: doing that is always, everywhere, a mistake, even if there are other aliases out there. Consider this:

class top { var x := 3 } class bot { inherit top alias y = x alias z = x exclude x alias y:=() = x:=() alias z:=() = x:=() exclude x:=() method yy:=(v) { print "YY"; y:=v } method zz:=(v) { print "ZZ"; z:=v } } this works fine under my semantics, bot has two pseudo-vars y and z that both write to the same field. Duh: although (and perhaps this is what you meant) its works fine until top tries to access x -- because our aliasing is shallow the code will still try to call x or x:=() which isn't there any more.

The other argument is pragmatically, that supporting these semantics costs very little, because we already need all the machinery to initialise the defs at the right time in the right context. My version of initialisation semantics just does exactly the same for vars.

Hmm: so I guess another option is that we could consider thatdefs are also initialised by setter methods, but those setter methods are somehow them magically excluded or cannot be called by any user code. In other words: are vars extensions of defs or defs extensions of vars? Initialisation semantics say to me that defs are more basic, and thatvars are the same as defs but additionally support assignment. Assignment semantics say to me that vars are more basic, and defs are just vars that somehow lose their assignment method. I do prefer the first description!

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/gracelang/language/issues/143#issuecomment-357114513, or mute the thread https://github.com/notifications/unsubscribe-auth/ABuh-niwlvDAtXdAGpQ3bJvwLfeepLMFks5tJrKwgaJpZM4RXN6F.

kjx commented 6 years ago

I can’t think of any reasonable way of explaining to novices why they should be different.

and I can't think of any reasonable way of explaining to novices why def x = 3 and var x := 3 should be different. Of course, I'd go along with this by writing var x = 3 but that's neither here nor there.

kjx commented 6 years ago

We have never been clear on whether the equivalence between 'var x' and the pair of methods exists only to clients, or also inside the object with field 'x'.

really? Why wouldn't it? Otherwise how could you replace just one or other of the methods?

Frankly I'd follow Self & Newspeak and go one step further: even locals are considered as accessed and set by requests. IF we had nested methods, then you could replace a local with a pair of methods too.

apblack commented 6 years ago

Otherwise how could you replace just one or other of the methods?

But isn't that exactly what you want to do — override assignments to x, but somehow keep x as a variable?

I don't see why you want to do this. You can get the same effect by declaring a def x or a method x in the subobject.

Here is what I think the real problem is: we can't override initialization. In contrast, in Smalltalk, initialization is just a method, so it can be overriden, just like any other method. In Grace, initialization is very special, and can't be overridden. So, if the initialization code contains a v := 5, there has to be a v:=(_) method: inheritors can't exclude it, no matter what they would like. It can be a no-op, of course, and it can be confidential, but it must be there. Does this matter?

The Beta/Simula philosophy, of course, has no problems with restricting what inheritors can do. My preferred solution to inheritance—traits without fields—avoids this problem too. Careful design can avoid it, by putting all initialization of vars into a method, and requesting that method from the initializer of the object constructor—that way, it can be overridden. But it's certainly true that making var v := expr equivalent to var v; v := expr does impose some restrictions on what the inheritor can do.

I also think initialisation and assignment are different concepts

I agree with this. It was the point of departure for much of my early writing on inheritance, and explains why doing inheritance with immutable objects was so much harder than I had expected. Kent Beck talks about this to, in his Smalltalk Best Practice Patterns book: it explains the "once" part of the "once and only once" rule. (The first "once" says that every important concept of the domain should be captured in the code once; the "only once" part says that it should not be repeated. Hence, "once and only once" is much more than DRY.) So, Kent argues that there should be an initialization method for a field, and a writer method for a field, and they should be different, because they are different concepts.

Of course, in Smalltalk, this is a Programing Pattern, not a language design issue.

The question that remains for me is, do we need initialization for variables at all, given that we have assignment?

KimBruce commented 6 years ago

On Jan 11, 2018, at 9:13 PM, Andrew Black notifications@github.com wrote:

I also think initialisation and assignment are different concepts

I agree with this. It was the point of departure for much of my early writing on inheritance, and explains why doing inheritance with immutable objects was so much harder than I had expected. Ken Beck talks about this to, in his Smalltalk Best Practice Patterns book: it explains the "once" part of the "once and only once" rule. (The first "once" says that every important concept of the domain should be captured in the code once; the "only once" part says that it should not be repeated. Hence, "once and only once" is much more than DRY.) So, Kent argues that there should be an initialization method for a field, and a writer method for a field, and they should be different, because they are different concepts.

Of course, in Smalltalk, this is a Programing Pattern, not a language design issue.

The question that remains for me is, do we need initialization for variables at all, given that we have assignment?

In my view, def x = obj has the semantic effect of identifying the identifier x with the value of x On the other hand, var x has the semantic effect of identifying the identifier x with a location supplied by the runtime (or it could be something more abstract).

Both associate an identifier with a value of some sort. var x := obj then is just an abbreviation for var x; x:= obj

The assignment doesn’t affect the meaning of the identifier x, but it does affect the derived method x that returns the new value.

I do think it is simplest to think of both def and var declarations in objects (i.e., not local variables in methods) as implicitly defining methods that can be overridden in subclasses (and I believe that we had agreed on that). The language spec doesn’t make it clear whether a def or declaration can override a method, though the reverse seems to be supported. If I had my druthers, I’d be very conservative and allow methods to override methods, defs, and vars, but only methods can override features. I.e., a def may not override a method. This is not such a restriction as it is trivial to code around this by replacing the def that is supposed to override by a method returning that value. Similarly for variables. I’m all for keeping the language as simple as possible as long as there are not great hits in expressiveness.

Whatever we decide, we should clarify the spec.

Kim

You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/gracelang/language/issues/143#issuecomment-357145091, or mute the thread https://github.com/notifications/unsubscribe-auth/ABuh-hJRM4r4UPLb3VfIPdOSd2lw2FHpks5tJunmgaJpZM4RXN6F.

kjx commented 6 years ago

But isn't that exactly what you want to do — override assignments to x, but somehow keep x as a variable?

I want to override assignments to x but I don't see why this should itself break initialisation (unless something else is going on as well). The initialisation code is supposed to run anyway, even if the variable is dead.

So, if the initialization code contains a v := 5, there has to be a v:=(_) method

Why? def d = 5 doesn't need a d:=(_) method. Why can't vars be initialised just like defs?

Careful design can avoid it, by putting all initialization of vars into a method

But again, what about initialisation of defs? I can see two ways to allow initialisation to be overridden. Either use individual hook methods for each var or def:

class super(a1,a2) {
  def d = initialiseD(a1)
  var v := initialiseV(a2)

  method initialiseD(a1) required
  method initialiseV(a2) required
}

class sub(a1,a2,a3) {
   inherit super(a1,a2) 
   method initialiseD(a1) { ... } 
   method initialiseB(a2) { ...} 

Or, somewhat more radically, try not to care about the superclass variables: redeclare them in subclasses and let the VM sort it out. The initialisation code for superclasses will run, but hopefully that won't to do much damage...

class super(a1,a2) {
  def d = ...
  var v := ...
}

class sub(a1,a2,a3) {
  inherit super(a1,a2) 
  def d = ...
  var v := ...
}

If a var or def is (completely) overridden or excluded, then it is dead and space need not be allocated for it (but that's purely an optimisation). Yes the superclass initialisation code will run, but if that's pure (actually in this case, read-only, but that's a topic or another day) then that code too can be omitted if the variable is dead.

I'm being careful here once again to show the symmetry betweenvars and defs. This is important to me and I still don't see why we should adopt (or stick with) a design that breaks it.

kjx commented 6 years ago

The question that remains for me is, do we need initialization for variables at all, given that we have assignment?

If you're serious about that then adopt it 100%. Don't allow var v := 4 at all, just var v; v := 4 and be done with it.

kjx commented 6 years ago

In my view, def x = obj has the semantic effect of identifying the identifier x with the value of x On the other hand, var x has the semantic effect of identifying the identifier x with a location supplied by the runtime (or it could be something more abstract).

Right, well sure - but that's not really what the syntax says. I have wondered about removing vars as such, and then doing e.g.

def counter : Variable[Number] = var[Number](32)

print (counter.value)
counter.set(counter.get + counter.get ) 
print (counter.value)

(or if you like, could be e.g. counter := ^counter + ^counter) where := is now just an infix operator defined on variables, and ^ a prefix op that reads 'em.) This would have to work for locals too, of course. I believe this design is pretty much BLISS.

I don't think we want to go that far. I do think def d = x associates the name 'd' with the value of 'x'; and I think 'var v = x' does exactly the same thing. Both need "storage" after all. Both can be initialised when they are created (defs must be; vars should be); both can be read subsequently; both initialisation and reading work in exactly the same way for both defs and vars. I think that vars can then do one more thing than defs: assignment changes the association of the value with the variable that is assigned.

(Hmm: I feel like I'm repeating myself. I'm sorry - I blame the Pinot Noir :-)

kjx commented 6 years ago

I do think it is simplest to think of both def and var declarations in objects ...

yes, we'd agreed on that. See comments about about locals: we can use the same rationale there if we want to.

If I had my druthers, I’d be very conservative and allow methods to override methods, defs, and vars, but only methods can override features. .... I’m all for keeping the language as simple as possible as long as there are not great hits in expressiveness.

So this is what I mean about us ending up arguing about the definition of simplicity. In some ways, yes, I can see a language with those restrictions is simpler than one without those restrictions. In other important ways, though, I think those restrictions layer complexity on top of an essentially simpler language without those restrictions. The restricted language may be simpler to implement (at least for some kinds of implementations) or implement efficiently, and the restrictions may prevent certain classes of bugs or misbehaviour. On the other hand, the less restricted, more general language can be shorter to describe at least: just describe the general case, omit the restrictions because there aren't any! This is a key tradeoff to me.

In Grace, we've yet another option: a general less restricted core language, and then restrictions provided in dialects. I'm not sure whether this is the best of all worlds (simple orthogonal core, but pitfalls are closed in dialects) or the worst (you need a fully generally implementation just in case, and either large parts of are not used (because of the restrictions) or the restrictions aren't used and people end up coding only in the most general language.

I think a language is simpler if anything can override anything, even defs overriding defs (or defs overriding methods, which frankly could get very weird very fast) but the semantics are clear and straightforward to me (especially with initialisation semantics). You think that promotes unnecessary complexity and want to rule it out. And this tension seems to arise in lots of different parts of the language design.

As for me, I'd like something with the conceptual clarity of Pascal, Self, ML and Haskell, rather than the compromises of BASIC, Java, JavaScript, or C++. To me that means a small, clear, orthogonal, unrestricted core.

KimBruce commented 6 years ago

Is an "is overriding” annotation available for defs and vars? In my view, the semantics I propose is easier. Defs and vars implicitly define methods, and only (explicitly defined) methods can override methods. Otherwise we get tangled up in trying to figure out what it means for a def to override a var (when almost certainly its a mistake — and if you really wanted that you could define it in an explicit method)

Kim

On Jan 12, 2018, at 3:27 AM, kjx notifications@github.com wrote:

I do think it is simplest to think of both def and var declarations in objects ...

yes, we'd agreed on that. See comments about about locals: we can use the same rationale there if we want to.

If I had my druthers, I’d be very conservative and allow methods to override methods, defs, and vars, but only methods can override features. .... I’m all for keeping the language as simple as possible as long as there are not great hits in expressiveness.

So this is what I mean about us ending up arguing about the definition of simplicity. In some ways, yes, I can see a language with those restrictions is simpler than one without those restrictions. In other important ways, though, I think those restrictions layer complexity on top of an essentially simpler language without those restrictions. The restricted language may be simpler to implement (at least for some kinds of implementations) or implement efficiently, and the restrictions may prevent certain classes of bugs or misbehaviour. On the other hand, the less restricted, more general language can be shorter to describe at least: just describe the general case, omit the restrictions because there aren't any! This is a key tradeoff to me.

In Grace, we've yet another option: a general less restricted core language, and then restrictions provided in dialects. I'm not sure whether this is the best of all weords (simple orthogonal core, but pitfalls are closed in dialects) or the worst (you need a fully generally implementation just in case, and either large parts of are not used (because of the restrictions) or the restrictions aren't used and people end up coding only in the most general language.

I think a language is simpler if anything can override anything, even defs overriding defs (or defs overriding methods, which frankly could get very weird very fast) but the semantics are clear and straightforward to me (especially with initialisation semantics). You think that promotes unnecessary complexity and want to rule it out. And this tension seems to arise in lots of different parts of the language design.

As for me, I'd like something with the conceptual clarity of Pascal, Self, ML and Haskell, rather than the compromises of BASIC, Java, JavaScript, or C++. To me that means a small, clear, orthogonal, unrestricted core.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/gracelang/language/issues/143#issuecomment-357214729, or mute the thread https://github.com/notifications/unsubscribe-auth/ABuh-tLgoq5ijFjuJfBus8nIZen4CA_Iks5tJ0G8gaJpZM4RXN6F.

kjx commented 6 years ago

Is an "is overriding” annotation available for defs and vars?

it should be, although (ha!) once again vars are a problem because you can't say just one leg of the var overrides. Doesn't matter: the current semantics of "overriding" says it's not required; it just raises an error if things don't override*

Otherwise we get tangled up in trying to figure out what it means for a def to override a var

under initialisation semantics, it's odd, sure, but not tangled:

class sup {
   var v = valueOf { print "init sup v";  42 } 
}
class sub {
   inherit sup 
       alias sup'v = v  //this line is optional but explains why things aren't necessarily daft.
   def v = valueOf { print "init sub v";  21 } 
}

The subclass sees a 'v' method that accesses it's 'def' so will always be 21. The subclass inherits a 'v:=(_)' method that sets the sup'v, but that value can't be set without the alias. (With the alias, the subclass gets a sup'v method to get the value). In initialisation semantics, both blocks will run, and have their side effects.

*another decision to make writing untyped and unannotated code more straightforward - although a stricter rule could be in a dialect - so why isn't is overriding in a dialect other than we don't know how to define it...)

kjx commented 6 years ago

This works fine with defs. I even thing it's the right thing:

class sup {
  def x = "some {string} with computation"
}
class sub {
  inherit sup
     alias sup`x = x
 def x = sup`x ++ "some suffix" 
} 

Now do the parallel thing with vars:

class sup {
  var x := "some {string} with computation"
}
class sub {
  inherit sup
     alias sup'x`x
     alias sup'x:=(_) = x:=(_)
 var x = sup`x ++ "some suffix" 
} 

Under "initialisation is not assignment" semantics this does the same as the def version - i.e. what I expect. Each variable is initialised with the value set by its initialiser. Under "initialisation is assignment" semantics this will assign to the subclass's 'x' field twice, and the sup'x field visible in the subclass will be uninitialised.

apblack commented 6 years ago

@KimBruce writes:

If I had my druthers, I’d be very conservative and allow methods to override methods, defs, and vars, but only methods can override features. I.e., a def may not override a method.

We had agreed on that several years ago, but then removed the restriction. This happened after I started coding the collections library. It's natural to define an abstract method size at the top of the hierarchy, but then to override size with a readable variable in those classes that record the size explicitly, and with a method in those classes that calculate size.

Of course, it is possible to work around the restriction by defining a variable numberOfElements and having a size method return it. But it's not the simplest thing, so why would we want to force programmers to do it? You first argue that we should treat vars exactly like a pair of methods: if you believe that, why make the exception?

IIRC, the original reason for the restriction was that the initialization in the super-object might access the overriding var before it is initialized. But this is always the case, and leads to a clean uninitialized variable exception.

apblack commented 6 years ago

There are two kinds of simplicity—simplicity of language definition and simplicity of programs written in the language. They very often conflict, which is why we have languages like FJ which have a simple semantics, but in which no one would seriously want to program.

We could make Grace much simpler by getting rid of inheritance altogether. This would make many of these issues moot. But would it make programs simpler? No, it would not. (However, if I can quit spending time wading through arguments here and get back to coding, I would like to try the experiment of reusing only traits, and not classes. I think that a few programs would get a little longer, but probably easier to understand.)

Another language simplification (which also would eliminate this whole issue!), is to get rid of fields entirely. Constant fields (defs) can be replaced by methods that return the value — once methods if you are concerned about the cost of evaluating the constant. Variable fields (vars in objects) can be replaced by temporary variables in the class, method, or other context that contains the object constructor, plus two methods that read and write that variable if it is required to be public or accessible to a re-user. Of course, this would make programs a bit more cumbersome to write, but it would simplify the language semantics.

We are never going to agree on which of these kinds of simplicity matters most until two things happen. First, we need to agree on our goals. @KimBruce and @apblack are interested in teaching, so simplicity of student programs is more important to them. @kjx is more interested in semantics, so simplicity of semantics is more important to him.

Second, we all need to write more programs, especially more large programs. @apblack is the only person to have tried writing serious production code in Grace, because he's the only person who have made a commitment to working on Grace code of any size (the minigrace compiler). The experience of actually using Grace has caused him to reverse my position several times. From a maintenance and productivity standpoint, a self-hosting compiler is probably a bad idea, but from the point of view of getting experience with writing Grace code, it has been wonderful.

@kjx made the interesting suggestion that we could avoid having to make some of these decisions by defining a semantically-simple core language and then adding restrictions or features using dialects. We could take this a little further by doing what Eelco and his group have done, and define a core language and "lowerings" that map the teaching language into it. This is more than we can currently do with dialects, but would enable us to remove not just classes but also fields from the core. We could get fields back by adding a lowering that moves the field declaration into the surrounding context, and also declares the appropriate reader method for a def, and reader and writer method for a var. Such lowerings would allow us to define a whole family of semantics for initialization, and for each of us to pick our own — surely a fun research project!

For example, @kjx could "lower" var x := exp into var x := exp in the surrounding context, giving OCaml-like semantics where exp is interpreted outside of the object. Or he could choose to lower it into var x in the surrounding context, followed by outer x := exp in the object initialization, giving the "initialization is not assignment" semantics. Or, he could lower it into var x in the surrounding context, followed by the definition of method x:=(nu) { outer x := nu } in the object and x := exp in the object initialization, giving the "initialization is assignment" semantics. What fun! A programmer could even use different semantics in different modules!

Not much help in writing understandable programs, though.

kjx commented 6 years ago

t's natural to define an abstract method size at the top of the hierarchy, but then to override size with a readable variable

One could argue a required/abstract method isn't a "method" for the purpose of Kim's rule - arguably its the absence of a method. (#137, #144). So you could have a rule that said only methods can override (concrete) methods, because supplying a required method is just filling in a hole, not overriding.

kjx commented 6 years ago

There are two kinds of simplicity—simplicity of language definition and simplicity of programs written in the language

Well yes, but there is probably more than that - notably the cases I seem to be arguing about: making a language more orthogonal at the cost of a more complex implementation vs hardwiring exceptions that simplify implementations. None of these cases seem to really make much difference to the complexity of the code.

Another language simplification ... is to get rid of fields entirely. .. it would simplify the language semantics.

Would it? I'm not so sure. I don't think it simplifies lookup, and it doesn't simplify variables because you still have to have variables in lexical scope! (explicit boxing would simplify things, and would make every imperative program worse).

We could take this a little further by doing what Eelco and his group have done, and define a core language and "lowerings" that map the teaching language into it.

I'm explicitly not proposing this. I think we want one common language semantics. I do like the way types layer gradually on top of those semantics, and I'm interested in which other "restrictive" features can be handled in a similar way.

kjx commented 6 years ago

A question of terminology: the spec says attribute but doesn't define what an attribute is. Perhaps a def or var or method declaration doesn't make a method in an object: rather it makes an attribute - or for var, two attributes.

apblack commented 6 years ago

The resolution of this issue:

Can var x and method x:=(_) coexist ?

seems to be "no".

A work item coming from this discussion is to make the language in the spec more uniform.

To get the answer "no" to this issue, these changes imply that an inheritor gets the outside view, so that declaring a method x:=(_) overrides an inherited attribute x:=(_), regardless of whether that attribute was implemented by a var x or method x:=(_)

kjx commented 6 years ago

To get the answer "no" to this issue, these changes imply that an inheritor gets the outside view

not sure I agree with this - the phrasing at least. Inheritors get access to confidential methods, and that seems inside/internal, whereas external clients don't. Other than that, I don't see there is an inside/outside distinction: our only computational act is a request, a request for some attribute.

I don't think this is resolved because it leaves open the question of initialisation vs assignment of vars; I continue to maintain the initialisation of defs and vars should be the same, so vars cannot be initialised by a method request because defs aren't initialised by a method request.

apblack commented 6 years ago

This issue is "Can var x and method x:=(_) coexist". The answer to that is "no". We are agreed!

There are several meters of discussion here on a separate issue: whether vars are initialized like defs, or whether vars are initialized using assignment. I really don't see a compelling argument either way, or, rather, I see compelling arguments both ways.

Two things seem clear, though.

  1. The language should do what the syntax says. So, if we want var initialization to be like def initialization, we should write var initialization with =. If we want var initialization to be like assignment, we should (continue to) write it with :=

  2. This issue is separate from the one that I originally raised with the question "Can var x and method x:=(_) coexist". It should have its own issue page. Perhaps it would be useful to re-cast this discussion as a green paper for the Grace workshop?