1

I am trying to implement a basic protocol extension like so:

protocol Value {
    func get() -> Float
    mutating func set(to:Float)
}
extension Value {
    static func min(of a:Value, and b:Value) -> Float {
        if a < b { //Expression type 'Bool' is ambiguous without more context
            return a.get()
        }else{
            return b.get()
        }
    }
    static func < (a:Value, b:Value) -> Bool {
        return a.get() < b.get()
    }
}

At the if clause the compiler says:Expression type 'Bool' is ambiguous without more context. Why doesn't this work?

gloo
  • 2,490
  • 3
  • 22
  • 38

3 Answers3

1

You can't write

if a < b {

because a and b have type Value which is NOT Comparable.

However you can compare the float value associated to a and b

if a.get() < b.get() {
Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • 1
    So defining the static function `<` for `Value` does not make them comparable? – gloo Dec 21 '17 at 19:37
  • 1
    @gloo There are several reasons why it will not work. Take a look at [this](https://stackoverflow.com/questions/24334684/how-do-i-implement-swifts-comparable-protocol). – Luca Angeletti Dec 21 '17 at 19:38
  • "So defining the static function < for Value does not make them comparable" No, it does not. Protocols are not adopted implicitly. You have to declare adoption explicitly. You would then _still_ need a definition of `<` of course. – matt Dec 21 '17 at 21:19
  • You *can* write a `<` overload without conforming the operands to `Comparable` (just not the reverse). OP's problem is a bit more subtle; their implementation of the overload as a `static` member isn't valid because they haven't given the compiler a way to infer `self`. – Hamish Dec 21 '17 at 21:29
1

As touched on in this Q&A, there's a difference between operator overloads implemented as static members and operator overloads implemented as top-level functions. static members take an additional (implicit) self parameter, which the compiler needs to be able to infer.

So how is the value of self inferred? Well, it has to be done from either the operands or return type of the overload. For a protocol extension, this means one of those types needs to be Self. Bear in mind that you can't directly call an operator on a type (i.e you can't say (Self.<)(a, b)).

Consider the following example:

protocol Value {
  func get() -> Float
}

extension Value {
  static func < (a: Value, b: Value) -> Bool {
    print("Being called on conforming type: \(self)")
    return a.get() < b.get()
  }
}

struct S : Value {
  func get() -> Float { return 0 }
}

let value: Value = S()
print(value < value) // Ambiguous reference to member '<'

What's the value of self in the call to <? The compiler can't infer it (really I think it should error directly on the overload as it's un-callable). Bear in mind that self at static scope in a protocol extension must be a concrete conforming type; it can't just be Value.self (as static methods in protocol extensions are only available to call on concrete conforming types, not on the protocol type itself).

We can fix both the above example, and your example by defining the overload as a top-level function instead:

protocol Value {
  func get() -> Float
}

func < (a: Value, b: Value) -> Bool {
  return a.get() < b.get()
}

struct S : Value {
  func get() -> Float { return 0 }
}

let value: Value = S()
print(value < value) // false

This works because now we don't need to infer a value for self.

We could have also given the compiler a way to infer the value of self, by making one or both of the parameters take Self:

protocol Value {
  func get() -> Float
}

extension Value {
  static func < (a: Self, b: Self) -> Bool {
    print("Being called on conforming type: \(self)")
    return a.get() < b.get()
  }
}

struct S : Value {
  func get() -> Float { return 0 }
}

let s = S()
print(s < s)

//  Being called on conforming type: S
//  false

The compiler can now infer self from the static type of operands. However, as said above, this needs to be a concrete type, so you can't deal with heterogenous Value operands (you could work with one operand taking a Value; but not both as then there'd be no way to infer self).


Although note that if you're providing a default implementation of <, you should probably also provide a default implementation of ==. Unless you have a good reason not to, I would also advise you make these overloads take homogenous concrete operands (i.e parameters of type Self), such that they can provide a default implementation for Comparable.

Also rather than having get() and set(to:) requirements, I would advise a settable property requirement instead:

// Not deriving from Comparable could be useful if you need to use the protocol as
// an actual type; however note that you won't be able to access Comparable stuff,
// such as the auto >, <=, >= overloads from a protocol extension.
protocol Value {
  var floatValue: Double { get set }
}

extension Value {

  static func == (lhs: Self, rhs: Self) -> Bool {
    return lhs.floatValue == rhs.floatValue
  }

  static func < (lhs: Self, rhs: Self) -> Bool {
    return lhs.floatValue < rhs.floatValue
  }
}

Finally, if Comparable conformance is essential for conformance to Value, you should make it derive from Comparable:

protocol Value : Comparable {
  var floatValue: Double { get set }
}

You shouldn't need a min(of:and:) function in either case, as when the conforming type conforms to Comparable, it can use the top-level min(_:_:) function.

Hamish
  • 78,605
  • 19
  • 187
  • 280
0

If you want to be able to make types that can use operators such as >, <, ==, etc., they have to conform to the Comparable protocol:

 protocol Value: Comparable {
    func get() -> Float
    mutating func set(to: Float)
}

This comes with more restrictions though. You will have to change all the Value types in the protocol extension to Self:

extension Value {
    static func min(of a: Self, and b: Self) -> Float {
        if a < b { //Expression type 'Bool' is ambiguous without more context
            return a.get()
        }else{
            return b.get()
        }
    }

    static func < (a: Self, b: Self) -> Bool {
        return a.get() < b.get()
    }
}

The Self types get replaced with the type that implements the protocol. So if I implemented Value on a type Container, the methods signatures would look like this:

class Container: Value {
    static func min(of a: Container, and b: Container) -> Float

    static func < (a: Container, b: Container) -> Bool
}

As a side note, if you want Value to conform to Comparable, you might want to also add the == operator to the Value extension:

static func <(lhs: Self, rhs: Self) -> Bool {
    return lhs.get() < rhs.get()
}
Caleb Kleveter
  • 11,170
  • 8
  • 62
  • 92