How to implement a default method for a custom infix operator in a protocol extension - swift

I'm trying to implement a custom comparison infix operator ==^ as a slimmed down version of the standard equality operator ==
My app is mostly protocol oriented and therefor I'm trying to implement the default method static func ==^ inside the protocol extension. However when I make my class conform to the protocol I'm getting a Type 'MySourceNode' does not conform to protocol 'SourceNodeType' error and Xcode offers me to add the static func ==^ protocol stub.
My question is, how do I properly write the default implementation inside the protocol extension?
I've tried to find answers on SO but most of them are older and only talk about generic methods that are defined outside the protocol extension. But that doesn't seem to work in my case.
Here is a playground file with a simplified version of my protocol. There is a bit of background info after the code. If something is unclear please let me know in the comments and I'll update my question accordingly.
import Foundation
import SpriteKit
infix operator ==^: ComparisonPrecedence
protocol SourceNodeType: SKShapeNode {
var constrainZRotation: SKConstraint! { get set }
func setConstraints()
static func ==^ (lhs: SourceNodeType, rhs: SourceNodeType) -> Bool
}
extension SourceNodeType {
func setConstraints() {
print("Setting constraints")
}
static func ==^ (lhs: SourceNodeType, rhs: SourceNodeType) -> Bool {
lhs.frame == rhs.frame &&
lhs.position == rhs.position &&
lhs.constrainZRotation == rhs.constrainZRotation
}
}
class MySourceNode: SKShapeNode, SourceNodeType {
var constrainZRotation: SKConstraint!
}
To explain the reason behind this custom infix operator. I need it because I'm comparing subclasses of SpriteKit's SKShapeNodes with each other quite often. But I don't want every variable in the class like the accessibility label or name to be compared as I'm changing these values when I added the nodes to the scene, which is partially after the comparison.

I realized putting the func ==^ method outside of the protocol and only implementing it with the SourceNodeType protocol as rhs and lhs types was exactly what I needed!
This answer solved my problem:
https://stackoverflow.com/a/41390111/12764795
Maybe someone could confirm that this is the current and correct way to go?
import Foundation
import SpriteKit
infix operator ==^: ComparisonPrecedence
func ==^ (lhs: SourceNodeType, rhs: SourceNodeType) -> Bool {
lhs.frame == rhs.frame &&
lhs.position == rhs.position &&
lhs.constrainZRotation == rhs.constrainZRotation
}
protocol SourceNodeType: SKShapeNode {
var constrainZRotation: SKConstraint! { get set }
func setConstraints()
}
extension SourceNodeType {
func setConstraints() {
print("Setting constraints")
}
}
class MySourceNode: SKShapeNode, SourceNodeType {
var constrainZRotation: SKConstraint!
}
let lhsSourceNode = MySourceNode(circleOfRadius: 10)
var rhsSourceNode = MySourceNode(circleOfRadius: 10)
print(lhsSourceNode ==^ rhsSourceNode) // TRUE
rhsSourceNode.name = "Name used for testing"
print(lhsSourceNode ==^ rhsSourceNode) // TRUE
rhsSourceNode.position.y += 100
print(lhsSourceNode ==^ rhsSourceNode) // FALSE

The thing is, you just can't satisfy this kind of protocol requirements :
static func ==^ (lhs: SourceNodeType, rhs: SourceNodeType) -> Bool
I could not find a clear explanation in Swift Documentation but if you try to make a type conform to that protocol it won't compile and it's not supposed to.
My question is, how do I properly write the default implementation inside the protocol extension?
If you want to use parameters of type SourceNodeType, using a free function is the most straightforward way but removing the operator requirement silences the compiler error ie :
protocol SourceNodeType: SKShapeNode {
var constrainZRotation: SKConstraint! { get set }
func setConstraints()
}
until you try to use your operator :
let node = MySourceNode()
let otherNode = MySourceNode()
node ==^ otherNode // Generic parameter 'Self' could not be inferred
You can still work around this issue by declaring :
extension SourceNodeType {
static func ==^ (lhs: Self, rhs: SourceNodeType) -> Bool {
lhs.frame == rhs.frame &&
lhs.position == rhs.position &&
lhs.constrainZRotation == rhs.constrainZRotation
}
}
it will work as expected but you still will not be able to declaring it as a protocol requirement because it will trigger :
Protocol 'SourceNodeType' can only be used as a generic constraint because it has Self or associated type requirements
Since rhs is of type SourceNodeType.

Related

Operator function '==' requires that 'Entry' conform to 'Equatable'

I have some code using if which i want to write in one line to make it compact ..entry is enum
if Session.shared.input?.entry == .confirm {
isButtonValidate = true
}
I want to make it optimised as
isButtonValidate = Session.shared.input?.entry == .confirm // Operator function '==' requires that 'Entry' conform to 'Equatable
what is the way to make it correct.
Thank You for help.
you could try this approach, especially if you do not have access to Entry:
extension Entry: Equatable {
static func == (lhs: Entry, rhs: Entry) -> Bool {
lhs.id == rhs.id // <-- here, whatever is appropriate for you
}
}

How can I update this Hashable.hashValue to conform to new requirements.?

