Giter Site home page Giter Site logo

unify operator overloading about bosatsu HOT 23 CLOSED

johnynek avatar johnynek commented on August 25, 2024
unify operator overloading

from bosatsu.

Comments (23)

johnynek avatar johnynek commented on August 25, 2024 1

-x could just be 0-x or neg(x) or x.neg

Simplicity and consistency are major goals here. That will inevitably leave some verbose corners.

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024 1

so my new proposal is that operator ++ is exactly like concat for instance. It can appear anywhere a binding name can:

operator ++ = foo
[1] ++ [2]

def takeOp(operator /, baz, bar):
  baz / bar

match x:
   operator f: f(1)

etc....

So, operator is a key word that allows us using operator names as binds.

Then next part is that an operator without that prefix f + is parsed as Apply(Operator("+"), f).

The last part is that we will limit operators to avoid : _ , @ ( ) and . so we don't have any ambiguity with other existing symbols.

I think with #192 it should be pretty easy to implement this (although doing that refactor was a brutal several hours....)

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

I have this idea last night:

we could allow operators to be file local only:

package Foo/Bar

operators [
  + as plus_Int,
  ++ as concat_List,
  prefix ! as not,
]

three = 1 + 2
three_list = [1] ++ [2] ++ [3]

false = ! True

Pros for operators:

  1. syntax gets out of the way and the semantics are easier to see.
  2. (minor) easier to write.

Cons for operators:

  1. readers don't always know what they mean (harder to google for generally).
  2. seen as encouraging a bespoke style that is difficult to transfer.

