What protocol should be adopted by a Type for a generic function to take any number type as an argument in Swift?
Update: The answer below still applies in principle, but Swift 4 completed a redesign of the numeric protocols, such that adding your own is often unnecessary. Take a look at the standard library's numeric protocols before you build your own system.
This actually isn't possible out of the box in Swift. To do this you'll need to create a new protocol, declared with whatever methods and operators you're going to use inside your generic function. This process will work for you, but the exact details will depend a little on what your generic function does. Here's how you'd do it for a function that gets a number n
and returns (n - 1)^2
.
First, define your protocol, with the operators and an initializer that takes an Int
(that's so we can subtract one).
protocol NumericType {
func +(lhs: Self, rhs: Self) -> Self
func -(lhs: Self, rhs: Self) -> Self
func *(lhs: Self, rhs: Self) -> Self
func /(lhs: Self, rhs: Self) -> Self
func %(lhs: Self, rhs: Self) -> Self
init(_ v: Int)
}
All of the numeric types already implement these, but at this point the compiler doesn't know that they conform to the new NumericType
protocol. You have to make this explicit -- Apple calls this "declaring protocol adoption with an extension." We'll do this for Double
, Float
, and all the integer types:
extension Double : NumericType { }
extension Float : NumericType { }
extension Int : NumericType { }
extension Int8 : NumericType { }
extension Int16 : NumericType { }
extension Int32 : NumericType { }
extension Int64 : NumericType { }
extension UInt : NumericType { }
extension UInt8 : NumericType { }
extension UInt16 : NumericType { }
extension UInt32 : NumericType { }
extension UInt64 : NumericType { }
Now we can write our actual function, using the NumericType
protocol as a generic constraint.
func minusOneSquared<T : NumericType> (number : T) -> T {
let minusOne = number - T(1)
return minusOne * minusOne
}
minusOneSquared(5) // 16
minusOneSquared(2.3) // 1.69
minusOneSquared(2 as UInt64) // 1
SwifT: Generic function that gets generic protocol as parameter not working
I'm not sure why you say "the protocol only demands that Element implements CustomStringConvertible." The protocol demands that echo
accept and return its Element, which may or may not be String. For example, I can implement this conforming type:
struct AnotherFoo: FooProtocol {
func echo(element: Int) -> Int { fatalError() }
}
So what should happen if I then call:
callEcho(container: AnotherFoo())
This would try to pass a string literal to a function that requires an Int.
How would I use an array of generics with a type parameter conforming to a protocol?
To see what's wrong with your scenario, forget about trying to declare the type of this array, and try to actually make such an array out of actual objects:
protocol MyProtocol {}
struct MyStruct<T: MyProtocol> {
let myProp: T
}
struct S1 : MyProtocol {}
struct S2 : MyProtocol {}
let myStruct1 = MyStruct(myProp: S1())
let myStruct2 = MyStruct(myProp: S2())
let array = [myStruct1, myStruct2] // error
The compiler kicks back: "heterogeneous collection literal could only be inferred to '[Any]'". And that sums it up. The types of myStruct1
and myStruct2
have nothing in common, so you cannot make an array of them.
That is why you are not able to declare the array to be of a type that will embrace both of them. There is no such type. The types of myStruct1
and myStruct2
, namely MyStruct<S1>
and MyStruct<S2>
, are unrelated.
I know that they look related, because the word "MyProtocol" in the original declaration appears to provide some sort commonality. But the word "MyProtocol" does not designate a type; it designates a constraint on the actual type, saying that whatever this one type is, it must be an adopter of MyProtocol. S1 and S2 are two different types, and so MyStruct<S1>
and MyStruct<S2>
are two different types. You can't put them together in an array. The fact that both S1 and S2 happen to adopt MyProtocol is irrelevant.
Part of the difficulty may be that you think that two generic types are somehow related because their parameterized types are related. That is not the case. The classic example is a class and its subclass:
class Cat {}
class Kitten: Cat {}
struct Animal<T: Cat> {}
let cat = Animal<Cat>()
let kitten = Animal<Kitten>()
let array2 = [cat, kitten] // error
We get the same compile error. Again, you might imagine that you can put cat
and kitten
together in an array because Kitten is a subclass of Cat. But that is not true. Animal<Cat>
and Animal<Kitten>
are unrelated types.
Usage of arithmetic operants (+, -, /, *) with generic types
For this you need to create a new protocol
that lets Swift know any instance of T
can have numeric operators performed on it, for example:
protocol NumericType: Equatable, Comparable {
func +(lhs: Self, rhs: Self) -> Self
func -(lhs: Self, rhs: Self) -> Self
func *(lhs: Self, rhs: Self) -> Self
func /(lhs: Self, rhs: Self) -> Self
func %(lhs: Self, rhs: Self) -> Self
init(_ v: Int)
}
extension Double : NumericType {}
extension Int : NumericType {}
Source: What protocol should be adopted by a Type for a generic function to take any number type as an argument in Swift?
Now when you define your MathStatistics
class:
class MathStatistics<T: NumericType> {
var numbers = [T]()
func average() -> T? {
if numbers.count == 0 {
return nil
}
let sum = numbers.reduce(T(0)) { $0 + $1 }
return sum / T(numbers.count)
}
}
Now you can use MathsStatistics
like so:
let stats = MathStatistics<Int>()
stats.numbers = [1, 3, 5]
println(stats.average()) // Prints: Optional(3)
Swift number generics?
Swift generics aren't like C++ templates.
In C++, you can just try to use a parameterized type however you want, and it's not an error until the compiler tries to instantiate the template with some type that doesn't support what your template tries to do.
In Swift, the generic construct can only use a parameterized type in ways known to be valid when the generic construct is first parsed. You specify these "ways known to be valid" by constraining the parameterized type with protocols.
You cannot call sqrt
or pow
with generic-typed arguments, because those functions are not themselves generic. They have each two definitions:
func pow(_: Double, _: Double) -> Double
func pow(lhs: Float, rhs: Float) -> Float
func sqrt(x: Double) -> Double
func sqrt(x: Float) -> Float
You could write type-specific versions of hypotenusa
:
func hypotenusa(a: Float, b: Float) -> Float
func hypotenusa(a: Double, b: Double) -> Double
func hypotenusa(a: CGFloat, b: CGFloat) -> CGFloat
I'm not sure why you'd create an Int
version at all, since very few right triangles have integer hypotenuses.
Anyway, you don't need to define the Float
and Double
versions at all, because the standard library already provides a hypot
function defined on Float
and Double
:
func hypot(_: Double, _: Double) -> Double
func hypot(lhs: Float, rhs: Float) -> Float
You could create another override for CGFloat
:
func hypot(l: CGFloat, r: CGFloat) -> CGFloat {
return hypot(Double(l), Double(r))
}
As for your addition
function, it has the same problem as your hypotenusa
function: the +
operator is not defined entirely generically. It has some generic definitions (unlike sqrt
and pow
), but those only cover the integer types (see IntegerArithmeticType
). There's not generic definition of +
that covers the floating-point types. Swift defines all of these versions of +
with explicit types:
func +(lhs: Float, rhs: Float) -> Float
func +<T>(lhs: Int, rhs: UnsafePointer<T>) -> UnsafePointer<T>
func +<T>(lhs: UnsafePointer<T>, rhs: Int) -> UnsafePointer<T>
func +(lhs: Int, rhs: Int) -> Int
func +(lhs: UInt, rhs: UInt) -> UInt
func +(lhs: Int64, rhs: Int64) -> Int64
func +(lhs: UInt64, rhs: UInt64) -> UInt64
func +<T>(lhs: Int, rhs: UnsafeMutablePointer<T>) -> UnsafeMutablePointer<T>
func +<T>(lhs: UnsafeMutablePointer<T>, rhs: Int) -> UnsafeMutablePointer<T>
func +(lhs: Int32, rhs: Int32) -> Int32
func +(lhs: UInt32, rhs: UInt32) -> UInt32
func +(lhs: Int16, rhs: Int16) -> Int16
func +(lhs: UInt16, rhs: UInt16) -> UInt16
func +(lhs: Int8, rhs: Int8) -> Int8
func +(lhs: UInt8, rhs: UInt8) -> UInt8
func +(lhs: Double, rhs: Double) -> Double
func +(lhs: String, rhs: String) -> String
func +(lhs: Float80, rhs: Float80) -> Float80
Literals in Swift generics
You can create a type constraint (MyFloats
below) to which your floating point types conform. You let this type constraint itself conform to Comparable
, so that you may make use of the less than binary infix operator <
when comparing the values of your generics. Also, for your example given above, the MyFloats
type constraint need contain only a single blueprint; an initializer for a Double
argument. This initalizer already exists for Double
, Float
and CGFloat
types, but since a protocol cannot know which types that conforms to it, you need to include this blueprint.
protocol MyFloats : Comparable {
init(_ value: Double)
}
extension Double : MyFloats { }
extension Float : MyFloats { }
extension CGFloat : MyFloats { }
func sign<T: MyFloats> (value:T) -> T {
if value < T(0.0) {
return T(-1.0)
}
if value > T(0.0) {
return T(1.0)
}
return T(0.0)
}
What protocol should be adopted by a Type for a generic function to take any number type as an argument in Swift?
Update: The answer below still applies in principle, but Swift 4 completed a redesign of the numeric protocols, such that adding your own is often unnecessary. Take a look at the standard library's numeric protocols before you build your own system.
This actually isn't possible out of the box in Swift. To do this you'll need to create a new protocol, declared with whatever methods and operators you're going to use inside your generic function. This process will work for you, but the exact details will depend a little on what your generic function does. Here's how you'd do it for a function that gets a number n
and returns (n - 1)^2
.
First, define your protocol, with the operators and an initializer that takes an Int
(that's so we can subtract one).
protocol NumericType {
func +(lhs: Self, rhs: Self) -> Self
func -(lhs: Self, rhs: Self) -> Self
func *(lhs: Self, rhs: Self) -> Self
func /(lhs: Self, rhs: Self) -> Self
func %(lhs: Self, rhs: Self) -> Self
init(_ v: Int)
}
All of the numeric types already implement these, but at this point the compiler doesn't know that they conform to the new NumericType
protocol. You have to make this explicit -- Apple calls this "declaring protocol adoption with an extension." We'll do this for Double
, Float
, and all the integer types:
extension Double : NumericType { }
extension Float : NumericType { }
extension Int : NumericType { }
extension Int8 : NumericType { }
extension Int16 : NumericType { }
extension Int32 : NumericType { }
extension Int64 : NumericType { }
extension UInt : NumericType { }
extension UInt8 : NumericType { }
extension UInt16 : NumericType { }
extension UInt32 : NumericType { }
extension UInt64 : NumericType { }
Now we can write our actual function, using the NumericType
protocol as a generic constraint.
func minusOneSquared<T : NumericType> (number : T) -> T {
let minusOne = number - T(1)
return minusOne * minusOne
}
minusOneSquared(5) // 16
minusOneSquared(2.3) // 1.69
minusOneSquared(2 as UInt64) // 1
Swift: Pass type from property to generic function
Ignoring the actual error report you have a fundamental problem with requestAndDecode
: it is a generic function whose type parameters are determined at the call site which is declared to return a value of type Result
yet it attempts to return a value of type self.responseResultType
whose value is an unknown type.
If Swift's type system supported this it would require runtime type checking, potential failure, and your code would have to handle that. E.g. you could pass TestData
to requestAndDecode
while responseResultType
might be ValidationResponse
...
Change the JSON call to:
JSONDecoder().decode(ApiResponse<Result>.self ...
and the types statically match (even though the actual type that Result
is is unknown).
You need to rethink your design. HTH
Related Topics
How to Check If a String Contains Another String in Swift
How to Create Instances of Managed Object Subclasses in a Nsmanagedobject Swift Extension
Cfrunloop in Swift Command Line Program
Swift Optional Escaping Closure Parameter
Printing a Variable Memory Address in Swift
Cannot Assign Property in Method of Struct
What Is the Cause of This Type Error
Property Initializers Run Before 'Self' Is Available
Tabview Resets Navigation Stack When Switching Tabs
Computed Read-Only Property VS Function in Swift
How to Check If a Text Field Is Empty or Not in Swift
How to Use Userdefaults With Swiftui
Load Local Web Files & Resources in Wkwebview
JavaScript Synchronous Native Communication to Wkwebview
Generic Function Taking a Type Name in Swift
Extra Arguments At Positions #11, #12 in Call Swiftui
"Classname Has No Member Functionname" When Adding Uibutton Target