Allow regular letters as operator names

Currently swift does not allow to name operators in the same way functions and variables are named. Meaning that writing clean api and hiding complexity behind sugar is impossible for programmers, only for compiler devs.

let loader = ImageLoader {
    let loadAdress <- local String ("adress")
    let pingToLoad <- async { (String) -> () in ... }
}

As you can see, if operators could have regular identifiers (local and async here) custom object declaration could have been clear and swifty and whatever other epithets you can come up with. (Having correct behaviour of operators with custom storage declaration would be nice as well). So why are operators' names are limited to consist of some set of symbols and not letters?

Why do you have 2 operators in succession? That code isn't at all clear to me, and I don't see how local or async are operators.

It is essentially

let loader = ImageLoader {
    let loadAdress <- local String ("adress")
    (storage declaration) (assignment operator) (prefix operator) (Type) (init())
    let pingToLoad <- async { (String) -> () in ... }
    (storage declaration) (assignment operator) (prefix operator) (closure)
}

Prefix operators are required to be adjacent to their operand.

let a = +5  // good
let b = + 5 // Unary operator cannot be separated from its operand
1 Like

Ahah, I see now. Can they be not? try and throw are not required to be adjacent and it seems to be fine for the parser

Keywords can be unambiguously parsed because the parser has a list of valid keywords. Operators can be anything permitted by the allowed character set.

1 Like

This is something that has already been discussed. It's also in the list of Commonly Rejected Changes:

Replace logical operators (&&, ||, !, etc.) with words like "and", "or", "not", and allow non-punctuation operators and infix functions: The operator and identifier grammars are intentionally partitioned in Swift, which is a key part of how user-defined overloaded operators are supported. Requiring the compiler to see the "operator" declaration to know how to parse a file would break the ability to be able to parse a Swift file without parsing all of its imports. This has a major negative effect on tooling support. While not needing infix support, not would need operator or keyword status to omit the parentheses as ! can, and not somePredicate() visually binds too loosely compared to !somePredicate().

2 Likes

So why they don't want this feature if it can be done? Is this mythical complexity increase they afraid of? Although I can see how it could be easily misused by making many operators that are not necessary, the potential ability to (again as I said) hide complexity and give more clear user-level apis is of worth. Don't you agree?

Notice that I am not proposing to replace them.

I suggest you read the linked discussion thread.

Here's a response from Cris, from the discussion thread, which I believe still holds true.

1 Like

I dont get it. Since each file has to be parsed, is it hard to look up the operator declarations or what? If imports are represented as graph then operators start to appear at some level and then propagate upwards the hierarchy. Why is this impossible?. And then, what allows kotlin and scala to have this feature?

It's hard to look up operators if you are only looking at one file. Keep in mind that the discussion is about parsing, not semantic analysis.

2 Likes

Parsing is not only used when compiling and linking. Reliably formatting or highlighting a file requires to be able to parse it correctly for instance.

Having custom operator looking the same than any other identifier means that even to format a single file, you will still have to parse every files.

With character partitioning, the tools can tell without ambiguity what is an operator and what is an identifier just by looking at the parsed code.

I can't answer for Scala, but at least the answer is easy for Kotlin. It does not supports custom operators.

2 Likes

Maybe for some code embeddings/snippets it would be a problem, but that's a weak argument to not to enable the language feature since most of the usage is already happening in IDE. Even still, it would be possible to annotate that external code manually.

It has infix notation. Same for scala.

A lot of Swift is written in plain old text editors and non-Swift centered IDEs such as VSC, and it's a goal of Swift to be useable in such environments. See LSP support, for example.

It's also a boon to build efficiency, as there are no cross-file dependencies for parsing. I do not know if the compiler currently takes advantage of this feature.

2 Likes

I see this in the linked document:

Note that infix functions always require both the receiver and the parameter to be specified. When you're calling a method on the current receiver using the infix notation, you need to use this explicitly; unlike regular method calls, it cannot be omitted. This is required to ensure unambiguous parsing.

Swift custom operators are far more flexible, and allowing some characters for some operators and not others would be inconsistent and confusing. Such features are non-goals.

3 Likes

Doesn't this mean that there should not be default arguments? Since in swift it is possible to write this

prefix operator |><>
prefix func |><> (rval: String = "!") -> String {
    return rval
}
print (|><>)

It prints (Function), a clear evidence that it already has incorrect parsing.

I highly doubt that it is a feature. Clearly implicit conversions of operator position would be and is a bug.

More over ...

infix operator |/ : DefaultPrecedence
func |/ (lval: Int = 0, rval: Int = 0) -> Int { // ok, compiler says declaration is allowed
    return lval + rval
}
print (
   1 |/, //error, expected expresssion after operator
    |/ 1, //error, not a prefix unary
    |/ // (Function)
)
1 Like

The note you quoted is from Kotlin's documentation, not Swift's.

You seem to be confusing lexical parsing and runtime behavior. The stdlib prints the description of function references as (Function). That is runtime behavior and has nothing to do with how the file was parsed. It's also correct, as |><> is obviously a function.

That's your opinion.

I don't know what you think is a bug.

This may well be a bug. Or something we should change. There's no value in allowing default values in operator function declarations. That there might be an oversight such as this does not negate the flexibility of operators and Swift's chosen design.

3 Likes

Since it is allowed to have default arguments in the declaration of this operator then compiler should have had transformed it into a function call, not a function reference.

If you have some stronger argument like 'it would make language' type system unsound', than why don't you just tell us. Plausibility is a different matter than implementation complexity. And you potential concern that there are people on non-darwin who programm in terminals or something still does not invalidate the main idea.

Also why do you mean by 'more flexible'? What is this even mean?

First, that still has nothing to do with parsing, and second, a bare reference (no parentheses) is just that -- a reference. It's not a function call.

You cannot apply a parenthesis to an operator. Check it out.