Type unions for generic constraints?

I would like to write a function that deals with real numbers. Per my searching, a "Real" is basically a type union of BinaryInteger and FloatingPoint types; there is no Real class defined (though in looking at the type hierarchy, perhaps it's Arithmetic? In any case, please read on).

My understanding of generics comes from the Julia language, which implements them in a way that is both similar to, and very different from, Swift's generics. (Never mind that they have a Real abstract type that represents just this union.)

I know that

func foo<T>(x: T) -> T where T: Bar, T: Baz { ... }

will require x to be both Bar-like and Baz-like. However, what I'm looking for is the or version of this: that is, x is either Bar-like or Baz-like. (In my case, x is either an Integer or a FloatingPoint number.)

Is there a way to express this in Swift?

Yes, you can use an enumeration with two cases. It’s been brought up to introduce ad-hoc enums that would let you write what would look like an OR type, but they’ve all been rejected.

2 Likes

If you only need the functionality common to both protocols (+, -, *), then you can always go with their parent protocol, Numeric.

func foo<T: Numeric>(x: T) -> T { ... }
1 Like

Thank you. Could you give an example of how this would look?

Thanks. I explicitly want to reject complex numbers, though. If a third party defines Complex to be a subtype of Number, I may be hosed. (Is my thinking correct?)

It would probably help if you would explain what you're trying to implement, and why rejecting complex numbers is important. Often to be efficient and correct you'll have to implement the function differently for integers and floating point numbers anyway, so you might as well just provide two separate function overloads.

It would look something like

enum MyDisjointType {
  case binaryInteger(BinaryInteger)
  case floatingPoint(FloatingPoint)
}

And then pattern match on the cases.

1 Like

Consider a weighted graph represented by a sparse adjacency matrix with weights as the values. I'd like to be able to implement a generic geodesic distance function (think "shortest paths") for this graph that takes the weights into consideration. Imaginary (non-real) weights don't mean anything in this particular world, so I'd rather not have a possibility of having to deal with them.

Consider further that some algorithms don't allow negative edge weights (that is, all matrix values have to be positive) and I'd further want to restrict the type of the matrix being passed in to be positive reals only.

I think Numeric & Comparable (since you need to compare summed weights of paths) will be sufficient then, because complex numbers can't really conform to Comparable, and if someone did decide to give their complex numbers some total order then your algorithm would give correct results according to that order. Note that you might want to provide separate integer and floating point versions anyway, because in some applications it might be important to use a specialised summation algorithm for floating point numbers to reduce error.

As for negative edge weights, the recommendation in Swift is generally to document and implement that as a precondition, rather than trying to enforce it in the type system. Otherwise you end up not being able to subtract PositiveFloatingPoint values, or trapping on a temporary negative value in the middle of computation that would have ended up being positive, etc.

7 Likes

Fantastic guidance, @jawbroken! I'm still trying to learn The Swift Way(tm) and appreciate this insight. Thank you.