ecsharp icon indicating copy to clipboard operation
ecsharp copied to clipboard

LES3: Attributes on operator targets (and subexpressions)?

Open qwertie opened this issue 6 years ago • 3 comments

I've been looking for a good way to represent properties in LES. There are some straightforward choices that can be used already:

    .prop X: int {
        .get { .return _x }
        .set { _x = value }
    }
    .prop X: int {
        get => x
        set => _x = value
    }
    .prop X: int => x

But these styles (except the last) require a keyword to introduce them (.prop). I guess this wouldn't be too burdensome since you might want a keyword to indicate accessibility anyway...

    .public X: int {
        get => x
        set => _x = value
    }

But fields and functions can be supported without a keyword, which creates an asymmetry:

    foo() => .print "Food" // OK
    _y: int // OK
    Y: int { get => _y } // syntax error

So I was thinking about whether there is a way around this, and I thought of a nicely compact syntax:

    X: int  get _x
    Y: int  get _y  set _y = value
    // Assumes line continuators exist (#86)
    Z: int
    |  get { _y }
    |  set { _y = value; PropertyChanged("Z") }

(maybe too compact? well, I'm fine with it)

It would work for events too (though I'm inclined to insist on a keyword for that case):

    .event PropertyChanged(name: symbol)
    |   add { /* TODO */ }
    |   remove { /* TODO */ }

Then I noticed a problem - since get and set are ordinary binary operators, there is no obvious way to associate attributes with them. So how do we represent the classic case of a public getter with private setter (or the more taboo sealed getter and virtual setter)?

Well... we could technically allow attributes that annotate the operators themselves...

    // proposed syntax
    Z: int
    |  @public get { _y }
    |  @private set { _y = value; }

In the syntax tree, the attributes would annotate the target (not the expression) as follows:

(@public `'get`)( Z: int, (@private `'set`)({_y}, {_y = value}) )

You could, of course, annotate any operator this way:

x @hello + y
// syntax tree: (@hello `'+`)(x, y)

Currently, attributes are only allowed at the beginning of an expression. If operators can have attributes, then surely subexpressions ought to be allowed to have attributes too?

@J energy := 0.5 * @kg mass * @(m**2/s**2) velocity**2

But then the question arises of how to decide how much of an expression the attribute would apply to. Perhaps an attribute can simply be associated with as much of an expression to its right as is logically possible according to the current precedence floor; in that case I believe the expression above would have the following structure.

@J ( energy := 0.5 * (@kg mass) * (@(m**2/s**2) (velocity**2)) )

I'm not entirely sold on either of these proposals, but it certainly seems worth exploring.

qwertie avatar Jun 22 '19 04:06 qwertie

A couple of other things to note about properties...

  • Since get and set are binary operators it is necessary to give an argument even if a body would be useless (e.g. in interfaces). I envision using _ to denote the lack of body; the same syntax could be used for function declarations if and only if the function body is also attached with an operator:

      .interface IListSource!T {
        Count: T get _
        TryGet(index:int): Maybe!T => _
        Item[index:int]: T get _          // indexer
      }
    
  • If the getter returns a lambda, the setter will be improperly embedded within the getter body:

      LazyValue: T 
      |  get () => LoadValue() 
      |  set {...code}
    

    This of course is parsed like (LazyValue: T) get ( () => ( LoadValue() set {...code} ) ) and presumably the user - who has no idea there is a weird parse going on - will be baffled if his code doesn't work. The compiler could perhaps detect the lambda (or the presence of other lowercase keyword operators which could also cause trouble) and ask the user to place the contents of the get clause in parentheses or braces.

qwertie avatar Jun 22 '19 18:06 qwertie

The other day I was thinking it would be nice to have a language that supports shorthand operators for public/export, private, virtual etc...

Foo() => {} // default accessibility (package?)
++Foo() => {} // public
+Foo() => {} // internal
*Foo() => {} // protected
+*Foo() => {} // protected | internal
-*Foo() => {} // protected & internal (private protected in C#)
-Foo() => {} // private
^Foo() => {} // virtual
^^Foo() => {} // override
^^^Foo() => {} // sealed override
%Foo() => () // umm... let's say static/singleton

But this would be a lot to remember and wouldn't work on get and set operators. Something that could work as a shorthand is a way of applying modifiers to multiple members:

@public @virtual @CustomAttribute {
    Func1() => { }
    Func2() => { }
    Foo: int  get _foo  @private set _foo = value
}

qwertie avatar Jun 22 '19 18:06 qwertie

I dunno, any thoughts on this @jonathanvdc? One could also argue that we ought to have keywords on all members anyhow, to aid grepping - I'd particularly like a keyword on member variables which I think tend to be more important to keep track of, and find, than functions (even though langs are more likely to have keyword for the latter). In any case I'd like LES to offer as much flexibility as practical to DSL/compiler authors.

qwertie avatar Jun 22 '19 19:06 qwertie