Swift Initializer Generics


(Tyler Cloutier) #1

Hi everyone,

I was wondering if there was a trick to get the following thing accomplished within the framework of Swift generics. I am using Surge <https://github.com/mattt/Surge>, a very nice and useful wrapper for the Accelerate framework. The problem is that I would like to extend the functionality of Surge in my own graphics library to include geometric constructs and I’ve run into an issue.

In order to use the appropriate Accelerate function call, Surge duplicates each function on the parameter types, either Float or Double. Ideally, I think there should be a better way to accomplish this with Swift generics, but perhaps that is a separate discussion. As an example consider dot product of Arrays (Vectors).

public func dot(x: [Float], y: [Float]) -> Float {
    precondition(x.count == y.count, "Vectors must have equal count")

    var result: Float = 0.0
    vDSP_dotpr(x, 1, y, 1, &result, vDSP_Length(x.count))

    return result
}

public func dot(x: [Double], y: [Double]) -> Double {
    precondition(x.count == y.count, "Vectors must have equal count")

    var result: Double = 0.0
    vDSP_dotprD(x, 1, y, 1, &result, vDSP_Length(x.count))

    return result
}

Consider, now, that I want to make a new function that uses one of these Surge functions. Am I correct in saying that I must define two functions, one for Float and one for Double?

func normalized(x: [Float]) -> [Float] {
    return x / sum(x)
}

func normalized(x: [Double]) -> [Double] {
    return x / sum(x)
}

Is there no way to get this done with generics? Writing the function with generic parameters yields the following error:

func normalized<T where T: FloatingPointType, T: FloatLiteralConvertible>(x: [T]) -> [T] {
    return x / sum(x) // Error: Cannot invoke 'sum' with an argument list of type '([T])'
}

Let’s assume, however that I go ahead and create two ‘normalized’ functions. I then want to create a new struct type call Plane. I would like to be able to construct a Plane using a normal vector and a point. Like so:

struct Plane<T where T: FloatingPointType, T: FloatLiteralConvertible> {
    let normal: [T]
    let distance: T
    
    init(normal: [T], point: [T]) {
        self.normal = normalized(normal) // Error: Ambiguous reference to member 'normalized'
        self.distance = dot(normal, y: point) // Error: Cannot invoke 'dot' with an argument list of type '([T], y: [T])'
    }
}

Yet as you can see there are still errors. In fact, as far as I can figure there is no way I can accomplish this last bit, no matter how much duplication I’m willing to tolerate.

Any help that anyone can provide would be much appreciated. Sorry for the duplicate email, I was off-list before.

Thanks!

Tyler


(Dmitri Gribenko) #2

Fixing this essentially requires a redesign of Surge to be centered
around protocols, I think. You would need to make sure that every
entry point that is implemented differently for Floats and Doubles is
called through a protocol requirement.

Dmitri

···

On Mon, Jan 18, 2016 at 4:23 PM, Tyler Fleming Cloutier via swift-users <swift-users@swift.org> wrote:

Is there no way to get this done with generics? Writing the function with
generic parameters yields the following error:

func normalized<T where T: FloatingPointType, T: FloatLiteralConvertible>(x:
[T]) -> [T] {
    return x / sum(x) // Error: Cannot invoke 'sum' with an argument list of
type '([T])'
}

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(David Turnbull) #3

How about this...

public func dot<genType:FloatingPointVectorType>(x:genType, _ y:genType) ->
genType.Element {

    let a = genType(x, y, *)

    return a.reduce(genType.Element(0)) { $0 + ($1 as! genType.Element) }

}

and this...

public func normalize<genType:FloatingPointVectorType>(x:genType) -> genType
{

    return x / length(x)

}

These are the actual implementations from SwiftGL. Here's what your Plane
looks like in SwiftGL:

struct Plane<T:FloatingPointScalarType> {

    let normal: Vector3<T>

    let distance: T

    init(normal: Vector3<T>, point: Vector3<T>) {

        self.normal = normalize(normal)

        self.distance = dot(normal, point)

    }

}

So not only is it possible, it's been done. There's no "trick" to this. It
works because I put a lot of time into making it work.

https://github.com/AE9RB/SwiftGL

-david

···

On Mon, Jan 18, 2016 at 4:23 PM, Tyler Fleming Cloutier via swift-users < swift-users@swift.org> wrote:

Hi everyone,

