trygve: breakout.k

48 views
Skip to first unread message

Egon Elbre

unread,
Apr 26, 2016, 8:13:04 AM4/26/16
to object-composition

I spent yesterday playing around with trygve environment and implemented a small breakout clone:




Criticism is welcome :)

Notes/issues while implementing:
* Often I wanted to use the same role name as an already existing class/context. E.g. it would be clearer if State is consistently used throughout to make clear that they are related.
* In some cases it's hard to verbalize what should a thing be - a role or a stageprop. e.g. Physics system modifies the State, but so may other systems e.g. Rendering could modify the animation progress. So not sure what to call it.
* Some roles ended up being namespaces rather than roles e.g. role Resolve. It's convenient to group such functions, but at the same time access a "Debug" display.
* There are places where I had to add some "superfluous" methods.
* While writing watchrun was helpful, e.g. while editing Sublime I had "watchrun -d examples bin/trygve examples/breakout.k" running, so I wouldn't have to manually press "parse" to check for issues.
* Error messages can be very cryptic (e.g. when you miss a parenthesis).
* It's quite hard to iterate quickly on code; I'm not sure what's taking the majority of compilation time.

PS: I wrote my code without proper use-case analysis.

+ Egon

Trygve Reenskaug

unread,
Apr 26, 2016, 9:14:54 AM4/26/16
to object-co...@googlegroups.com
Nice. What about two independent balls with different colors? Nice problem if they hit each other in mid air.
--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.
Visit this group at https://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

--

The essence of object orientation is that objects collaborate  to achieve a goal.
Trygve Reenskaug      
mailto: try...@ifi.uio.no
Morgedalsvn. 5A       
http://folk.uio.no/trygver/
N-0378 Oslo             
http://fullOO.info
Norway                     Tel: (+47) 22 49 57 27

Egon Elbre

unread,
Apr 26, 2016, 9:43:05 AM4/26/16
to object-composition
On Tuesday, 26 April 2016 16:14:54 UTC+3, trygve wrote:
Nice. What about two independent balls with different colors?

It would require changing/adding ~13 lines.

I would be interested if someone else would try to do that, since it would test how readable and manageable the code is. (spoiler of what to change, if you just want to try)

BTW. I initially started the collision system with ~10 balls flying around, although at the moment there are stability issues when things are significantly overlapped.

+ Egon

James O Coplien

unread,
Apr 26, 2016, 11:58:02 AM4/26/16
to object-co...@googlegroups.com
This is without a doubt the most significant trygve program to date. It exercises most of the features, shows that we can have reasonable real-time performance for experiments, and is intriguing enough to get people engaged and maybe interested in playing with the code.

The code is included in the latest GitHub distribution. It should come up by default if you build and launch the graphical environment.

As for error messages: The parser was constructed so that it would produce proper results for proper input, and at least avoid crashing in the presence of invalid constructs. A general overall strategy to deal better with syntax errors (that violate the grammar) would be a major undertaking and at this point it’s not a high priority in supporting its research function. I have been working to reduce error stumbling — which, by the way, has had the temporary effect of introducing a few more crashes. If you encounter an error message in closed form which you feel could be more clearly presented, please let me know or file a GitHub issue.

Antlr is notorious for being a slow parser, and the trygve environment makes it worse with a four-pass compiler. I think that to fix this properly is not a matter of speeding things up but of introducing differential compilation — and that’s a different environment and platform than this one.

It would be good to do a use case analysis of the game and to be able to argue why this is a class and that is a Context. Trygve is doing something analogous with the original pong.k code right now.

Anyhow, today is a great day with our largest trygve program to date — and one that is fun and worthy of good design exploration at that! Many thanks, Egon!)

— Cope


Den 26/04/2016 kl. 14.13 skrev Egon Elbre <egon...@gmail.com>:

I spent yesterday playing around with trygve environment and implemented a small breakout clone:

….

Matthew Browne

unread,
Apr 28, 2016, 7:54:00 AM4/28/16
to object-co...@googlegroups.com

Thanks for this great example Egon! I've only skimmed the code so far, but one thing I noticed is that you have 3 contexts that have no roles: Display, State, Breakout. Could you explain the reason for that? Is it because there are no role methods? The idea of a Context with no roles seems a little strange to me, especially in a language that also provides classes.

Egon Elbre

