Ideal Language

The meta-field dilemma: `a.size` or `size(a)`

I can’t decide whether a.size or size(a) is more correct when getting the length of an array. While writing Leaf I have a recurrent feeling that perhaps a.size is wrong. What is the correct way to get meta-properties from an object?

Other examples

Considering just size will bias the discussion, so let’s list a few other common meta-like properties:

  • a.type vs. type(a)
  • getHashCode(a) vs. a.getHashCode()
  • a.count vs count(a)
  • toString(a) vs a.toString()
  • a.clone() vs clone(a)

To contrast let’s pick a couple scenarios where one syntax clearly seems preferred. Consider a simple structure:

1
2
3
4
type point : [  
    x : integer,
    y : integer
]

a.x feels like the right way to access the x field. x(a) seems very wrong. The difference is that x and y are actual properties of point, they aren’t meta-properties. The difficulty in defining a meta-property is part of the problem.

I can’t think of a great situation where a meta-property definitely looks better as a function, perhaps taking the address of a variable.

1
2
&a
a.address

a.address feels wrong. Yet, in C++ we actually have get_shared_from_this() member functions, as well as some projects having templated casting operators defined on their types (like the Leaf source code itself). The line between a property, meta-property, and operator is quite blurry.

What is a meta-property

Does it come down to just that question: what is a meta-property? I’m inclined to say that only true properties should have a property syntax pt.x and all meta-properties should use a function syntax getHashCode(pt). We can’t make this rule though unless we can define a clear distinction between property and meta-property.

A property is something that is intrinsically part of a value. If the property were removed the value would lose part of its meaning, it’d be incomplete. This is why x is a property of point: if we remove x then we simply don’t have a point anymore.

A meta-property is something that is extrinsic to the value. The interpretation of the value does not depend on this meta-property in any way. The interpretation of a point does not depend on it’s address, it’s hash code, or how it’s formatted as a string. Those are all meta-properties.

In Leaf I’m already using the terms instrinsic and extrinsic in the type system. They apply to type definitions, but the meaning is essentially the same as here for properties.

Whether a.size or size(a) is correct thus depends on whether size is intrinsic or extrinsic to the value. For an array, or any collection, this is part of the instrinsic value. The collection can’t exist without a size. a.size appears to be the correct form.

The problem of OOP

One reason why the property types get mixed is due to how OOP works, using classes and virtual inheritance. According to my definition, getHashCode is a meta-property, thus should have the syntax getHashCode(a). Yet it’s important that the implementation of getHashCode can be overridden per type: it needs to be a polymorphic function.

The common approach to polymorphic functions is a virtual member function. This forces getHashCode to become a real property of the object, rather than a meta-property.

A second approach is overloading functions based on the type. This only works for unrelated types; a type hierarchy still needs the virtual approach.

It’s possible that we simply decorate meta-functions and properties to change the calling syntax:

1
2
[extrinsic]
defn getHashCode = function() { ... }

We still define these as though they are normal virtual functions, but the extrinsic decorate says they must be called only as getHashCode(a) and not a.getHashCode(). To me this feels like kind of a hack, not a clear solution.

I’d prefer some first-class support of out-of-line polymorphic functions. There must be some implementation of this somewhere, but I can’t find it. Does anybody know of a language that allows this?

5 replies »

  1. I am in `a.foo` camp because of daily usage — any kind of hinting system can make a clever suggestion, in case of `foo(a)` you will get a ton of irrelevant ones. Besides `foo(a)` would make sense for me if it is applicable always for `a`, for example in case of C# you can type `typeof(type)`.

    • I agree with macias, the a.foo approach allows one’s IDE to more intuitively expose “all of the things I can do with this object”. A big part of why C# is such a pleasure to use is that how programmers will actually interact with the language (via Visual Studio) is a core *language design* consideration, not just an after-the-fact IDE design consideration.

      It reminds me of how in the 1950s, the US Air Force realized that ergonomics wasn’t just about comfort but needed to be elevated to a fundamental engineering concern in order to create truly high performing aircraft: https://www.thestar.com/news/insight/2016/01/16/when-us-air-force-discovered-the-flaw-of-averages.html

      http://99percentinvisible.org/episode/on-average/

    • I think the IDE integration can be done without needing members. It shouldn’t be a problem for a parser to also emit all the functions that take a type as the first argument. Though it might be weird to type a `.` and then it convert to function notation.

  2. To be honest, a.foo() and foo(a) are equivalent. If you accept this and allow both forms in Leaf, then you can make a convention that for extrinsic properties the latter form is preferred. Conventions (as contrasted to compiler-enforced-rules) can work really well. Python is an example.

    In fact, in case of C++ the member form a.foo(), under the hood translates to foo(a).

    • The difference between the forms is more pronounced in Leaf than other languages perhaps. It’s due to the unification between closures and classes.

      `a.foo()` really means to call `foo` within the scope of `a`. Since functions and scopes can be passed around the distinction, while subtle, is somewhat important.

      `foo(a)` is passing argument `a` to the function `foo`.

      If I allowed both it might become ambigious. Consider you are already inside the scope of `a`, (inside a `a.pine()` call for example, and you do `foo(a)`. It’d be ambiguous as to whether this means `a.foo(a)`, just `foo(a)`, or `a.foo()`.

      The “context”/scope in Leaf is not treated as an argument.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s