Are we able to nest functions that return protocol types now? Or was that always the case and the documentation is wrong? - swift

I am working on a project with SwiftUI. And from the get-go, swiftUI shows the power and application of protocol oriented programming. So I started studying and expanding my knowledge on protocols and opaque types.
In the documentation The swift programming language towards the end of the page it clearly states:
problem with this approach is that the shape transformations don’t nest. The result of flipping a triangle is a value of type Shape, and the protoFlip(:) function takes an argument of some type that conforms to the Shape protocol. However, a value of a protocol type doesn’t conform to that protocol; the value returned by protoFlip(:) doesn’t conform to Shape. This means code like protoFlip(protoFlip(smallTriange)) that applies multiple transformations is invalid because the flipped shape isn’t a valid argument to protoFlip(_:)
However... when I try to nest the function, it works perfectly fine. So is the documentation wrong or is this a very recent update to the language and the documentation is a bit behind?
Here is the code (which I ran on playgrounds).
protocol Shape {
func draw() -> String
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result: [String] = []
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
//return type is a protocol
func protoFlip<T: Shape>(_ shape: T) -> Shape {
if shape is Square {
return shape
}
return FlippedShape(shape: shape)
}
//Testing if nested function works
let smallTriangle = Triangle(size: 3)
let testNest = protoFlip(protoFlip(smallTriangle))

I think that the book is using a bad example to illustrate the problem. In general your code will compile and run correctly. But some swift features like associated types and self requirements in methods of protocol breaking the concept of polymorphism we used to.
To summarise what I'am talking about take a look at this code:
protocol Shape: Equatable {
func draw() -> String
func doSomething(with other: Self) // self requirement
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result: [String] = []
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
func doSomething(with other: Self) {
print("Tri size: \(other.size)")
}
}
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
func doSomething(with other: Self) {
print("Other filled shape: \(other.draw())")
}
}
struct Rect: Shape {
var width: Int
var height: Int
func draw() -> String {
let line = String(repeating: "*", count: width)
let result = Array<String>(repeating: line, count: height)
return result.joined(separator: "\n")
}
func doSomething(with other: Self) {
print("W: \(other.width) H:\(other.height)")
}
}
func protoFlip<T: Shape>(_ shape: T) -> Shape { //Compiler emits error: Use of protocol 'Shape' as a type must be written 'any Shape'
return FlippedShape(shape: shape)
}
let smallTriangle = Triangle(size: 3)
let protoFlippedTriangle = protoFlip(protoFlip(protoFlip(smallTriangle)))
print(protoFlippedTriangle.draw())
This code won't compile without opaque types because in this case compiler need to check if the shape we passed to functions is the same as we returned. Because we can not call doSomething on any other Shape other then the shape of the same type

Related

Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements [duplicate]

