Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idea: Implicit open for arithmetic infix operators #6477

Open
cometkim opened this issue Nov 5, 2023 · 8 comments
Open

Idea: Implicit open for arithmetic infix operators #6477

cometkim opened this issue Nov 5, 2023 · 8 comments

Comments

@cometkim
Copy link
Member

cometkim commented Nov 5, 2023

Note: This proposal is not related to the custom infix operators. Just improves the ergonomics of the + operator for built-in types.

One of the most awkward parts of introducing ReScript today is the arithmetic operators. Especially when using +., -. for float .

To be clear, the distinction of integers and floatings and strict type checking are not bad at all. However, the fact that it introduces various operators such as +, +., and ++ depending on the type is awkward for users from JS or other languages that support overloading / extensions.

This is clearly a constraint from OCaml. The situation can get worse as we drop support for custom infix operators.

For example, https://forum.rescript-lang.org/t/new-operator-for-bigint-support/3922 when we add support for BigInt literals, should we add new arithmetic operators for it? how?

I tried to think a simple concept. Please let me know if this is not possible.

When an arithmetic operator (+, -, *, /) is detected in an expression, the module containing the infix implementation of the left type is implicitly local-open. If ReScript adds further operator support equivalent to JS, like >>, <<, %, ** may be added.

1.2 + 3. treated as Float.Infix.(1.2 + 3.) so it could be compiled instead of type error. 1.2 + 3 should be a type error as it is. To prevent this behavior from being abused by other modules, the compiler uses a restricted list of supported types.

@zth
Copy link
Collaborator

zth commented Nov 21, 2023

@cristianoc has more insight, but I don't think this type of thing will work that well in reality because you'd still have quite a few cases where you'd need an explicit operator for inference to work. Example:

someVar + 3.

What if the type of someVar is to be inferred here? What type should it get?

More examples:

someVar + someOtherVar

Both are to be inferred here, and I want them to be floats. You'd have to give up that type of inference and force annotations of them somewhere if you want to infer floats and not have an explicit operator.

@cometkim
Copy link
Member Author

cometkim commented Nov 21, 2023

you're right.

And requiring type explicity for operators breaks compatibility, and falling back to the int operator (which is how F# does) doesn't add much benefit 🤔 I believe explicit local open syntax can solve this, but we don't have that syntax (yet?).

But still, adding a set of operators for bigint and further types also seems awkward to me.

@zth
Copy link
Collaborator

zth commented Nov 21, 2023

I honestly think we'll be better off focusing on how we can make error messages and tooling around this as good as it can get. So ensuring that it's always clear (when it can be) what's wrong and how to fix it, and potentially also ensuring that there are code actions and similar that'll allow you to just rewrite to the right format whenever you're doing things the wrong way.

@cometkim
Copy link
Member Author

I understand the tooling side is important, but tooling doesn't solve all syntax complexity issues (e.g. Rust)

The difference between +, +., ++ is the feedback I most often hear when introducing ReScript. I haven't met yet anyone whose experienced language that doesn't support operator overloading. every each person asked back to me why do I need additional operator here.

I haven't used F# in any depth, so I'm not sure how much of a difference simple inference strategy makes. But I can imagine a few cases where it can be useful;

I had to change my existing int-base encoder to be float-based due to precision issues. Changing all of the operators used in the code was cumbersome. Once bigint is added to language I'll have to the same again.

Anyway I opened this because I think it's a real problem, but it's still an idea and I don't have a strong opinion. I wanted to share some thoughts with you before doing any related work, such as bigint support.

Perhaps the forum would be a more appropriate place. I will move this later.

@cristianoc
Copy link
Collaborator

You can do that today user-side, abusing quoted ids a little. No need for language extensions to experiment on a project.

module FloatOps = {
let \"+" = (x,y) => x+.y
}

open! FloatOps
let z = 3. + 3.

@fhammerschmidt
Copy link
Member

Maybe we should expose them it in ReScript-Core as it is the case now in Belt: https://rescript-lang.org/docs/manual/latest/api/belt/float

@cometkim
Copy link
Member Author

This is already implemented for comparators lik ==, <, >, etc

// these are fine
let _ = 1 > 2
let _ = 1.1 < 2.1

// type error
let _ = 1 < 1.2
[E] Line 3, column 12:
This has type: float
  But it's being compared to something of type: int

  You can only compare things of the same type.
  
  You can convert float to int with Belt.Float.toInt.
  If this is a literal, try a number without a trailing dot (e.g. 20).

@cometkim
Copy link
Member Author

If the signature is omitted, it defaults to int. This can make some signature mismatch noise when writing implementation.

For convenience we can reuse the coercion operator: (someVar :> bigint) + otherVar

This is still somewhat inconvenient because it requires parentheses. Maybe this can be solved by adding the as syntax used in TypeScript or Flow (Flow replaced the (value : Type) syntax with the as syntax for the same reason)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants