“Verb tuples” for properties, fac eting, and namespacing methods

Kevin Reid <kpreid-M/[email protected]> Sat, 3 Nov 2012 15:53:36 -0700
Newsgroups gmane.comp.lang.e.general
Message-ID <[email protected]>
Properties: the motivating use case
-----------------------------------

There are a number of uses for having “properties” in a language. By properties, I mean that an object's interface presents explicitly named references to other objects, which may be able to be replaced or otherwise operated on. Some other languages have idioms for defining properties, (Java)
  class Foo {
    String getBar() { ... }
    void setBar(String s) { ... }
  }
and some provide properties as a fundamental element, even giving them priority, including Python and (JavaScript)
  var foo = {
    bar: "initial value"
  };

Properties can be seen as in opposition to object-oriented programming, because they encourage the view of an object as state without behavior, as being not an active participant in the system. However, object-orientedness is not all there is to be had, and here are some use cases for properties which I see as relevant:

* Record types. The difference between a map and a record is that a
  record's interface is optimized for a static set of keys. Properties
  mean that records do not have to generate an arbitrary pattern of 
  boilerplate methods, nor do they have to use a "get(key)" interface
  which would be a pain to reimplement if you want more than a record.
  [This is a bit poorly-argued, sorry.]

* Algebraic data types are an elegant way of expressing certain data
  structures. Even if one dislikes generic data structures as API, they
  can be very useful as a foundation for implementing algorithms.
  Algebraic data types are essentially a group of record types, and
  record types essentially are nothing but properties.

* Interop with any language having properties. Notably, in JavaScript
  and Python it is ambiguous whether something is a property-per-se or
  a method.

* Highly configurable things, such as GUI widgets, often simply have
  many individually-variable attributes. (Arguably this should be solved
  in more elegant ways (e.g. a style system), but shouldn't we keep the
  simplest-thing-that-works option pretty?)


The Status Quo
--------------

E has had properties as an experimental feature for a while.

The surface syntax was originally "obj.prop", in the days when calls were written with a space instead of a dot. Later, it was changed to "obj::prop".

The original implementation was that

  obj::prop

expands to

  obj.__getPropertySlot("prop").get()

and similarly for assignment. The concept was that an object could override __getPropertySlot in order to return custom slot objects; the Miranda (default) implementation was

  to __getPropertySlot(propName) {
    def fragment := uppercaseFirstLetter(propName)
    return def slot {
      to get() { return E.call(self, "get" + fragment, []) }
      to put(v) { return E.call(self, "set" + fragment, [v]) }
    }
  }

The problem with this design is that it does not compose neatly with inheritance-by-delegation; an inheriting object's Miranda __getPropertySlot does not know whether it should invoke the super __getPropertySlot (thus getting only super's properties and not self's) or perform the generic slot construction (thus hiding a custom property defined by super).

In practice, this was always used in the default form, using getFoo and setFoo methods. This means that the flexibility of custom slots is not available.


Revisiting Properties
---------------------

As suggested above, I would like to include properties in my largely-hypothetical E redesign -- but in a way which does not increase the complexity of the language or inter-object semantics solely for this purpose. Here are some “rejected” ideas -- rejected in that they are not the one I'm talking about here:

* Just use getFoo/setFoo.
This has several disadvantages; as a naming convention, it is not very sharply distinct from "normal methods", and it means a case transformation -- dynamic property access is necessarily a matter of string-munging. If an object has a slot, say, and wishes to export it as a property, it has to include several stereotyped methods.

* A property is a verb which happens to return a slot.
This means that the implementation of an object has to define a slot -- which, for efficiency and stable identity, must be defined *outside the object expression*, that is,

  def prop {
    to get ...
    to put ...
  }
  def obj { 
    to prop() { return prop }
  }

This is a lot of extra code, and there is no way to define sugar for it which meets E's constraints on sugar. Furthermore, it means that an object has facets, possibly many incidental facets, which makes it harder to use trivial revoking/filtering wrappers, since they have to define wrappers for the facets.

* A property is a thing that is not expressed as methods.
Not in the every-interaction-is-a-message spirit of E.


The Crazy Idea: Verb Tuples
---------------------------

As you may know, a message in current E consists of:
    verb :String, args :List[any]
There are other contexts in which the notion of message may include the recipient, or a resolver for capturing the return value, but in this case we are interested in that which an object receives in a matcher and dispatches on.

I propose changing this to:
    verb :List[String], args :List[any]

Note that the verb is still just Data; it has gained only a little additional structure.

Everywhere the verb would have been some String s, we use [s] instead, so normal methods and calls are largely unchanged. Then, we can define a property as a group of methods; in particular, our "prop" example uses the verbs ["prop", "get"] and ["prop", "put"].

Why in that order? Because then we can generate slot facets by a sort of partial application, which I'm going to call "slicing" for the moment: given an object with the above methods, we derive one which has the methods ["get"] and ["put"] -- which is the slot protocol!

Since this mechanism is generic, it can be used for other “inlined” facets which impose no additional cost until they are requested as separate objects.

It interacts nicely with caretakers and other forms of wrapper, because the facets are derived by the client and so do not need to be implemented by the wrapper, nor does a non-transitive caretaker “fail insecure” by not wrapping a returned facet.

It can be used for, for example, Miranda methods in place of the "__" prefix.

Putting Verb Tuples to Work
---------------------------

Currently, a function, i.e. something invoked with the syntax obj() rather than obj.met(), is an object which has the verb "run". (This is purely a convention supported by syntactic sugar.) In the verb tuple world, suppose we use, not ["run"], but []. This maps naturally to the omitting-the-verb syntax.

Furthermore, it means that the tuple-slicing subsumes the “verb-curry” feature of current E, which is where (obj.met) generates a facet such that (obj.met)() = obj.met(). Since the actual verb is ["met"], slicing it produces the verb []. We now have invented a syntax for tuple verbs: the elements are separated with dots.

All nice and elegant, eh? Not quite. What happens if we apply this syntax to the original problem, namely properties?

If the object has a property "prop", then we now have the syntax obj.prop yielding, not the property's value, but the property slot! If you want to access the property, you have to write obj.prop.get(), or using the subscript sugar, obj.prop[].

We could reintroduce the dereference operator and write *obj.prop, or a postfix Pascal-style obj.prop^.

Or how about an alternate dot? 
	obj..prop
	obj::prop
	obj~prop 
	obj->prop
(That last one fits into certain traditions, but doesn't really have the same semantics, and in E we use arrows for asynchronous operations.)

So, that's one open problem in this scheme.


More problems
-------------

* We're defining an object by embedding its definition inside an object. Well, an object has one other set of properties than just the methods it has: its auditors. How do we define *what auditors the derived facet should have*? We could define a generic set that are applied if possible, e.g. the facet is DeepFrozen if its container is. We could let the auditors decide what they want to do for each possible facet, but on the other hand the object might want to make that decision.

* We have introduced an extra way to define facets, unlike normal distinct objects which may or may not happen to be implemented in the same place. (They are interconvertible, in that a preexisting object can be "embedded" by forwarding messages using matchers, but that's more cruft.)

This last one is the most significant argument I currently see against the inclusion of this feature.

-- 
Kevin Reid                                  <http://switchb.org/kpreid/>