This question already has answers here:
Protocol can only be used as a generic constraint because it has Self or associatedType requirements
(6 answers)
Closed 5 months ago.
In swift5.x, I want to use a protocol named Animal with an associatedType 'T'.
class Shape {
func transform() {
print("Shape transofrm")
}
}
protocol Animal {
associatedtype T: Shape
var tansformT: T { get set }
func bark()
}
var a: any Animal
Rectangle and Circle implements the Shape protocol:
class Rectangle: Shape {
override func transform() {
print("Rectangle transofrm")
}
}
class Circle: Shape {
override func transform() {
print("Circle transofrm")
}
}
And there are two class implements Animal protocol:
class Dog : Animal {
var tansformT: Rectangle = Rectangle()
func bark() {
}
}
class Cat: Animal {
var tansformT = Circle()
func bark() {
print("Cat bark")
}
}
I want to declare a variable ani, it can be Dog or Cat according the condition, so i try this:
var a = 10
var ani: Animal
if a == 10 {
ani = Cat()
} else {
ani = Dog()
}
then the compiler reports an error:
Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements
I have try my best for 3+ hours, I don't know how to solve it.
Here I found an answer that can help you out.
https://stackoverflow.com/a/59330536/17286292
Failed with error:
Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements
var ani: Animal = Cat()
But change to opaque result type like this, it will work.
var ani: some Animal = Cat()
Doc: https://docs.swift.org/swift-book/LanguageGuide/OpaqueTypes.html
From doc:
struct Square: Shape {
var size: Int
func draw() -> String {
let line = String(repeating: "*", count: size)
let result = Array<String>(repeating: line, count: size)
return result.joined(separator: "\n")
}
}
func makeTrapezoid() -> some Shape {
let top = Triangle(size: 2)
let middle = Square(size: 2)
let bottom = FlippedShape(shape: top)
let trapezoid = JoinedShape(
top: top,
bottom: JoinedShape(top: middle, bottom: bottom)
)
return trapezoid
}
let trapezoid = makeTrapezoid()
print(trapezoid.draw())
// *
// **
// **
// **
// **
// *
The makeTrapezoid() function in this example declares its return type
as some Shape; as a result, the function returns a value of some given
type that conforms to the Shape protocol, without specifying any
particular concrete type. Writing makeTrapezoid() this way lets it
express the fundamental aspect of its public interface—the value it
returns is a shape—without making the specific types that the shape is
made from a part of its public interface.

Static Var closure returns Type.Type instead of Type

I try to break, count and later join objects inside another class.
So I build protocol:
typealias DataBreaker<T> = () -> [Double]
typealias DataJoiner<T> = (_ particles: [Double]) -> T
protocol SpaceIntepolatable {
associatedtype Atom
var breaker:DataBreaker<Atom> {get}
static var joiner:DataJoiner<Atom> {get}
}
and build an extension for Point:
extension Point:SpaceIntepolatable {
typealias Atom = Point
var breaker:DataBreaker<Atom> {
return {
return [self.x, self.y]
}
}
static var joiner:DataJoiner<Atom> {
return {particles in
return Atom(x: particles[0], y: particles[1])
}
}
}
Till now is fine. I can break Point into an Array<Double>
let particles = atom.breaker()
but joining
let newAtom = Atom.joiner(particles)
causes a compiler error:
Cannot convert value of type 'Atom.Atom' to specified type 'Atom'
It's probably because joiner is a static. But how to avoid it and get Atom as a result?
You have to call it on Point when you are outside the scope of the class.
let newAtom = Point.joiner(particles)
Edit:
You say that you have a generic class that looks like this:
class Space<Atom: SpaceIntepolatable> {
func test() {
let particles: [Double] = [0, 1]
let newAtom: Atom = Atom.joiner(particles)
}
}
Now, the problem is that the type of newAtom is incorrect. The SpaceIntepolatable protocol doesn't specify that Point and Point.Atom are the same type. Therefore Atom (Point) and Atom.Atom (Point.Atom) are not considered the same. What we want is Atom.Atom. Or we can just let the type to be inferred:
let newAtom: Atom.Atom = Atom.joiner(particles)
let newAtom = Atom.joiner(particles)
In general it is advisable not to reuse type names because then you get things like Atom.Atom. Maybe you actually want something like this:
protocol SpaceIntepolatable {
var breaker: DataBreaker<Self> { get }
static var joiner: DataJoiner<Self> { get }
}
and lose the Atom typealias completely, then:
class Space<Atom: SpaceIntepolatable> {
func test() {
let particles: [Double] = [0, 1]
let newAtom: Atom = Atom.joiner(particles)
}
}
will actually work.

Swift type erasure - for this case?

