[RFD] Non-Self Protocol Requirements


(Erica Sadun) #1

The following situation has come up for me now and then: I want to work with groups of types that share a common behavior, but that behavior is not sourced in the implementation of the Self type but rather in a secondary type. Specifically, could Swift be extended to introduce a protocol requirement that discusses how a type is used by a secondary type and not the kind of member provided directly by the type. For example:

// These are all numbers
let int32: Int32 = 1; let int8: Int8 = 1
let double: Double = 1.0; let cgfloat: CGFloat = 1.0; let float: Float = 1

// They can all be converted to Double using Double.init
Double(int32); Double(int8); Double(double); Double(cgfloat); Double(float)

// A heterogeneous collection cannot be unified into a single protocol
let foo: [Any] = [int32, int8, double, cgfloat, float]
foo.map{ Double($0) } // Can't work, Any doesn't make any sense here

The kind of thing I am looking for is something like this:

protocol DoubleSource {
    Double.init(Self)
}

In other words, the functionality constraint is not provided by the base type but by a second type to which the base type is a parameter.

My use case is for unrelated types (that is, there's no inheritance relationship like you'd find in `UISwitch` and `UISlider`, for example -- both of which are `UIView` and `UIControl`), where there is a secondary type that implements behavior with the same signature for these separate types, such as the Double initializer. Where this pops up the most is in working with Sprite/SceneKit, GamePlayKit, QuartzCore, Accelerate, unifying my numeric values so I can mix and match calls and clean up the flow where some calls require CGPoint, others need float2, etc. Ideally I would be able to

extension DoubleSource {
    func bar() -> T {
        let value = Double.init(self)
        // do something with value; return T of some type
    }
}

let foo: [DoubleSource] = [int32, int8, double, cgfloat, float]
foo.map{ bar($0) } // would work

Would something like this be a valuable direction to extend Swift? Is it something found in other languages? Does this kind of requirement have a name? Is it *pragmatically* possible to introduce it in Swift?

Thanks in advance for your thoughts and insights.

-- E


(Patrick Smith) #2

Yes, something like this would be handy! Even the ability to coerce from one type to another, if that destination type has a keyless initialiser for the source type.

Here’s some imaginary syntax for with Erica’s array example. I would prefer a way to not have a separate type for ‘DoubleSource’ if possible.

let foo: [Double] = Double[int32, int8, double, cgfloat, float] // As if you had written Double(…) around each item

or

let foo: [Double] = [int32, int8, double, cgfloat, float](Double.init) // // As if you had written Double(…) around each item

Here’s another use case I’ve had:

enum Deferred<Result> {
  typealias UseResult = () throws -> Result
  
  case unit(UseResult)
  case future(((UseResult) -> ()) -> ())

  init(_ subroutine: UseResult) {
    self = .unit(subroutine)
  }
}

Instead of this:

struct Example {
  func next() -> Deferred<Int> {
    return Deferred{ 42 }
  }
}

It would be nice to be able to do this:

struct Example {
  func next() -> Deferred<Int> {
    return { 42 }
  }
}

I don’t know if that’s playing with fire, but it would be seemingly nice for Swift to automatically infer what I want.

···

On 17 May 2016, at 12:15 AM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

The following situation has come up for me now and then: I want to work with groups of types that share a common behavior, but that behavior is not sourced in the implementation of the Self type but rather in a secondary type. Specifically, could Swift be extended to introduce a protocol requirement that discusses how a type is used by a secondary type and not the kind of member provided directly by the type. For example:

// These are all numbers
let int32: Int32 = 1; let int8: Int8 = 1
let double: Double = 1.0; let cgfloat: CGFloat = 1.0; let float: Float = 1

// They can all be converted to Double using Double.init
Double(int32); Double(int8); Double(double); Double(cgfloat); Double(float)

// A heterogeneous collection cannot be unified into a single protocol
let foo: [Any] = [int32, int8, double, cgfloat, float]
foo.map{ Double($0) } // Can't work, Any doesn't make any sense here

The kind of thing I am looking for is something like this:

protocol DoubleSource {
    Double.init(Self)
}

In other words, the functionality constraint is not provided by the base type but by a second type to which the base type is a parameter.

My use case is for unrelated types (that is, there's no inheritance relationship like you'd find in `UISwitch` and `UISlider`, for example -- both of which are `UIView` and `UIControl`), where there is a secondary type that implements behavior with the same signature for these separate types, such as the Double initializer. Where this pops up the most is in working with Sprite/SceneKit, GamePlayKit, QuartzCore, Accelerate, unifying my numeric values so I can mix and match calls and clean up the flow where some calls require CGPoint, others need float2, etc. Ideally I would be able to

extension DoubleSource {
    func bar() -> T {
        let value = Double.init(self)
        // do something with value; return T of some type
    }
}

let foo: [DoubleSource] = [int32, int8, double, cgfloat, float]
foo.map{ bar($0) } // would work

Would something like this be a valuable direction to extend Swift? Is it something found in other languages? Does this kind of requirement have a name? Is it *pragmatically* possible to introduce it in Swift?

Thanks in advance for your thoughts and insights.

-- E

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


(Vladimir) #3

I really like this suggestion. Actually IMO we don't need explicit type declaration here:

// clearly it is an array of Double
let foo = Double[int32, int8, double, cgfloat, float]

We can use the same feature for other types like
let foo = String[1, 1.0, true]

Or even some complex type:
let foo = ComplexType[1, 1.0, true]

But I really don't like this:
//let foo: [Double] = [int32, int8, double, cgfloat, float](Double.init)

···

On 16.05.2016 18:06, Patrick Smith via swift-evolution wrote:

let foo: [Double] = Double[int32, int8, double, cgfloat, float] // As if
you had written Double(…) around each item