To self or not to self?

With most of my recent programming work being in python, one of the adjustments I’ve had to make when experimenting with goblins is not having access to a “self” or “this” reference from within an object. I can see reasons for and against having these references and I was wondering if this was an intentional choice and whether there are any ocap-specific considerations.

The main difference this has led to is needing to rely more on composition than I’m used to, in order to have references to objects providing specific functionality or capabilities.

2 Likes

Not really addressing your question, but something me and @cwebber have done when we’ve needed “self” has been to do something like this:

(use-modules (goblins)
             (goblins vat))

(define (spawn-hello)
  (define (^self bcom)
    (lambda ()
      (format #f "Hello myself! ~a" self)))
  (define self (spawn ^self))
  self)

(define a-vat (spawn-vat))
(a-vat
 (lambda ()
   (define hello-actor (spawn-hello))
   (pk 'result ($ hello-actor))))

An example of this in some real code is in the racket edgename database code: brux/edgenames.rkt · main · spritely / brux · GitLab

2 Likes

Also worth noting that in the Racket version’s actor-lib there’s selfish-spawn, which is basically that but abstracted a level: goblins/actor-lib/selfish-spawn.rkt · master · spritely / goblins · GitLab

2 Likes

In Agoric’s Hardened JavaScript, we generally eschew the receiver object (this) and we do not allow it at all in the Jessie subset. It’s certainly possible to treat the receiver as just another capability-bearing argument of a pure function, but within that approach, one has to take care to abstain from placing private methods or data on the object. It is far easier to reason about encapsulation as a security primitive in terms of state captured in the closure of a bag of related functions/methods that simply ignore their receiver argument, in order to avoid confusing the rights the object represents from the internal machinery. And, there are of course certainly mechanisms that achieve the same effect and also thread a receiver object.

2 Likes

@cwebber and I have had lively discussions on this topic, and I think we’ve agreed to disagree, or at least have chosen to pursue opposite defaults. The Goblins code you showed depends on a mutable environment in which you can bind self after you’ve created the actor that refers to itself.

In a pure-functional system (like Humus, for example) that kind of circular reference would require the equivalent of a Y-combinator. I considered that too awkward/ugly, so I prefer to provide a self reference (sometimes as a keyword) within every Actor. One pattern that uses self is lazy-initialization, where the Actor uses become to initialize itself on receipt of its first message-event and resends that message (asynchronously) to itself for handling after initialization.

I don’t have the “risk” associated with “private” methods, partly because I don’t have synchronous method-calls, but mostly because self is not a privileged reference. It is, in fact, the same reference any other collaborator would use to send an asynchronous message to this actor.

I think the distinction comes down to this. Who has a reference to a newly-created actor, the creator only, or the creator and the actor itself? Goblins implements the former, and I prefer the latter.

1 Like

Hmm. This is interesting. I’ve been working with the Unum/Presence model since the beginning, and lazy initialization has always presented a challenge (especially when some Una want to initialize relative to others…). Thanks!

1 Like

In Agoric’s Hardened JavaScript, we generally eschew the receiver object (this ) and we do not allow it at all in the Jessie subset.

aaand then we hit the upgrade problem. And now we use this a little bit. For a sort of OCap ORM.

I’m not sure where any good docs are, but I know at least one example…

1 Like

I also am running into this specific issue with the little project I am currently working on (tic-tac-toe). I certainly would not need self, but it feels lacking just due to the languages I’ve used in the past, for example when using (methods ...) if you want to invoke your own method. I have gotten around this constraint by having defines that the methods are bound to (the defined method is _name and the method is [(name) (_name)]–but it doesn’t feel elegant.

In fact, I even attempted to port selfish-spawn to Guile, which I created an MR for: Port selfish-spawn from Racket actor-lib (!97) · Merge requests · spritely / Goblins - Guile implementation · GitLab

What I really wonder about, is whether it’s possible to use bcom if you have a selfish-spawned object. Can a non-selfish object bcom one, and can you bcom anything (selfish or not) effectively if you are a selfish object? If you pass the same self - does it have the proper debug name and everything. Worth experimenting with but also perhaps I just need to reframe how I design objects!

I am new to all of this, of course.