I have requirement of implementing TypeConverter and later use it as variable type. Inspired by ObjectMapper I have defined following protocol:
protocol TypeConverter {
associatedtype A
associatedtype B
func transformFrom(fromType: A?) -> B?
func transformTo(toType: B?) -> A?
}
Concrete implementation is:
class IntToStringTypeConverter: TypeConverter {
typealias A = Int
typealias B = String
func transformFrom(fromType: Int?) -> String? {
guard let fromType = fromType else { return nil }
return String(fromType)
}
func transformTo(toType: String?) -> Int? {
guard let toType = toType else { return nil }
return Int(toType)
}
}
Because protocol TypeConverter has associatedtype, I cannot declare it as variable, for example: var converter: TypeConverter, but I need such feature. The solution to such case is using typeErasure. By following this link https://medium.com/#NilStack/swift-world-type-erasure-5b720bc0318a it should be possible, but I don't have real idea how.
Here is my try, but its not right :)... Is this even solve-able this way? Or I should use this one: https://appventure.me/2017/12/10/patterns-for-working-with-associated-types ?
class AnyTypeConverter<Y, Z>: TypeConverter {
typealias A = Y
typealias B = Z
private let _transformFrom: (Z?) -> Y?
private let _transformTo: (Y?) -> Z?
init<W: TypeConverter>(_ iFormTypeConverter: W) where W.A == Y, W.B == Z {
self._transformFrom = iFormTypeConverter.transformFrom
self._transformTo = iFormTypeConverter.transformTo
}
func transformFrom(modelType: Y?) -> Z? {
return transformFrom(modelType: modelType)
}
func transformTo(iFormType: Z?) -> Y? {
return transformTo(iFormType: iFormType)
}
}
This is not really a good use for a protocol with associated types. PATs are very complicated tools, and there's really no reason for it in this case at all. You don't even need a type-eraser so much as just a struct:
struct TypeConverter<Model, Form> {
let transformFrom: (Model) -> Form?
let transformTo: (Form) -> Model?
}
let stringToInt = TypeConverter(transformFrom:String.init,
transformTo:Int.init)
stringToInt.transformFrom(123)
stringToInt.transformTo("x")
You of course could make this conform to TypeConverter if you wanted to (and I can update to add that), but I recommend dropping the protocol entirely and just using structs. This is very close to how Formatter works.
After implementing two cells I have found out that I can simplify this thing a bit, and go with only one associated type :). The type used in the cell is actually defined by the UI component - if there is UITextField, then type will be String, if I implement custom stepper, it will be Int (for example). If I want to make my cell generic, then it should work with Any type, for which I can write converter between model and (pre)defined cellType.
protocol FormTypeConverter {
associatedtype FormType
func fromModelToForm(_ value: Any?) -> FormType?
func fromFormToModel(_ value: FormType?) -> Any?
}
With that I can use simple type erasure as follows (source in links in the first post)
struct AnyFormTypeConverter<T>: FormTypeConverter {
// MARK: - Variables
private let fromModelToFormWrapper: (Any?) -> T?
private let fromFormToModelWrapper: (T?) -> Any?
init<Y: FormTypeConverter>(_ formTypeConverter: Y) where Y.FormType == T {
self.fromModelToFormWrapper = formTypeConverter.fromModelToForm
self.fromFormToModelWrapper = formTypeConverter.fromFormToModel
}
func fromModelToForm(_ value: Any?) -> T? {
return fromModelToFormWrapper(value)
}
func fromFormToModel(_ value: T?) -> Any? {
return fromFormToModel(value)
}
}
This case implementation suits excellent. Already implemented two completely different forms in no-time :)

Sets of a given size as a Swift type

