Why must a protocol operator be implemented as a global function?
Asked Answered
F

5

30

I've seen the answer to this Swift Equatable Protocol question that mentions how the == method must be declared in the global scope.

If I don't adopt Equatable, I still could declare == to test for equality between two of my types.

// extension Foo: Equatable {}

func ==(lhs: Foo, rhs: Foo) -> Bool {
    return lhs.bar == rhs.bar
}

struct Foo {
    let bar:Int
}

The fact that its implementation needs to be declared at a global scope, makes it seem incidental to and distinct from a protocol, even if Equatable was adopted.

How is the Equatable protocol anything more than syntactic sugar that merely lets (us and) the compiler safely know that our type implemented the required method of the protocol?

Why does the operator implementation have to be globally declared, even for a protocol? Is this due to some different way that an operator is dispatched?

Favianus answered 6/2, 2016 at 20:19 Comment(1)
It's not overriding, it's called overloading. It declares the same operator for different types. Operators have to be declared on the global level, that's a requirement of the language.Warble
A
23

UPDATE

From the Xcode 8 beta 4 release notes:

Operators can be defined within types or extensions thereof. For example:

struct Foo: Equatable {
    let value: Int
    static func ==(lhs: Foo, rhs: Foo) -> Bool {
        return lhs.value == rhs.value
    }
}

Such operators must be declared as static (or, within a class, class final), and have the same signature as their global counterparts. As part of this change, operator requirements declared in protocols must also be explicitly declared static:

protocol Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool
}

ORIGINAL

This was discussed on the swift-evolution list recently (2016-01-31 through 2016-02-09 so far). Here's what Chris Lattner said, regarding declaring operators in a struct or class scope:

Yep, this is a generally desirable feature (at least for symmetric operators). This would also be great to get dynamic dispatch of operators within class declarations. I don’t think we have a firm proposal nailing down how name lookup works with this though.

And later (replying to Haravikk):

What are the name lookup issues? Do you mean cases where an operator for Foo == Foo exists in more than one location?

Yes. Name lookup has to have a well defined search order, which defines shadowing and invalid multiple definition rules.

Personally I’d just stick with what we have now, i.e- treat operator implementations within a specific class/struct as being globally defined anyway and throw an error if the same signature is declared more than once.

We need multiple modules to be able to define instances of an operator, we need operators in extensions, and we need retroactive conformance to work, as with any other member.

Actinium answered 10/2, 2016 at 20:34 Comment(0)
W
7

Explanation from the documentation

The operator function is defined as a global function with a function name that matches the operator to be overloaded.

The function is defined globally, rather than as a method on the target class or structure, so that it can be used as an infix operator between existing instances of the target class or structure.

I replaced the name of the concrete structure (Vector2D) in the quotation by a generic expression target class or structure

Warble answered 10/2, 2016 at 20:26 Comment(0)
C
7

The fact that its implementation needs to be declared at a global scope, makes it seem incidental to and distinct from a protocol, even if Equatable was adopted.

That is true of every protocol, whether it requires global functions, class methods, or instance methods. You can always implement things independently of whether there is a protocol that is requiring it.

How is the Equatable protocol anything more than syntactic sugar that merely lets (us and) the compiler safely know that our type implemented the required method of the protocol?

That's not sugar. That's the definition of a protocol and the entire point of protocols. It tells you that this type has available these things that can be applied to it.

Why does the operator implementation have to be globally declared, even for a protocol? Is this due to some different way that an operator is dispatched?

Because that's Swift syntax. Operator function implementations have to be global functions. There has been interest from the Swift team to change this to make it more consistent, but today this is just how Swift works.

Comp answered 10/2, 2016 at 20:35 Comment(0)
S
1

It was somewhat of a sugar before ie because you needed to manually do the implementation of Equatable. Though Rob Napier brought up a good point that that's what protocols are for.

But that's no longer the case ie just by conformance you get Automated Synthesis and no longer need the boilerplate code.

struct Country: Equatable {
  let name: String
  let capital: String
  var visited: Bool
}

That’s it! The compiler will do the rest.

let france = Country(name: "France", capital: "Paris", visited: true)
let spain = Country(name: "Spain", capital: "Madrid", visited: true)
if france == spain { ... } // false

That being said if you want you can still override the implementation of the == function.

For more see here

Schroth answered 22/8, 2018 at 20:43 Comment(1)
R
0

In the Swift standard library, the '==' operator is defined. It doesn't have associativity for one side or the other of the comparison and has a numbered precedence in the overall Swift operator order. You can examine this if you CMD-click Swift on 'import Swift'

infix operator == {
    associativity none
    precedence 130
}

For Int's, the library already has Int/Int8/Int16/Int32/Int64 as Equatable:

public func ==(lhs: Int, rhs: Int) -> Bool

as it does for many other types.

Int is made Hashable in an extension (where to conform to that protocol, you need to create a var hashValue: Int { get } that changes for each execution of the app:

extension Int : Hashable {
    public var hashValue: Int { get }
}

The operator '==' and protocols, in general, need to be declared outside struct/enum/class at top-level because you are extending the language and for now, that's how swift is compiled; all Swift's operators are attributes defined at global scope.

For your 'Foo', you adopted Equatable by adding the func:

func ==(lhs: Foo, rhs: Foo) -> Bool {
    return lhs.bar == rhs.bar
}

which uses the 'Self' local variables in the protocol:

public protocol Equatable {
    public func ==(lhs: Self, rhs: Self) -> Bool
}
Rowe answered 15/2, 2016 at 1:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.