This proposal increases readability (all operators are declared in file scope and linked to a method), at the cost of extra verbosity (we can't import an operator, we have to copy-paste common ones).

An additional idea to aid readability is to limit the set of symbols that could be operators. Something like:

core = oneOf [ + - * / | % ! ? < > & * + ^ ]
repeated = core+ # ++ ** // !!
diamond = <core|repeated> # <*> <+> <++>

With a limited menu, plus explicit mapping to functions, maybe this is a good place to land.

Lastly, a binding form would be good:

x += 2
``
could be expanded to:

x = x + 2

So, more generally, any operator with `=` appended is also a legal use of an operator, though not legal for definition. This is really doubling down on new-let-scope simulating mutable references however... which maayyybe is a bad idea.

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

cc @non

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

actually, out of band, @avibryant suggested reusing the import binding syntax:

package Foo/Bar

import Baz [ foo as operator / ]
import Bosatsu/Predef [ plus_Int as operator + ]

So this introduces operator and probably prefix operator and postfix operator inside the import statement.

It also highlights we need to be able to do rebinding of Bosatsu/Predef.

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

@snoble thoughts on this?

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

The challenge here is that if we allow totally unbounded syntax as operators, how do we parse them? Maybe there is something elegant, but I don't know what that might be.

Alternatively, we could have a fixed universe of possible operators, and then use the binding syntax we describe here. In this way, the parser does not need to know about what operators are in scope to parse, but of course we could have a name error later.

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

A simplifying requirement would be that all operators are postfix, and due to currying, they are all functions on one variable. So 1 + is parsed as OperatorApply(Literal(1), Operator("+"))

Note, in this world, . could be regular operator: a.f == f(a) which binds tighter than function application (so a.f(b) == f(a)(b) == f(a, b)`).

This means the few prefix operators can't be easily supported, but I don't want to go crazy with operator syntax. Again a core goal is you can understand any file by reading it.

If we force all operators to be functions, (maybe curried) then I wonder if we can just use regular syntax to define them, the difference is, they have different call syntax.

external def +(left: Int, right: Int) -> Int

then we could import Bosatsu/Predef [ + as plus ] if we wanted to opt out.

This is somewhat of a reversal of the idea that operators are second class and that we wanted to tax the writer by explaining them at the top of each file.

what do you think @snoble ?

from bosatsu.

snoble avatar snoble commented on August 25, 2024

This feels really compelling. Just thinking of some edge cases. Can an operator still be treated as a regular variable? Can you do f(+)? Can you have multicharacter operators (eg >> would be nice for composition)? If so, how do you prioritize how multiple characters are combined?

Can you use a let to assign a function to an operator?

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

Very good questions.

Maybe there could be some notation to treat an operator like a regular value. Without that notation, then we treat it as autoapplying.

operator(+) = plus

z = 1 + 2

def operator(-)(x, y): x.minus(y)

f(operator(+))

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

Or maybe no parens

import Foo [ bar as operator % ]
operator + = plus
f(operator +)

from bosatsu.

snoble avatar snoble commented on August 25, 2024

That's looking pretty neat. And I guess that solves how to think of multicharacter operators. If you Operator apply to a value assigned to an operator you first have to use the operator keyword. So multiple operator characters in a row are always treated as a single variable name

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

yeah. I think that's right. I actually really like this proposal at this point. I think it is simply:

  1. operator is a keyword that allows the following symbol to be used anywhere a let bound name would normally be used.
  2. without the operator prefix, an operator is parsed as applying the previous value to the value bound to the operator.

The only open question in my mind is prioritization. My first inclination is no prioritization: strict left to right. Use parens if you don't want that rule.

My fallback is a fixed prioritization algorithm to enable *-like things to bind tighter than + like things and || like things to bind looser than && like things.

from bosatsu.

snoble avatar snoble commented on August 25, 2024

This is Ruby's precedence ordering
https://ruby-doc.org/core-2.2.0/doc/syntax/precedence_rdoc.html

from bosatsu.

snoble avatar snoble commented on August 25, 2024

I forgot about unary operators. Is it resonable a symbol could be assigned a different function for when it's applied as a unary?

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

parsing it a bit tricky with unary operators. Unless we have a fixed table that we know they are unary.

a~b is that (a ~) b or a (~b).

I almost want to prevent any unaries.

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

By my count python only has not and ~ as unary operators. And only ** has right to left associativity.

Iā€™m very inclined to say no unary operators and always left to right associativity.

Iā€™m very close to implementing this proposal and seeing how it feels. I have to admit working with methods on integers is pretty annoying.

from bosatsu.

snoble avatar snoble commented on August 25, 2024

Shoot, I think that's right. I was trying to argue the opposite but then I got to trying to figure out what happens if you get two operators in a row, and I can't think of how to disambiguate in a way that doesn't seem broken.

Either you can support 2 + >> f (where both are binary operators) or you can support 2 + - 3 (where the first is binary and the second is unary).

It might not be crazy to just disallow both. So you can't have an operator directly following an operator. So the first would have to be written as (2 +) >> f and the second as 2 + (- 3)

With this restriction you can say an operator is parsed as unary iff it begins a statement. So your example would be parsed as (a ~) b. Which seems reasonable since I think you need parentheses for non-operator applies

Not sure this is good. But I'm assuming users would like to write a = - b for example

from bosatsu.

snoble avatar snoble commented on August 25, 2024

This doesn't solve how you could give an operator a unary and a binary definition... And maybe that's a good reason not to have unary operators

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

One idea after #194 and the idea that the unescaped names should be used for equality, we could just use backticks for operators:

`+` = add

maybe this is too terse, but I am not crazy about having two ways to say the same thing.... operator + = add is a bit nicer to read, though... It is easy enough to support both.

from bosatsu.

snoble avatar snoble commented on August 25, 2024

It is true that foldLeft(0, +) is pretty satisfying compared to typing out operator. But I don't have the instinctive aversion to having multiple ways to do things

from bosatsu.

johnynek avatar johnynek commented on August 25, 2024

An interesting trick scala does is any operator that ends with : is right associative. So you can write: a :: b :: c ::: Nil and build a list from right to left. That right association can be nice. It is something to consider that was not put in place in #198

from bosatsu.

snoble avatar snoble commented on August 25, 2024

Oooooh... I've wondered about that in Scala in the past. And that would be helpful in faking the tuple syntax

from bosatsu.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    šŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. šŸ“ŠšŸ“ˆšŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ā¤ļø Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.