unread,
Apr 28, 2016, 8:21:02 AM4/28/16
to object-composition
On Thursday, 28 April 2016 14:54:00 UTC+3, Matthew Browne wrote:

Thanks for this great example Egon! I've only skimmed the code so far, but one thing I noticed is that you have 3 contexts that have no roles: Display, State, Breakout. Could you explain the reason for that? Is it because there are no role methods?


The main reason for that is that the "Display" is composed of several interacting pieces, but the pieces that play the roles are very concrete, i.e. 

	private Panel panel_;
	private Frame frame_;
	private Mouse mouse_;

Ideally I would have made them into roles, but the "requires" section would have been to implement a specific type. Similarly for State and Breakout. (also for World)

The semantics of "you cannot call directly a role player method" and not being able to define a type as a requirement, made it difficult to make them into proper roles, so I opted for a "private member" instead.

	role Display requires Display;
	role World requires World;

Similarly I would have wanted to directly call the role-player methods e.g. instead of

	role Handler {
		public void collide(Entity A, Entity B){
			onCollision(A, B);
		}
	} requires {
		public void onCollision(Entity A, Entity B);
	}

write:

	role Handler requires CollisionHandler

Note: I'm not necessarily saying what I wanted to do was a good idea, but rather what seemed appropriate at the time and they may have significant problems. And, I may have missed some of the features that trygve has.

The idea of a Context with no roles seems a little strange to me, especially in a language that also provides classes.


In this code, I used classes as "data records"/"structs" and have single purpose, and the context as where things interact and have more complex logic.

+ Egon

James O Coplien

unread,
Apr 28, 2016, 9:45:41 AM4/28/16
to object-co...@googlegroups.com
Keen eye, Egon. This issue recurs, again and again. I myself have grown tired of having to create forwarding functions in public Role interfaces that serve no purpose other than to expose a method of the Role-player. You can indeed call these methods directly but the compiler barks at you with a (non-fatal) warning. I argue against being able to call them directly and would like to make the warning fatal. Trygve argues eloquently the other way. I’m thinking that we’ll need a more indirect approach that involves explicitly adding a new feature, as the language currently handles only one dimension of a two-dimensional problem.

Here’s the rationale I just mailed to Trygve when he encountered the same issue earlier this week:

Den 26/04/2016 kl. 15.06 skrev Trygve Reenskaug <try...@ifi.uio.no>:

The diagnostic
line 138: NONCOMPLIANT: Trying to enact object script `setBackground(Color)' without using the interface of the Role it is playing: `ARENA'.
The roleScript Ball::draw() says on line 138 
    ARENA.setBackground(Color.blue);

A role is the name of an object. We used to say that a role is the object and understands the hundreds of messages understood by it.  In Trygve, this large interface is filtered by the requires clause. I like this innovation because it gives me control over my use of the object.  The interface called required is the subset of the object's interface that is visible through the role. This is an abstraction interface; the correctness of its implementation has to be taken on trust. The role scripts define what you have called the role interface. Its members are compactions of scripts visible within the context. A role understands the combination of the two. This combined interface is what the programmer sees. Yesterday, I would have called it the role role's interface, but this term can't mean both the local and the combined interfaces. So I think the NONCOMPLAINT is superfluous since `setBackground(Color)'  is in the interface explicitly defined in the requires clause.

I know, but it’s good to hear that this is your dominant perspective.

What’s the difference between the requires clause and your proposed using clause?

The requires clause gives control over what parts of the object interface are available to the Role. The Role interface gives control over what parts of the object (the Role methods) are available to other Roles. The using clause is an alias that selectively clones parts of the former for inclusion in the latter.

I’ve found it a great inconvenience to work around this NONCOMPLIANT message: it leads to bad renaming of things that shouldn’t have to be renamed merely for the sake of aliasing. The using directive solves that. It also shows that the programmer has made a conscious decision to expose parts of the object interface to other Roles. Right now there is no way to distinguish between those two cases (making an instance method available to the Role it is playing versus making it visible to other Roles in the Context).

I like to be able to reason about the use case through the Role logic alone. If instance methods return then it’s easy to believe in that trust boundary between the object and the Role. But if a Role’s requires list means that the instance method is published to the entire Context it takes away my ability to reason about the Role / instance interface locally. I have to reason about the instance methods of all objects across the entire Context. It’s harder to envision the execution contextualisation that could be a basis for that trust, as things get pretty complicated at the Context level. So I issue a warning. The using directive is a pragma in disguise that basically would turn off the warning — it’s a way for the programmer to tell the compiler “I know what I’m doing.