In Swift, is there a way to construct a type of “sets of a given size?”(Despite the absence of dependent types in Swift, is such a construction nonetheless possible without excessive “contortions?”)
As an example, I would like to be able to define a parameterized type SetOfSizeTwo<T> whose instances are sets comprising of exactly two objects of (Hashable) type T.
Currently, I am using a poor man’s proxy:
struct SetOfSizeTwo<T> where T: Hashable {
var set = Set<T>(minimumCapacity: 2)
}
However, this type does not force the property set to be of size of 2.
Update
The blog article A hack for fixed-size arrays in Swift, by Ole Begemann, leads me to believe that a robust construction of a fixed-sized-set type in Swift 4 is non-trivial, if at all possible.
Here is one approach, as closest as I could get, to suit your needs. It is a bit rough around the edges and needs some polishing, but I think you'll get the idea:
class Size {
let size: Int
init(size: Int) {
self.size = size
}
required init() {
self.size = 0
}
}
class SizeOne: Size {
private override init(size: Int) {
super.init(size: size)
}
required init() {
super.init(size: 1)
}
}
class SizedSet<S, T> where S: Size, T: Hashable {
private var set: Set<T>
private let maximumSize: S
init() {
set = Set<T>()
maximumSize = S()
}
func insert(item: T) {
if !set.contains(item) && set.count + 1 <= maximumSize.size {
set.insert(item)
}
}
func remove(item: T) {
set.remove(item)
}
func contents() -> Set<T> {
return set
}
}
Usage:
let set: SizedSet<SizeOne, Int> = SizedSet()
print(set.contents())
// []
set.insert(item: 1)
print(set.contents())
// [1]
set.insert(item: 2)
print(set.contents())
// [1]

How to create an array of instances of a subclass from a superclass

From this answer, I know that I can create an instance of a subclass from a superclass. Yet, I can't figure out how to create an array of the subclass from the superclass.
Drawing on the above example, here's my best shot so far:
class Calculator {
func showKind() { println("regular") }
required init() {}
}
class ScientificCalculator: Calculator {
let model: String = "HP-15C"
override func showKind() { println("\(model) - Scientific") }
required init() {
super.init()
}
}
extension Calculator {
class func createMultiple<T:Calculator>(num: Int) -> T {
let subclass: T.Type = T.self
var calculators = [subclass]()
for i in 0..<num {
calculators.append(subclass())
}
return calculators
}
}
let scis: [ScientificCalculator] = ScientificCalculator.createMultiple(2)
for sci in scis {
sci.showKind()
}
With that code, the line var calculators = [subclass]() shows the error Invalid use of '()' to call a value of non-function type '[T.Type]'.
How can I return an array of ScientificCalculators from Calculator.createMultiple?
You were on the right track but you've made some mistakes.
First you need to return a array of T and not just a single element. So you need to change the return type from T to [T]:
class func createMultiple<T:Calculator>(num: Int) -> [T] {
Also you can just use T to initialize new instances of your subclass like that:
var calculators:[T] = [T]()
But the other parts are correct. So you final method would look like that:
extension Calculator {
class func createMultiple<T:Calculator>(num: Int) -> [T] {
let subclass: T.Type = T.self
var calculators = [T]()
for i in 0..<num {
calculators.append(subclass())
}
return calculators
}
}
Edit
If you are using Swift 1.2 you don't have to deal with subclass anymore and you will be able to use T instead like shown in Airspeeds answer.
calculators.append(T())
EDIT: this behaviour appears to have changed in the latest Swift 1.2 beta. You shouldn’t need to use T.self. T is the type you want to create. But if you are using 1.1, it appears not to work (even if T is the subtype, it creates the supertype), and using the metatype to create the type works around this problem. See end of answer for a 1.1 version.
You don’t need to mess with subclass: T.Type = T.self. Just use T – that itself is the type (or rather, a placeholder for whatever type is specified by the caller):
extension Calculator {
// you meant to return an array of T, right?
class func createMultiple<T: Calculator>(num: Int) -> [T] {
// declare an array of T
var calculators = [T]()
for i in 0..<num {
// create new T and append
calculators.append(T())
}
return calculators
}
}
btw, you can replace that for loop with map:
class func createMultiple<T: Calculator>(num: Int) -> [T] {
return map(0..<num) { _ in T() }
}
If you are still on Swift 1.1, you need to use T.self to work around a problem where the subtype is not properly created:
extension Calculator {
// only use this version if you need this to work in Swift 1.1:
class func createMultiple<T: Calculator>(num: Int) -> [T] {
let subclass: T.Type = T.self
return map(0..<num) { _ in subclass() }
}
}