I'm trying to fix up an old tutorial from RayWenderlich's site, no longer supported.
The warning appears in three files, Chain.swift, Cookie.swift and Swap.swift from the "How to Make a Game Like Candy Crush with SpriteKit and Swift:" tutorial
I'm at a loss even after exploring the available replies that appear in many places. I'm struggling to understand just what this code is doing so that I can fix it. I know it's just a warning, and I can probably ignore it, but the game is also showing X where blank tiles should appear so I suspect that it may have something to do with this?
The warning is this:
'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'Chain' to 'Hashable' by implementing 'hash(into:)' instead
File example
class Chain: Hashable, CustomStringConvertible {
var cookies: [Cookie] = []
var score = 0
enum ChainType: CustomStringConvertible {
case horizontal
case vertical
var description: String {
switch self {
case .horizontal: return "Horizontal"
case .vertical: return "Vertical"
}
}
}
var chainType: ChainType
init(chainType: ChainType) {
self.chainType = chainType
}
func add(cookie: Cookie) {
cookies.append(cookie)
}
func firstCookie() -> Cookie {
return cookies[0]
}
func lastCookie() -> Cookie {
return cookies[cookies.count - 1]
}
var length: Int {
return cookies.count
}
var description: String {
return "type:\(chainType) cookies:\(cookies)"
}
var hashValue: Int {
return cookies.reduce (0) { $0.hashValue ^ $1.hashValue }
}
static func ==(lhs: Chain, rhs: Chain) -> Bool {
return lhs.cookies == rhs.cookies
}
}
From the Hashable documentation:
Hashing a value means feeding its essential components into a hash function, represented by the Hasher type. Essential components are those that contribute to the type’s implementation of Equatable. Two instances that are equal must feed the same values to Hasher in hash(into:), in the same order.
And from the hash(into:) documentation:
The components used for hashing must be the same as the components compared in your type’s == operator implementation. Call hasher.combine(_:) with each of these components.
The implementation of
static func ==(lhs: Chain, rhs: Chain) -> Bool {
return lhs.cookies == rhs.cookies
}
shows that cookies is the “essential component” which determines equality of instances. Therefore
func hash(into hasher: inout Hasher) {
hasher.combine(cookies)
}
is a valid (and sensible) implementation of the Hashable requirement.

Swift protocols and equatable

I'm still learning how to work with arrays of objects implementing protocols with associated types.
I have the following protocols:
public protocol Word : Equatable, Hashable { // compiles
associatedtype WordType : Equatable
var moreWords: [WordType] { get }
}
public protocol WordDataSource { // compiles
associatedtype SomeWord : Word
func findWord(spelling: String) -> SomeWord?
}
I have WordA, WordB and WordC all implementing Word and subclassing NSObject
Basically, I want to implement the datasource protocol using different kinds of class implementing the Word class. This is the kind of code I would like to write, but obviously it doesn't compile.
class MyDataSource : WordDataSource {
func findWord(spelling: String) -> SomeWord? {
if conditionA {
return WordA()
}
if conditionB {
return WordB()
}
if conditionA {
return WordC()
}
}
}
Is that even possible in Swift? What should I write to make that work?
Thanks a lot for your help!
This is not possible, and it's not possible for a reason. Let's assume that your class MyDataSource does compile. Now, we could write such code:
let fooWord = MyDataSource().findWord(spelling: "Foo") // Would return WordA instance
let barWord = MyDataSource().findWord(spelling: "Bar") // Would return WordB instance
but all we know about those two types is that they are of this SomeWord type. So they should be comparable, since Word is comparable, right?
But they're two completely different types, so how would you know how should they be compared? Take a look at the definition of Equatable protocol:
public static func ==(lhs: Self, rhs: Self) -> Bool
You can only compare two objects of the same type that conform to this protocol.

Why does operator in Protocol `need more context`?

I have a protocol that only requires a > function. When I try to compare two objects that comform to the protocol I gives me a compiler error with the message "Type of expression is ambiguous without more context". I'd rather not require the function isGreaterThan(...) (or something) if I can avoid it, I'd rather stick to what people are used to using to compare (>).
So I have 2 questions. Why can't I do this? Is there a better way to do this, or a good workaround?
Thanks!
protocol Compare {
func >(lhs: Compare, rhs: Compare) -> Bool
}
class TheClass {
func hey(aCompare: Compare, theCompare: Compare) {
if aCompare > theCompare {
print("aCompare is greater than theCompare")
}
}
}
Rather than using the Protocol in the > operator, I should be using Self like Apple's Equatable and Comparable protocols.
protocol Compare {
func >(lhs: Self, rhs: Self) -> Bool
}
class TheClass {
func hey(aCompare: Compare, theCompare: Compare) {
if aCompare > theCompare {
print("aCompare is greater than theCompare")
}
}
}
Now compiles.. Thanks #jrturton and #originaluser2

Operator overloading not yet supported?

According to the Swift Programming Guide, operator overloading is allowed and actually quite versatile. However, I have been unable to get it working in the playground.
For example, the Equatable protocol wants this: func ==(lhs:Self, rhs:Self) -> Bool
Let's say I make a simple Location3D struct:
struct Location3D
{
var x : Double
var y : Double
var z : Double
}
Now I want this Location3D to implement the Equatable protocol, so I add it along with this method:
func ==(lhs: Self, rhs: Self) -> Bool
{
return lhs.x == rhs.x &&
lhs.y == rhs.y &&
lhs.z == rhs.z
}
I get the compiler error of operators are only allowed at global scope. Huh?
So I tried adding #infix to the function, moving the function to an extension, changing the type to a class instead... all to no avail.
Am I missing something? How are you supposed to implement Equtable and Comparable when operators don't seem to work?
You need to override the == operator in the global scope but with your type for the arguments.
In this case it means you declare your struct to conform to the protocol and then simply implement the function outside it's scope.
struct Location3D : Equatable {
// ...
}
func ==(lhs: Location3D, rhs: Location3D) -> Bool {
// ...
}
See the library reference for further discussion:
https://developer.apple.com/documentation/swift/equatable