What Protocol Should Be Adopted by a Type For a Generic Function to Take Any Number Type as an Argument in Swift

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



Leave a reply



Submit