Given this rationale, does it change your opinion? I’m among the first in line to get rid of the annoyance of this warning. But I think the issue is a bit more finessed than you describe it and I want to ensure we’re not opening the door to bad practices by getting rid of it.

So it’s subtle. There are two things going on: the contract between the Role and the Role-player, and the Contract between the Role-player and the rest of the Context code.

This is not a trygve issue: it is in fact a broader DCI issue. I was reflecting further on this issue yesterday and it came to mind that much of my difficulty in understanding (and translating) the Front Loading example owed to the fact that Context code was sending messages to Role variables which, according to the source code, the Role wasn’t set up to accept… As author of the code Trygve knew that the Role-player would support that method so it would not be a run-time error. (And there is of course no compile-time check for this in Squeak.) As non-author of the code, this (and other things) was enough of a liability to reading that I gave up on converting it to trygve after several months of work.

It is impossible in DCI/Squeak to understand what messages will be directed to a Role-player for any given single Role, without reading the entire Context.

There are two steps to solving the problem that DCI/Squeak had. The first is to document the Role/instance contract. That’s what requires does. This makes it possible to read, in one place, the real intended message interface of an object as a Role-player. It lets the source code communicate at a glance what can happen at run-time. If any Context code is allowed directly to invoke instance methods of a Role player, a Role interface that declares only Role methods communicates only a subset of these messages.

The second is ensure that a Role can maintain the consistency of its Role-player’s state. It can do that if it can serve as the gatekeeper for invocations of Role-player instance methods. All warning-free trygve programs have the ability to do that. If you have the warning that Trygve flagged in red above, then behaviour like this is possible:

context RoleViolation {
   role R1 {
      // Ensures that Role-player count can be
      // incremented only by invoking increment method
      public void increment() { this.incr() }
      public int val() { return this.value() }
   } requires {
      void incr();   // exposed only to R1 methods, or the world?
      int value()
   }
   role R2 {
      public void m2() { R1.incr() }
   }
   public RoleViolation() {
     R1 = new Role1Player();
     R2 = new Role2Player()
   }
   public void run() {
     assert(R1.val == 1)
     R1.increment()
     assert(R1.val == 2)
     R2.m2()
     assert(R1.val == 2, "counter should be 2")
   }
}

class Role1Player {
public Role1Player() { value_ = 1 }
public void incr() { value_++ }
public int value() { return value_ }
       int value_
}

class Role2Player { }

new RoleViolation().run()


The warning alerts you to the fact that there is a potential sneak path. It needs to do this because while there is a trygve facility to protect the Role-player from unauthorized access by the Role (the requires contract) there is no language mechanism that further restricts access to the rest of the Context.

I have considered offering programmers a way to document that they really want to promote the Role-player method to a Role method:

