wren
wren copied to clipboard
[RFC] Grammar for inverted operator.
In the recent issues (#968, #989) raised the need for inverted binary operators aka a binary operator where the right hand side parameter of the operator should be this.
I suggest the grammar:
class Foo {
(rhs)op(this) { }
}
It is far from perfect, but it set a base for the discussion.
The aim is to provide a dedicated grammar for such operators, that don't collide with unary and binary operators. It also aims to help to reason and think about the code, because the inversion of operands is not an intuitive thing.
Examples of such operators are:
123 in [4, 5, 6] // where it should would act like: [4, 5, 6].contains(123)
"foo" ~~ "bar" // where it should would act like: "bar".contains("foo") or RegExpt.new("bar").match("foo") or ...
Why not just op(rhs)?
This is the same grammar as binary operator, and is confusing. The fact that operands are inverted make them hard to reason and think about, in particular when the grammar don't reflect that inversion.
Could a quick example be given here of when this is useful in real life since those other two threads are super super long. :)
@joshgoebel mhermier put two in the OP. E.g, for 123 in [4, 5, 6], you could implement the operator two ways:
123.in([4,5,6])[4,5,6].contains(123)
In the former case, individual values have to know about the different types of containers. In the latter case, containers only need to know about themselves. I think the second approach is more cohesive, and would probably be easier to maintain. Does that help?
(I have also noticed the GH issue interface is not ideal for long discussions :( )
I like the idea --- what about just (lhs)op? E.g.,
class CustomContainer {
(needle)in { /* find _needle_ in _this_ */ }
+(elem) { /* append _elem_ to _this_ */ }
}
As far as I know, parentheses cannot be overridden in Wren :) , so the syntax would be unambiguous. Since we already have implicit this when this is on the LHS, I think we could do it when this is on the RHS also. What say you?
mhermier put two in the OP.
They were added after I requested a small example. :) But yes it's all quite clear now what this feature would be useful for now. I see one small potential issue...
// object is a Bird and object2 is a Plane
object.relatedTo(object2)
Today just by looking at that code I can be certain that relatedTo exists and is a non-static member of the Bird class. So if I go digging into the source I know exactly where to look. After adding this feature I can no longer be certain of this... I'd have to first look in Bird and then failing to find the method I'd have to go look for a RHS method in Plane...
I'm not sure this is resolvable or a major problem?... but it's a thought. Perhaps a different syntax at the dispatch site could resolve this but then that might defeat the whole point of it appearing seamless.
@joshgoebel sorry for duplicating mhermier's effort :)
Fair point about "where is this defined?" --- the same concern exists in C++. If I understand correctly, the scope of this proposal is operators, and you can't add new operators from a .wren file. So object.relatedTo(_) would always be a method of object.
The Wren programmer would have to know that a+b was a.+(b) and a in b was b.contains(a). However, the new syntax would make that clear. We could also issue a helpful error message if someone tried to define in(_) but only (_)in was valid.
Sorry for the multiple reedit, it is quite not clear in my head yet.
There is no such confusion op is a place holder for an operator name. Therefore '.' lookup don't apply here, and don't change.
I don't thought much of the possibilities of that change. This is just an idea not thought much, but maybe we can have any user defined inverted operator as needed, like:
// object is a Bird and object2 is a Plane
object.relatedTo(object2) // The usual behavior
object relatedTo object2 // As an inverted operator
The example is confusing because the name is the same, but it would behave completely differently.
@mhermier true --- if we did allow custom-defined binary operators, (_)op vs. op(_) would be an easy way to assign which was which. For custom-defined operators, we would have to figure out associativity.
There is a huge down side to allowing user defined operator:
[a b, c, d, e]
// ^ error will be cough here, complaining about missing right hand side of inverted operator here
// ^ instead of here complaining about missing ','
@mhermier makes sense --- I think it's fine not to add user-defined operators at the moment.
User defined operators are one of the worst things invented, if not the worst.
Parsing them is a nightmare, and understanding them is even worse. Can you tell what the <*> operator means? (Not a joke! IIRC I saw it in a F# codebase).
User defined operators are one of the worst things invented, if not the worst.
They are, even worse than un-sliced bread!
Scala anyone?
But, seriously, I do like @cxw42's syntax in https://github.com/wren-lang/wren/issues/1002#issuecomment-830859422 for dealing with inverted operators.
That syntax is nice, it would go in pair with current unary operator syntax, but here lies the problem I have with it. It doesn't emphasis the operator nature of the method signature. Adding (this) in the signature explicit the nature without requiring the operator keyword.
That's a fair point.
I'd be happy with or without the (this) frankly.
The is also another argument, but I don't know if the grammar would allow them. How would we write an unary right operator signature?
How would we write an unary right operator signature?
Are you thinking about something like a++ if we get compound assignment operators?
Yes, how would the signature look like in the method definition?
Well, I'm not sure whether the likes of a++ and a-- would be a good idea anyway as (in combination with the corresponding prefix operators) they're a can of worms even in C/C++.
It might be better to just go with a += 1 and a -= 1 instead.
If that were the case, then I think that any object which supported the + or - operators should automatically support += and -= with the appropriate meaning and that you shouldn't be able to override the compound assignment operators in isolation.
But to answer the question you posed, I guess you'd have to involvethis, perhaps:
(this)op { \\ ... }