I was wondering if there was a trick to get the following thing
accomplished within the framework of Swift generics. I am using Surge
<https://github.com/mattt/Surge>, a very nice and useful wrapper for the
Accelerate framework. The problem is that I would like to extend the
functionality of Surge in my own graphics library to include geometric
constructs and I’ve run into an issue.

In order to use the appropriate Accelerate function call, Surge duplicates
each function on the parameter types, either Float or Double. Ideally, I
think there should be a better way to accomplish this with Swift generics,
but perhaps that is a separate discussion. As an example consider dot
product of Arrays (Vectors).

public func dot(x: [Float], y: [Float]) -> Float {
    precondition(x.count == y.count, "Vectors must have equal count")

    var result: Float = 0.0
    vDSP_dotpr(x, 1, y, 1, &result, vDSP_Length(x.count))

    return result
}

public func dot(x: [Double], y: [Double]) -> Double {
    precondition(x.count == y.count, "Vectors must have equal count")

    var result: Double = 0.0
    vDSP_dotprD(x, 1, y, 1, &result, vDSP_Length(x.count))

    return result
}

Consider, now, that I want to make a new function that uses one of these
Surge functions. Am I correct in saying that I must define two functions,
one for Float and one for Double?

func normalized(x: [Float]) -> [Float] {
    return x / sum(x)
}

func normalized(x: [Double]) -> [Double] {
    return x / sum(x)
}

Is there no way to get this done with generics? Writing the function with
generic parameters yields the following error:

func normalized<T where T: FloatingPointType, T:
>(x: [T]) -> [T] {
    return x / sum(x) // Error: Cannot invoke 'sum' with an argument list
of type '([T])'
}

Let’s assume, however that I go ahead and create two ‘normalized’
functions. I then want to create a new struct type call Plane. I would like
to be able to construct a Plane using a normal vector and a point. Like so:

struct Plane<T where T: FloatingPointType, T: FloatLiteralConvertible> {
    let normal: [T]
    let distance: T

    init(normal: [T], point: [T]) {
        self.normal = normalized(normal) // Error: Ambiguous reference to
member 'normalized'
        self.distance = dot(normal, y: point) // Error: Cannot invoke
'dot' with an argument list of type '([T], y: [T])'
    }
}

Yet as you can see there are still errors. In fact, as far as I can figure
there is no way I can accomplish this last bit, no matter how much
duplication I’m willing to tolerate.

Any help that anyone can provide would be much appreciated. Sorry for the
duplicate email, I was off-list before.

Thanks!

Tyler

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Dmitri Gribenko) #4

This won't be calling into Accelerate. The assumption is that
Accelerate is manually-optimized and heavily tuned, and it will be
faster than a naive Swift implementation.

Dmitri

···

On Mon, Jan 18, 2016 at 5:06 PM, David Turnbull via swift-users <swift-users@swift.org> wrote:

How about this...

public func dot<genType:FloatingPointVectorType>(x:genType, _ y:genType) ->
genType.Element {

    let a = genType(x, y, *)

    return a.reduce(genType.Element(0)) { $0 + ($1 as! genType.Element) }

}

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(David Turnbull) #5

SwiftGL isn't "naive Swift" except where it can't use the SIMD module.

-david

···

On Mon, Jan 18, 2016 at 5:09 PM, Dmitri Gribenko <gribozavr@gmail.com> wrote:

On Mon, Jan 18, 2016 at 5:06 PM, David Turnbull via swift-users > <swift-users@swift.org> wrote:
> How about this...
>
> public func dot<genType:FloatingPointVectorType>(x:genType, _ y:genType)
->
> genType.Element {
>
> let a = genType(x, y, *)
>
> return a.reduce(genType.Element(0)) { $0 + ($1 as! genType.Element) }
>
> }

This won't be calling into Accelerate. The assumption is that
Accelerate is manually-optimized and heavily tuned, and it will be
faster than a naive Swift implementation.

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Tyler Cloutier) #6

Thanks for your responses!

Yes, it is true that I would like to use the Accelerate framework as much as possible. And since Surge is just a thin wrapper for that I wanted to reuse that functionality.

Dmitri, your suggestion is interesting regarding protocols, but I was giving it a try and I’m not sure exactly how you could accomplish it. For example, I tried the following.

protocol Summable: ArrayLiteralConvertible, CollectionType, _DestructorSafeContainer {
    typealias T: FloatingPointType, FloatLiteralConvertible
    func sum() -> T
}

