Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Precedence for custom operator in relation to dot (".") literal

In Swift 3, I have written a custom operator prefix operator § which I use in a method taking a String as value returning a LocalizedString struct (holding key and value).

public prefix func §(key: String) -> LocalizedString {
    return LocalizedString(key: key)
}

public struct LocalizedString {
    public var key: String
    public var value: String

    public init(key: String) {
        let translated = translate(using: key) // assume we have this
        self.key = key
        self.value = translated ?? "!!\(key)!!"
    }
}

(Yes I know about the awesome L10n enum in SwiftGen, but we are downloading our strings from our backend, and this question is more about how to work with custom operators)

But what if we wanna get the translated value from the result of the § operator (i.e. the property value from the resulting LocalizedString)

let translation = §"MyKey".value // Compile error "Value of type 'String' has no member 'value'"

We can of course easily fix this compile error by wraping it in parenthesis (§"MyKey").value. But if do not want to do that. Is it possible to set precedence for custom operators in relationship to the 'dot' literal?

Yes I know that only infix operators may declare precedence, but it would make sense to somehow work with precedence in order to achieve what I want:

precedencegroup Localization { higherThan: DotPrecedence } // There is no such group as "Dot"
prefix operator §: Localization

To mark that the Swift compiler first should evaluate §"MyKey" and understand that is not a string, but in fact an LocalizedString (struct).

Feels unlikely that this would be impossible? What am I missing?

like image 688
Sajjon Avatar asked Dec 02 '16 13:12

Sajjon


1 Answers

The . is not an operator like all the other ones defined in the standard library, it is provided by the compiler instead. The grammar for it are Explicit Member Expressions.

Having a higher precedence than the . is nothing the compiler should enable you to do, as it's such a fundamental use case. Imagine what you could do if the compiler enabled such a thing:

-"Test".characters.count

If you could have a higher precedence than ., the compiler has to check all possibilities:

(-"Test").characters.count   // func -(s: String) -> String
(-("Test".characters)).count // func -(s: String.CharacterView) -> String.CharacterView
-("Test".characters.count)   // func -(s: Int) -> Int

Which would

  • Potentially increase the compile time a lot
  • Be ambiguous
  • Possibly change behaviour of existing code upon adding overloads

What I suggest you to do is abandon the idea with a new operator, it's only going to be adding more cognitive load by squashing some specific behaviour into a single obscure character. This is how I'd do it:

extension String {
    var translatedString : String {
        return translate(using: self)
    }
}

"MyKey".localizedString

Or if you want to use your LocalizedString:

extension String {
    var localized : LocalizedString {
        return LocalizedString(key: self)
    }
}

"MyKey".localized.value

These versions are much more comprehensive.

like image 92
Silvan Mosberger Avatar answered Sep 24 '22 05:09

Silvan Mosberger