protocol is a kind of type in swift and should be used in everywhere as a type, so why Protocol Composition can't be used in struct in swift?
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// Prints "Happy birthday, Malcolm, you're 21!"
In struct Person, if I change it into
struct Person: Named & Aged {
var name: String
var age: Int
}
the complier error appears: Protocol composition is neither allowed nor needed here
So why I can use Protocol composition in function rather than struct?
Related
I want to add two different types, Pig and Frog, to the same array. Normally this would work well as long as both conform to the same protocol. However, in swiftUI if you want to use that array for a List then those types also need to conform to Identifiable. Like so:
protocol Animal: Identifiable {
var id: String { get }
var name: String { get }
var count: Int { get }
var image: String { get }
}
struct Pig: Animal {
var id: String = UUID().uuidString
var name = "Pig"
var count = 2
var image = "🐷"
}
struct Frog: Animal {
var id: String = UUID().uuidString
var name = "Frog"
var count = 3
var image = "🐸"
}
struct ContentView: View {
var animals: [Animal] = [Pig(), Frog()]
var body: some View {
List(animals) { animal in
Row(animals: animal)
}
}
}
This gives me the error Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements.
So my questions are why is this a problem and how do I get around it?
I experimented a bit and tried using an associated type like this:
protocol ConstructAnimal: View where AnimalType: Animal {
associatedtype AnimalType
var animals: [AnimalType] { get set }
}
But this still only lets me have one type in the array at a time.
I have a use case to compare the enployee's rank. Here is what I want to do:
protocol Enployee: Comparable {
var id: String { get }
var rank: Int { get }
var name: String { get }
var type: String { get }
}
extension Enployee {
static func <(lhs: Enployee, rhs: Enployee) -> Bool {
return lhs.rank < rhs.rank
}
}
But I got the following error:
Protocol 'Enployee' can only be used as a generic constraint because it has Self or associated type requirements
Then I changed my code:
extension Enployee {
static func <(lhs: Self, rhs: Self) -> Bool {
return lhs.rank < rhs.rank
}
}
I can compile it. But when I continue working on my user case:
struct Engineer: Enployee {
var id: String
var rank: Int
var name: String
let type: String = "Engineer"
}
struct Manager: Enployee {
var id: String
var rank: Int
var name: String
let type: String = "Manager"
}
let staff1 = Engineer(id: "123", rank: 2, name: "Joe")
let staff2 = Engineer(id: "124", rank: 2, name: "Frank")
let staff3 = Manager(id: "101", rank: 10, name: "John")
public struct Department<T: Comparable> {
}
let queue = Department<Enployee>()
I got another error message:
Protocol 'Enployee' as a type cannot conform to 'Comparable'
Any idea?
The error message tells you what the problem is. Having declared Department<T: Comparable> you cannot resolve T as Enployee; it is a protocol, not a type conforming to Comparable (such as Engineer).
A generic has to be resolved to one type. It would be legal to declare Department<T: Enployee> if that is what you really mean. But then you are setting yourself up for multiple Department types, a department of Manager and a department of Engineer. That is probably not what you want, so this is a bad use of a generic.
Moreover, type strings are a horrifically bad smell:
struct Engineer: Enployee {
var id: String
var rank: Int
var name: String
let type: String = "Engineer" // uck
}
It seems much more likely that you want a class and subclasses here. Why fight against OOP?
There is this job in Swift 5.0:
The class is presented below. In the body of this class, create a function that will print the parameters of this class for a specific object. Create such an object of class Student, call it this function and display the result on the screen:
Job class
class Student {
var name: String
var surname: String
var yearOfBorn: Int
var mark: Double
init(name: String, surname: String, yearOfBorn: Int, mark: Double) {
self.name = name
self.surname = surname
self.yearOfBorn = yearOfBorn
self.mark = mark
}
}
How i can make it?
I trying:
func printStudent() {
if name == name {
print(name)
} else if surname == surname {
print(surname)
} else if yearOfBorn == yearOfBorn {
print(yearOfBorn)
} else if mark == mark {
print(mark)
}
}
I’m not sure what your intent was with these if statements. Perhaps you are thinking of:
if let foo = foo { ... }
But that technique is only used if foo was an optional. But your properties are not optionals, so if let syntax is unnecessary.
Needless to say, you could just do:
func printStudent() {
print(name)
print(surname)
print(yearOfBorn)
print(mark)
}
FWIW, if your intent is just to print this out for your own purposes, you might want to make your class conform to CustomStringConvertible:
extension Student: CustomStringConvertible {
var description: String { return "<Student name=\(name); surname=\(surname); yearOfBorn=\(yearOfBorn); mark=\(mark)>" }
}
Then you don’t need to write your own printStudent method at all, but can use print directly:
let student = Student(name: "Rob", surname: "Ryan", yearOfBorn: 2000, mark: 4)
print(student)
And that will produce:
<Student name=Rob; surname=Ryan; yearOfBorn=2000; mark=4.0>
Alternatively, if you’re OK with struct value type instead, you don’t need the init method or the CustomStringConvertible protocol, at all. Then you can define Student as simply:
struct Student {
var name: String
var surname: String
var yearOfBorn: Int
var mark: Double
}
And then
let student = Student(name: "Rob", surname: "Ryan", yearOfBorn: 2000, mark: 4)
print(student)
Will produce:
Student(name: "Rob", surname: "Ryan", yearOfBorn: 2000, mark: 4.0)
If you want to print all the attributes of the object you don’t need this if statements; as a matter of fact if you pass name == name as the parameter the first if statement will be always entered and thus the other ones skipped.
You just need to create a function like this where you print each attribute:
func printStudent() {
print(self.name)
print(self.surname)
print(self.yearOfBorn)
...
}
You just need to print the variables:
func printStudent() {
print("Name: \(self.name), Surname: \(self.surname), Year Of Born: \(self.yearOfBorn)")
}
try this code:
func printStudent () {
print("name: \(self.name), surname: \(self.surname), yearOfBorn: \ .
(self.yearOfBorn), mark: \(self.mark)")
}
The case:
Consider the following:
protocol Car {
static var country: String { get }
var id: Int { get }
var name: String { get set }
}
struct BMW: Car {
static var country: String = "Germany"
var id: Int
var name: String
}
struct Toyota: Car {
static var country: String = "Japan"
var id: Int
var name: String
}
Here I have a simple example of how to create an abstraction layer by using a -Car- protocol, thus I am able to declare a heterogeneous collection of cars:
let cars: [Car] = [BMW(id: 101, name: "X6"), Toyota(id: 102, name: "Prius")]
And it works fine.
The problem:
I want to be able to evaluate the equality of the cars (by id), example:
cars[0] != cars[1] // true
So, what I tried to do is to let Car to conforms to Equatable protocol:
protocol Car: Equatable { ...
However, I got the "typical" compile-time error:
error: protocol 'Car' can only be used as a generic constraint because
it has Self or associated type requirements
I am unable to declare cars: [Car] array anymore. If I am not mistaking, the reason behind it is that Equatable uses Self so it would be considered as homogeneous.
How can I handle this problem? Could Type erasure be a mechanism to resolve it?
A possible solution is a protocol extension, instead of an operator it provides an isEqual(to function
protocol Car {
static var country: String { get }
var id: Int { get }
var name: String { get set }
func isEqual(to car : Car) -> Bool
}
extension Car {
func isEqual(to car : Car) -> Bool {
return self.id == car.id
}
}
and use it
cars[0].isEqual(to: cars[1])
Here is solution using Type Erasure:
protocol Car {
var id: Int { get }
var name: String { get set }
}
struct BMW: Car {
var id: Int
var name: String
}
struct Toyota: Car {
var id: Int
var name: String
}
struct AnyCar: Car, Equatable {
private var carBase: Car
init(_ car: Car) {
self.carBase = car
}
var id: Int { return self.carBase.id }
var name: String {
get { return carBase.name}
set { carBase.name = newValue }
}
public static func ==(lhs: AnyCar, rhs: AnyCar) -> Bool {
return lhs.carBase.id == rhs.carBase.id
}
}
let cars: [AnyCar] = [AnyCar(BMW(id: 101, name: "X6")), AnyCar(Toyota(id: 101, name: "Prius"))]
print(cars[0] == cars[1])
Don't know how to implement this with static property. If I figure out, I will edit this answer.
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.
It is possible to override ==:
import UIKit
var str = "Hello, playground"
protocol Car {
static var country: String { get }
var id: Int { get }
var name: String { get set }
}
struct BMW: Car {
static var country: String = "Germany"
var id: Int
var name: String
}
struct Toyota: Car {
static var country: String = "Japan"
var id: Int
var name: String
}
func ==(lhs: Car, rhs: Car) -> Bool {
return lhs.id == rhs.id
}
BMW(id:0, name:"bmw") == Toyota(id: 0, name: "toyota")
I've a class and a protocol into myModel.swift
public protocol IModelArgs{
var name: String { get set};
var surname: String { get set};
}
public class Model {
var name: String;
var surname: String;
init(args: IModelArgs) {
self.name = args.name;
self.surname = args.surname;
}
}
IModelArgs is the protocol of arguments object passed to Model constructor.
Into another file I need to create the instance of Model class, but I'm not able to pass args object to constructor: What I'm wrong?
let myM = Model(args: ("T1","T2"));
The main problem in your case that ("T1","T2") is a tuple and not the object that conform your protocol. In your case it should look like this:
struct ArgsObject: IModelArgs {
var name: String
var surname: String
}
let myM = Model(args: ArgsObject(name: "someName", surname: "someSurname"))
But if you want to use the protocol only to pass an object to the constructor, you do not need to do this. Create struct for it like this:
struct ArgsObject {
let name: String
let surname: String
}
class Model {
var name: String
var surname: String
init(args: ArgsObject) {
self.name = args.name
self.surname = args.surname
}
}
let myM = Model(args: ArgsObject(name: "someName", surname: "someSurname"))
Few optional comments
Don't use ; and protocol names like ISomething, it's not the Java