extension Summable {
    func sum() -> T {
        var result: T = 0.0
        vDSP_sve(self, 1, &result, vDSP_Length(self.count)) // Cannot convert value of type 'Self' to expected argument type 'UnsafePointer<Float>'
        return result
    }
}

Well not only did that produce an error, but it doesn’t really makes sense or accomplish the goal because I haven’t distinguished between Float and Double. Neither does the following which has a vague error message.

protocol Summable: CollectionType {
    typealias Element: FloatingPointType, FloatLiteralConvertible
    func sum() -> Element
}

extension Array: Summable { // Type 'Array<Element>' does not conform to protocol 'Summable'
    func sum() -> Element {
        var result: Element = 0.0
        vDSP_sve(self, 1, &result, vDSP_Length(self.count))
        return result
    }
}

I suspect I’m misunderstanding your suggestion since I can’t quite work out how to do this. I’ve been wrangling the type checker here for a while, but I don’t know what the devil to do. With the different syntax for associated types, generic parameters, and constraints it’s a bit difficult to explore different without knowing exactly what to do.

Thanks,

Tyler

···

On Jan 18, 2016, at 5:09 PM, Dmitri Gribenko <gribozavr@gmail.com> wrote:

On Mon, Jan 18, 2016 at 5:06 PM, David Turnbull via swift-users > <swift-users@swift.org> wrote:

How about this...

public func dot<genType:FloatingPointVectorType>(x:genType, _ y:genType) ->
genType.Element {

   let a = genType(x, y, *)

   return a.reduce(genType.Element(0)) { $0 + ($1 as! genType.Element) }

}

This won't be calling into Accelerate. The assumption is that
Accelerate is manually-optimized and heavily tuned, and it will be
faster than a naive Swift implementation.

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Dmitri Gribenko) #7

Here's a sketch.

//
// Mock of the Accelerate library.
//

// For exposition only, use the Accelerate function instead.
func fastDotProductOfDoubles(
  lhs: UnsafeBufferPointer<Double>,
  _ rhs: UnsafeBufferPointer<Double>
) -> Double {
  fatalError("call vDSP_dotprD")
}

// For exposition only, use the Accelerate function instead.
func fastDotProductOfFloats(
  lhs: UnsafeBufferPointer<Float>,
  _ rhs: UnsafeBufferPointer<Float>
) -> Float {
  fatalError("call vDSP_dotpr")
}

// For exposition only, use the Accelerate function instead.
func fastSumOfDoubles(data: UnsafeBufferPointer<Double>) -> Double {
  fatalError("call Accelerate")
}

// For exposition only, use the Accelerate function instead.
func fastSumOfFloats(data: UnsafeBufferPointer<Float>) -> Float {
  fatalError("call Accelerate")
}

//
// Swift wrapper for the Accelerate library.
//

protocol AccelerateFloatingPoint {
  static func _dot(
    lhs: UnsafeBufferPointer<Self>,
    _ rhs: UnsafeBufferPointer<Self>
  ) -> Self

  static func _sum(data: UnsafeBufferPointer<Self>) -> Self
}

extension Float : AccelerateFloatingPoint {
  static func _dot(
    lhs: UnsafeBufferPointer<Float>,
    _ rhs: UnsafeBufferPointer<Float>
  ) -> Float {
    return fastDotProductOfFloats(lhs, rhs)
  }

  static func _sum(data: UnsafeBufferPointer<Float>) -> Float {
    return fastSumOfFloats(data)
  }
}

extension Double : AccelerateFloatingPoint {
  static func _dot(
    lhs: UnsafeBufferPointer<Double>,
    _ rhs: UnsafeBufferPointer<Double>
  ) -> Double {
    return fastDotProductOfDoubles(lhs, rhs)
  }

  static func _sum(data: UnsafeBufferPointer<Double>) -> Double {
    return fastSumOfDoubles(data)
  }
}

func dot<T : AccelerateFloatingPoint>(lhs: [T], _ rhs: [T]) -> T {
  return lhs.withUnsafeBufferPointer {
    (lhs) in
    return rhs.withUnsafeBufferPointer {
      (rhs) in
      return T._dot(lhs, rhs)
    }
  }
}

func sum<T : AccelerateFloatingPoint>(data: [T]) -> T {
  return data.withUnsafeBufferPointer {
    (data) in
    return T._sum(data)
  }
}

Dmitri

···

