1

Let's say I have these functions

// bar is some external function
// it has signature bar(_ number: Double) -> Double

func foo(number: Int) -> Double {
    return bar(Double(number))
}

func foo(number: Float) -> Double {
    return bar(Double(number))
}

func foo(number: Double) -> Double {
    return bar(number)
}

I want to have one generic function:

func foo<T>(number: T) -> Double {
    return bar(Double(number))
}

But it seems that compiler does not like this idea:

enter image description here

How to properly make such generic functions in swift3? Or is it impossible?

Denis Kreshikhin
  • 8,856
  • 9
  • 52
  • 84
  • 1
    You just need to extend `FloatingPoint` check this https://stackoverflow.com/questions/29179692/how-can-i-convert-from-degrees-to-radians/29179878#29179878 – Leo Dabus May 25 '17 at 18:33
  • If you need to use Integers also I suggest using Decimal – Leo Dabus May 25 '17 at 18:36
  • Note that returning Self you will be returning the same object type. Double, Float or CGFloat – Leo Dabus May 25 '17 at 18:37
  • 1
    If you need to always return Double you can check Martin R answer here explaining how to create a protocol DoubleConvertible https://stackoverflow.com/a/26797755/2303865 – Leo Dabus May 25 '17 at 18:39
  • Sometimes this is worth it, but be very careful and thoughtful when you create these kinds of automatic conversions. Int is not a subset of Double (`Double(Int.max) == Double(Int.max - 1)`, because neither is a member of Double and require rounding). Extending Float to Double can lead to surprising behaviors (`Float(1.0 / 3.0) * 3) == 1` but `(Double(Float(1.0 / 3.0)) * 3) != 1`). There are many cases where these corner cases don't matter, but there are many cases where they do. Make sure you've considered them. – Rob Napier May 27 '17 at 16:34
  • (But generally I just write one for `Int` and one for `FloatingPoint` when I want something like this. Minimal duplication and minimal complexity. Sometimes I use a `.doubleValue` protocol like in the examples.) – Rob Napier May 27 '17 at 16:36

2 Answers2

2

What you're trying to do is not exactly possible I don't think. You might be interested in this: https://gist.github.com/erica/2f6a38c844573c778b0f

After that code is imported you can do

func foo<T: DoubleRepresentable>(number: T) -> Double {
    return number.doubleValue
}
garretriddle
  • 409
  • 3
  • 10
1

As far as I'm concerned, Swift and many other strongly typed languages should really go back to math school and learn about number theory. Apart from internal representations (which a high level language should abstract away instead of burden programmers with), all numeric types belong to a domain that is a subset of a larger one.

To compensate for this you can create a protocol that will "understand" that a Real number (Double) belongs to a domain that includes all others. You will then be able to define functions that will accept all numeric types from lower level domains using that protocol and process them with "Real" (Double) operators that will produce a valid result (in theory).

For example:

protocol Numeric
{
   var asDouble:Double { get }
}

extension Double:Numeric   { var asDouble:Double { return self } }
extension Int:Numeric      { var asDouble:Double { return Double(self) } }
extension Int8:Numeric     { var asDouble:Double { return Double(self) } }
extension Int16:Numeric    { var asDouble:Double { return Double(self) } }
extension Int32:Numeric    { var asDouble:Double { return Double(self) } }
extension Int64:Numeric    { var asDouble:Double { return Double(self) } }
extension UInt:Numeric     { var asDouble:Double { return Double(self) } }
extension UInt8:Numeric    { var asDouble:Double { return Double(self) } }
extension UInt16:Numeric   { var asDouble:Double { return Double(self) } }
extension UInt32:Numeric   { var asDouble:Double { return Double(self) } }
extension UInt64:Numeric   { var asDouble:Double { return Double(self) } }
extension Float:Numeric    { var asDouble:Double { return Double(self) } }
extension Float80:Numeric  { var asDouble:Double { return Double(self) } }
extension NSNumber:Numeric { var asDouble:Double { return Double(self) } }

func foo(_ number: Numeric) -> Double 
{
  return bar(number.asDouble)
}

Of course this does not take into account precision limitations (e.g. Float vs Double ) and will not actually process numbers from larger domains (irrational or imaginary) but, for all intents and purposes and from a conceptual point of view, it will get the insignificant variations out of the way.

Now the next tedious piece of work will be to implement all math and assignment operators so they can work with the Numeric protocol (but I will leave that to Apple).

Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • This isn't true. Double is not the same thing as Real (pi is not a member of Double). Int is not a subset of Double (`Int.max` is not a member of Double). The way that C has traditionally obscured this with automatic promotions leads to bugs (and I've hit them) where the approximations are wrong. The difficulty in usability is because Apple *has* learned about number theory and many developers have a misunderstanding of how numbers work in computers. What is significant and insignificant depends on what you're working on. – Rob Napier May 27 '17 at 15:49
  • For a demonstration of one of the problems, check the result of `Double(Int.max) == Double(Int.max - 1)`. If you just promote all numbers to Double, you can get really surprising results. Similarly, the common confusion about why `tan(M_PI / 2)` is not infinite (https://stackoverflow.com/questions/44070695/not-getting-tan-90-value-as-undefined-in-objective-c/44071363#44071363). Things that seem obvious when working in Real numbers fail when working in Double. Unfortunately (and it *is* unfortunate; I wish it weren't true), programers often need to understand how numbers are represented. – Rob Napier May 27 '17 at 15:55
  • I did exclude irrational numbers (e.g. pi) and internal representation issues of the various types in my statement so I stand by it. It may not be completely true depending on your perspective but it certainly isn't entirely false either. Relevance is easily highlighted by the numerous type casts that most programs need to go through regardless of use case. BTW where in number theory (not computer engineering) is Int16 in a different domain than int32 ? – Alain T. May 27 '17 at 18:25
  • Swift 4 will provide some new protocols that will make it easier to write extensions. But there are very good reasons that Swift does not just promote values to other types. The explicitness is intentional, and founded on a history of bugs caused by automatic promotion. One can argue about whether it's better to make common use convenient at the cost of crazy bugs in uncommon cases, but avoiding crazy bugs even in uncommon cases is a driving goal in Swift. It's not this way due to a misunderstanding of math. – Rob Napier May 27 '17 at 18:36
  • 1
    Int16 is a subtype of Int32 (but UInt16 is not a proper subtype of UInt32 because of overflow behaviors). Int32 is not a subtype of Float. My point is that equating "Real number (Double)" is a rough approximation that often breaks. It's complicated and in many cases developers must care about the complications. Generally the better solution to reducing type casts it to convert all values early in the pipeline to a single type rather than at point of use. The history of C/ObjC/Swift makes this hard sometimes, but it's a history problem that will improve as Cocoa is made more Swift-friendly. – Rob Napier May 27 '17 at 18:40