Allow @ as prefix operator

I was wondering if we could allow @ as a prefix operator - what would be the impacts. My guess is that this is currently disallowed due to @ prefixing annotations, but the annotations are not randomly within code, they are only on places where usage of an operator would be invalid anyway.

Motivation for this - as someone who deals with URLs a lot, it comes a point in time where you are sick of typing URL(string: ...). So I started experimenting with a prefix operator that would create a URL out of a string - ideally, it would be @ as the symbol (at least for me) symbolizes internet/online/web/... The general usage would be:

@"http://apple.com" -> URL? (@ + String -> URL?)
@urlString -> URL?

@!"http://apple.com" -> URL (@! + String -> URL)
@!urlString -> URL

Ideally, with StaticString, this would be checked at compile time, which would perhaps satisfy the long-time longing for URL literals (which has been discussed here multiple times). I.e.:

prefix func @(urlString: String) -> URL? // Just a shortcut for URL(string:)
prefix func @(urlString: StaticString) -> URL // Compile-time check of the static string

I'm currently playing with this using ~ instead of @ and it's fairly neat to use, so I'm wondering if there'd be more people interested in seeing something like this...

1 Like

I really doubt we'll ever make @"stuff" a valid syntax; we have a custom error to help Objective-C users who type the @ out of habit, and we don't really want to give that up.

That's fun! :// would have been nice, too, but colon is not allowed either, and // triggers comments :'(

let url = ://urlString

Some unicode fun:

let url = ➊urlString
let url = ↱urlString
let url = ⇗urlString
let url = ⏈urlString

I would say that a bigger issue than Objective-C users is that it conflicts with the syntax for attributes. You can say it's not currently ambiguous, but that could theoretically change in a world with custom attributes, and I don't see a great reason to overload the meaning of the symbol just for a custom prefix operator. Wouldn't you be better off just conforming URL to ExpressibleByStringLiteral instead?

2 Likes

If you have let someString = @"some", then "some" is a StaticString and as I've mentioned, StaticString would get validated at compile time, so you'd actually get an error 'some' is not a valid URL. with a possible fix-it suggestion to remove the @ sign. So this is not something that would be interfering with the existing errors or fix-its.

This doesn't have a failable initializer. So let url = myURLString as URL would crash at runtime without explicitely using as! which I don't think is correct at all.

Even custom annotations would, however, IMHO be above declarations where it's not ambigous. Do you have an example from another language where you can place an annotation to such a place that it'd be ambiguous?

Well, ExpressibleByStringLiteral would only take string literals, so let url = myURLString as URL wouldn't work. I don't see a problem with crashing at runtime for string literals, like your "http://apple.com" example . Putting aside the use of @ as an operator, I think the compile time verification part of your post is interesting, and something to consider as part of the compile-time evaluation features that have been discussed in other threads.

I considered leaving that part out because it wasn't particularly important and I don't want to argue that they are likely to conflict. Even if it isn't technically ambiguous, and will never be in the future, I don't see a great argument for creating confusion by overloading the meaning of @ like this. It's simpler if @ always means attribute and you don't have to look at @escaping and try to work out from context if it is an attribute or a prefix operator and a variable called escaping.

1 Like

While I agree that this syntax is just complex enough to be annoying to type a lot, it doesn't look nearly complex enough to be a plausible justification for introducing new syntax. By contrast, the drawbacks include:

  • It's yet another piece of arbitrary symbology whose meaning has to be learned. (The connotations of the @ symbol do help, but it's not a slam-dunk.)
  • Whiling you're thinking about this use-case, it seems worth improving, but in the universe of things that might be Made Better Through Syntaxℱ, it doesn't obviously stand out as specially deserving.

As with (say) closures typed literally within parentheses in a parameter list, I think that much of the "load" comes from the double nesting ("..." and (...) in this case). One possible alternative that isn't nested and doesn't require any new syntax would be an extension on String:

    "https://apple.com".url // or "...".asURL

This is really easy to type, and has the virtue of meaning what it looks like it means.

1 Like

I'm not sure inventing an operator for these operations saves that much. I'd rather we just drop the string: label from the URL initializer—it seems like a violation of "omit needless words" and it's surely a "value-preserving conversion" according to Swift's API design guidelines (the fact that it's failable is fine; Int(_: String) is also like this). Then writing URL("...") would be clearer and not much worse than the proposed @ prefix (or any other operator).

Then, once compiler-evaluable expressions are available and powerful enough to support the algorithm used to validate the string, the URL could also get a compiler-evaluable ExpressibleFromStringLiteral conformance, and then we could just write one of these with compile-time verification:

let url = "http://apple.com" as URL
let url: URL = "http://apple.com"
9 Likes

isn’t the :// part of the url?

I was just looking for another mnemonic operator: ://String -> URL?;-)

1 Like

but you would need to specify the protocol which comes before the :// for a complete url, no?

Without commenting on the use cases, it's not quite true that @-as-operator and @-for-attributes is unambiguous.

var global = "abc"
class Messy {
  var x = @global
  @objc func foo() {}
}
5 Likes

@-as-operator would be nice for doing a little DSL around layout constraints:

let c = view.leading  + 20 @ .high