Type Check Operator (Is) for Check VS Homogenous Protocol: Why Can This Be Done for Optionals

Array of protocol type

What you need to do is to use type erasure, much like AnyHashable does in the Swift Standard Library.

You can't do:

var a: [Hashable] = [5, "Yo"]
// error: protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements

What you have to do is to use the type-erased type AnyHashable:

var a: [AnyHashable] = [AnyHashable(5), AnyHashable("Yo")]
a[0].hashValue // => shows 5 in a playground

So your solution would be to first split the protocol in smaller parts and promote Equatable to Hashable (to reuse AnyHashable)

protocol Conditionable {
var condition: Condition? { get set }
}

protocol Executable {
func execute() -> SKAction
}

protocol Commandable: Hashable, Executable, Conditionable {}

Then create an AnyCommandable struct, like this:

struct AnyCommandable: Commandable, Equatable {
var exeBase: Executable
var condBase: Conditionable
var eqBase: AnyHashable

init<T: Commandable>(_ commandable: T) where T : Equatable {
self.condBase = commandable
self.exeBase = commandable
self.eqBase = AnyHashable(commandable)
}

var condition: Condition? {
get {
return condBase.condition
}
set {
condBase.condition = condition
}
}

var hashValue: Int {
return eqBase.hashValue
}

func execute() -> SKAction {
return exeBase.execute()
}

public static func ==(lhs: AnyCommandable, rhs: AnyCommandable) -> Bool {
return lhs.eqBase == rhs.eqBase
}
}

And then you can use it like this:

var a = FunctionCommand()
a.commands = [AnyCommandable(MoveCommand()), AnyCommandable(FunctionCommand())]

And you can easily access properties of commands, because AnyCommandable implements Commandable

a.commands[0].condition

You need to remember to now add Hashable and Equatable to all your commands.
I used those implementations for testing:

struct MoveCommand: Commandable {

var movingVector: CGVector!

var condition: Condition?
func execute() -> SKAction {
return SKAction()
}

var hashValue: Int {
return Int(movingVector.dx) * Int(movingVector.dy)
}

public static func ==(lhs: MoveCommand, rhs: MoveCommand) -> Bool {
return lhs.movingVector == rhs.movingVector
}
}

struct FunctionCommand: Commandable {
var commands = [AnyCommandable]()

var condition: Condition?

func execute() -> SKAction {
return SKAction.group(commands.map { $0.execute() })
}

var hashValue: Int {
return commands.count
}

public static func ==(lhs: FunctionCommand, rhs: FunctionCommand) -> Bool {
return lhs.commands == rhs.commands
}
}

How to evaluate equality for homogeneous collections?

Some good solutions to the general problem have already been given – if you just want a way to compare two Car values for equality, then overloading == or defining your own equality method, as shown by @Vyacheslav and @vadian respectively, is a quick and simple way to go. However note that this isn't an actual conformance to Equatable, and therefore won't compose for example with conditional conformances – i.e you won't be able to then compare two [Car] values without defining another equality overload.

The more general solution to the problem, as shown by @BohdanSavych, is to build a wrapper type that provides the conformance to Equatable. This requires more boilerplate, but generally composes better.

It's worth noting that the inability to use protocols with associated types as actual types is just a current limitation of the Swift language – a limitation that will likely be lifted in future versions with generalised existentials.

However it often helps in situations like this to consider whether your data structures can be reorganised to eliminate the need for a protocol to begin with, which can eliminate the associated complexity. Rather than modelling individual manufacturers as separate types – how about modelling a manufacturer as a type, and then have a property of this type on a single Car structure?

For example:

struct Car : Hashable {
struct ID : Hashable {
let rawValue: Int
}
let id: ID

struct Manufacturer : Hashable {
var name: String
var country: String // may want to consider lifting into a "Country" type
}
let manufacturer: Manufacturer
let name: String
}

extension Car.Manufacturer {
static let bmw = Car.Manufacturer(name: "BMW", country: "Germany")
static let toyota = Car.Manufacturer(name: "Toyota", country: "Japan")
}

extension Car {
static let bmwX6 = Car(
id: ID(rawValue: 101), manufacturer: .bmw, name: "X6"
)
static let toyotaPrius = Car(
id: ID(rawValue: 102), manufacturer: .toyota, name: "Prius"
)
}

let cars: [Car] = [.bmwX6, .toyotaPrius]
print(cars[0] != cars[1]) // true