context RoleViolation {
   role R1 {
      // Ensures that Role-player count can be
      // incremented only by invoking increment method
      public void increment() { this.incr() }
      public int val() { return this.value() }
      using incr()
   } requires {
      void incr();   // exposed only to R1 methods, or the world?
      int value()
   }

or:

context RoleViolation {
   role R1 {
      // Ensures that Role-player count can be
      // incremented only by invoking increment method
      public void increment() { this.incr() }
      public int val() { return this.value() }
      public void incr();
   } requires {
      void incr();   // exposed only to R1 methods, or the world?
      int value()
   }

or, for that matter, that they don’t want it published:

context RoleViolation {
   role R1 {
      // Ensures that Role-player count can be
      // incremented only by invoking increment method
      public void increment() { this.incr() }
      public int val() { return this.value() }
      private void incr();
   } requires {
      void incr();   // exposed only to R1 methods, or the world?
      int value()
   }

The latter seems to be the most readable and general (if it doesn’t break the grammar), but I’m open to other ideas. Any of these will allow us to clean up a lot of trygve code.

Right now I’m waiting for Trygve to finish his conversion of pong.k, and we’ll reflect on those results to consider what direction we should take. In the mean time other comments and perspectives are welcome. This is exactly why trygve was created in the first place.



Den 28/04/2016 kl. 14.21 skrev Egon Elbre <egon...@gmail.com>:

...
The semantics of "you cannot call directly a role player method" and not being able to define a type as a requirement, made it difficult to make them into proper roles, so I opted for a "private member" instead.

	role Display requires Display;
	role World requires World;

Similarly I would have wanted to directly call the role-player methods e.g. instead of

	role Handler {
		public void collide(Entity A, Entity B){
			onCollision(A, B);
		}
	} requires {
		public void onCollision(Entity A, Entity B);
	}

write:

	role Handler requires CollisionHandler

Note: I'm not necessarily saying what I wanted to do was a good idea, but rather what seemed appropriate at the time and they may have significant problems. And, I may have missed some of the features that trygve has.

The idea of a Context with no roles seems a little strange to me, especially in a language that also provides classes.


In this code, I used classes as "data records"/"structs" and have single purpose, and the context as where things interact and have more complex logic.

+ Egon

Egon Elbre

unread,
Apr 28, 2016, 10:23:11 AM4/28/16
to object-composition
On Thursday, 28 April 2016 16:45:41 UTC+3, cope wrote:
 
... 
There are possibilities of getting rid of "requires" completely:

context RoleViolation {
   role R1 {
      void incr(); // private by default
      private void incr(); // or always require a qualifer
      public int value(); // optionally expose

      // Ensures that Role-player count can be
      // incremented only by invoking increment method
      public void increment() { this.incr() }
      public int val() { return this.value() }
   }

context RoleViolation {
   role R1 {
      requires Display; // state that it can be played by a specific type(s) (either an interface, class or context)
      ...
   }
 
I would use a concrete type in some cases, because it means people have to learn/create less new concepts when reading the code. E.g. I wouldn't have 5 requires sections with the exact same content, but instead a single interface/type.

But, I haven't made my mind up which of all of them I like.

Matthew Browne

unread,
Apr 29, 2016, 4:29:30 PM4/29/16
to object-co...@googlegroups.com
On 4/28/16 9:44 AM, James O Coplien wrote:
I like to be able to reason about the use case through the Role logic alone. If instance methods return then it’s easy to believe in that trust boundary between the object and the Role. But if a Role’s requires list means that the instance method is published to the entire Context it takes away my ability to reason about the Role / instance interface locally. I have to reason about the instance methods of all objects across the entire Context. It’s harder to envision the execution contextualisation that could be a basis for that trust, as things get pretty complicated at the Context level. So I issue a warning. The using directive is a pragma in disguise that basically would turn off the warning — it’s a way for the programmer to tell the compiler “I know what I’m doing.
While I haven't personally experienced that unrestricted use of instance methods makes code harder to understand (so long as they are in the role-object contract), I think anything we can do to support better code comprehension is a good thing.

To recap, here are the syntax options that have been proposed so far to make instance methods available to other roles:

Option 1

context RoleAccess {
   role R1 {

      public int val() { return this.value() }
      using incr()  // exposed to any role in RoleAccess context
   } requires {
      void incr();
      int value()
   }

Option 2

context RoleAccess {
   role R1 {

      public int val() { return this.value() }
      public void incr();  // exposed to any role in RoleAccess context
   } requires {
      void incr();
      int value()
   }


Option 3 (Egon's suggestion)


"There are possibilities of getting rid of "requires" completely:"

context RoleAccess {
   role R1 {
      void incr(); // private by default
      private void incr(); // or always require a qualifer
      public int value(); // optionally expose

      // Ensures that Role-player count can be
      // incremented only by invoking increment method
      public void increment() { this.incr() }
      public int val() { return this.value() }
   }

context RoleAccess {

   role R1 {
      requires Display; // state that it can be played by a specific type(s) (either an interface, class or context)
      ...
   }


~~~

I would like to propose a fourth option: keep the 'requires' clause, but provide a new access modifier to indicate that an instance method is available to other roles. But I don't know what would be a good word to use; for the sake of an example I'll call it "interactable":

context RoleAccess {
   role R1 {

      public int val() { return this.value() }
   } requires {
      interactable void incr(); // exposed to any role in RoleAccess context
      int value()
   }

The main advantage of this fourth option would be that it keeps the 'requires' syntax but is more concise than option 1 and option 2.

I am fine with whatever we ultimately decide is most clear.
Reply all
Reply to author
Forward
0 new messages