On Mon, Jan 18, 2016 at 6:34 PM, Tyler Fleming Cloutier <cloutiertyler@aol.com> wrote:

I suspect I’m misunderstanding your suggestion since I can’t quite work out
how to do this. I’ve been wrangling the type checker here for a while, but I
don’t know what the devil to do. With the different syntax for associated
types, generic parameters, and constraints it’s a bit difficult to explore
different without knowing exactly what to do.

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Tyler Cloutier) #8

I will be damned! And that was a fast response too! I absolutely would not ever have thought of extending Float and Double to do this. That is crafty indeed. Is there a reason to convert to the array to an UnsafeBufferPointer before passing it into the Accelerate function?

Are you aware of any Swift 3.0 generics improvements that might make this more obvious?

Thanks for the help!

Tyler

···

On Jan 18, 2016, at 6:47 PM, Dmitri Gribenko <gribozavr@gmail.com> wrote:

On Mon, Jan 18, 2016 at 6:34 PM, Tyler Fleming Cloutier > <cloutiertyler@aol.com> wrote:

I suspect I’m misunderstanding your suggestion since I can’t quite work out
how to do this. I’ve been wrangling the type checker here for a while, but I
don’t know what the devil to do. With the different syntax for associated
types, generic parameters, and constraints it’s a bit difficult to explore
different without knowing exactly what to do.

Here's a sketch.

//
// Mock of the Accelerate library.
//

// For exposition only, use the Accelerate function instead.
func fastDotProductOfDoubles(
lhs: UnsafeBufferPointer<Double>,
_ rhs: UnsafeBufferPointer<Double>
) -> Double {
fatalError("call vDSP_dotprD")
}

// For exposition only, use the Accelerate function instead.
func fastDotProductOfFloats(
lhs: UnsafeBufferPointer<Float>,
_ rhs: UnsafeBufferPointer<Float>
) -> Float {
fatalError("call vDSP_dotpr")
}

// For exposition only, use the Accelerate function instead.
func fastSumOfDoubles(data: UnsafeBufferPointer<Double>) -> Double {
fatalError("call Accelerate")
}

// For exposition only, use the Accelerate function instead.
func fastSumOfFloats(data: UnsafeBufferPointer<Float>) -> Float {
fatalError("call Accelerate")
}

//
// Swift wrapper for the Accelerate library.
//

protocol AccelerateFloatingPoint {
static func _dot(
   lhs: UnsafeBufferPointer<Self>,
   _ rhs: UnsafeBufferPointer<Self>
) -> Self

static func _sum(data: UnsafeBufferPointer<Self>) -> Self
}

extension Float : AccelerateFloatingPoint {
static func _dot(
   lhs: UnsafeBufferPointer<Float>,
   _ rhs: UnsafeBufferPointer<Float>
) -> Float {
   return fastDotProductOfFloats(lhs, rhs)
}

static func _sum(data: UnsafeBufferPointer<Float>) -> Float {
   return fastSumOfFloats(data)
}
}

extension Double : AccelerateFloatingPoint {
static func _dot(
   lhs: UnsafeBufferPointer<Double>,
   _ rhs: UnsafeBufferPointer<Double>
) -> Double {
   return fastDotProductOfDoubles(lhs, rhs)
}

static func _sum(data: UnsafeBufferPointer<Double>) -> Double {
   return fastSumOfDoubles(data)
}
}

func dot<T : AccelerateFloatingPoint>(lhs: [T], _ rhs: [T]) -> T {
return lhs.withUnsafeBufferPointer {
   (lhs) in
   return rhs.withUnsafeBufferPointer {
     (rhs) in
     return T._dot(lhs, rhs)
   }
}
}

func sum<T : AccelerateFloatingPoint>(data: [T]) -> T {
return data.withUnsafeBufferPointer {
   (data) in
   return T._sum(data)
}
}

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Dmitri Gribenko) #9

I will be damned! And that was a fast response too! I absolutely would not ever have thought of extending Float and Double to do this.

The idea is that with Swift generics in most cases you need to find
the set of types that vary among your calls, and then hang a protocol
onto those types.

That is crafty indeed. Is there a reason to convert to the array to an UnsafeBufferPointer before passing it into the Accelerate function?

Just so that we don't get extra reference counting while passing the
array through layers of wrapping.

Dmitri

···

On Mon, Jan 18, 2016 at 7:14 PM, Tyler Fleming Cloutier <cloutiertyler@aol.com> wrote:

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/