Here we're taking advantage of the automatic Hashable synthesis introduced in SE-0185 for Swift 4.1, which will consider all of Car's stored properties for equality. If you want to refine this to only consider the id, you can provide your own implementation of == and hashValue (just be sure to enforce the invariant that if x.id == y.id, then all the other properties are equal).

Given that the conformance is so easily synthesised, IMO there's no real reason to just conform to Equatable rather than Hashable in this case.

A couple of other noteworthy things in the above example:

  • Using a ID nested structure to represent the id property instead of a plain Int. It doesn't make sense to perform Int operations on such a value (what does it mean to subtract two identifiers?), and you don't want to be able to pass a car identifier to something that for example expects a pizza identifier. By lifting the value into its own strong nested type, we can avoid these issues (Rob Napier has a great talk that uses this exact example).

  • Using convenience static properties for common values. This lets us for example define the manufacturer BMW once and then re-use the value across different car models that they make.

Swift protocol conformance when returning a generic

A)

T is a type, a homogenous concrete type specified at runtime.

Imaging T is class Foo : FeedItem it's obvious that FeedItemModel cannot be converted to Foo

B)

FeedItemModel is recognized as conforming to FeedItem but this is irrelevant.


It's often a mix-up of generics and protocols. Generic types are not covariant. If you need covariant types use an associated type.

Why does the following protocol have this required function?

The problem being solved is a limitation of generics.

Let's say we have a Bird struct and Insect struct. A generic equatable lets us define == where the actual object types are the same type. So we can make Bird adopt Equatable so that if we have b1 and b2 both typed as Bird we can decide whether they are equal. And we can make Insect adopt Equatable so that if we have i1 and i2 both typed as Insect we can decide whether they are equal.

But now suppose both Bird and Insect adopt the Flier protocol. You cannot make Flier adopt Equatable, because there's a limitation in how generics work. So if two objects are typed as Flier, you have no way of implementing equatability for them.

The video demonstrates that protocol extensions solve this problem. With a protocol extension on Flier, you can define a different method that compares two Fliers and decides whether they are equal - namely, by deciding first whether they are of the same class and then applying ==. Thus you can make sense of equatability for a Flier (a protocol).

Make Swift protocol conform to Equatable on associated type

Add -> Bool. Just a bad error message. Sometimes writing function declaration across multiple lines doesn't make it more readable.

public func ==<Node1 : GraphNode, Node2 : GraphNode where Node1.Content == Node2.Content>(lhs: Node1, rhs: Node2) -> Bool {

return (lhs.content == rhs.content)

}

Type annotations for *args and **kwargs

For variable positional arguments (*args) and variable keyword arguments (**kw) you only need to specify the expected value for one such argument.

From the Arbitrary argument lists and default argument values section of the Type Hints PEP:

Arbitrary argument lists can as well be type annotated, so that the definition:

def foo(*args: str, **kwds: int): ...

is acceptable and it means that, e.g., all of the following represent function calls with valid types of arguments:

foo('a', 'b', 'c')
foo(x=1, y=2)
foo('', z=0)

So you'd want to specify your method like this:

def foo(*args: int):

However, if your function can only accept either one or two integer values, you should not use *args at all, use one explicit positional argument and a second keyword argument:

def foo(first: int, second: Optional[int] = None):

Now your function is actually limited to one or two arguments, and both must be integers if specified. *args always means 0 or more, and can't be limited by type hints to a more specific range.

Using a Type Variable in a Generic

Swift's static typing means the type of a variable must be known at compile time.

In the context of a generic function func foo<T>() { ... }, T looks like a variable, but its type is actually known at compile time based on where the function is called from. The behavior of Array<T>() depends on T, but this information is known at compile time.

When using protocols, Swift employs dynamic dispatch, so you can write Array<MyProtocol>(), and the array simply stores references to things which implement MyProtocol — so when you get something out of the array, you have access to all functions/variables/typealiases required by MyProtocol.

But if t is actually a variable of kind Any.Type, Array<t>() is meaningless since its type is actually not known at compile time. (Since Array is a generic struct, the compiler needs know which type to use as the generic parameter, but this is not possible.)

I would recommend watching some videos from WWDC this year:

  • Protocol-Oriented Programming in Swift
  • Building Better Apps with Value Types in Swift

I found this slide particularly helpful for understanding protocols and dynamic dispatch:

Sample Image



Related Topics



Leave a reply



Submit