I have a custom data structure that I'd like to be able to use in a generic way. Specifically, I'd like to pass a getter as an argument, so I can run a particular function on a numeric property. This is the basic pattern I'm following (toy problem):
/* A getter for a numeric property of a generic type. */
func genericGet<E, T where T: NumberConvertible>(object: E, getter: E -> T) -> T {
return getter(object)
}
struct EV : Linkable, Equatable, Comparable {
var pitch: Int
var ts: Double
var dur: Double
}
let anEvent = EV(pitch: 61, ts: 2.55, dur: 1.0)
func getSomethingFromSomething<E, T: NumberConvertible>(thing: E, getThing: E -> T) -> T {
return myGet(thing, getter: getThing)
}
let somePitch = getSomethingFromSomething(anEvent, getThing: {$0.pitch}) print("pitch = \(somePitch)") // prints "61"All good! However, when I try the same approach in my generic struct:
struct MultiLink<E where E: Linkable, E: Equatable, E: Comparable> {
var item: E
var linkedItems: [E]?
func bestTransition<E, T: NumberConvertible>(getter: E -> T, scaling: Double) -> E? {
if let items = linkedItems {
let c = items.count
for i in 0..<c {
let linkedItem = items[i]
let itemProp = genericGet(item, getter: getter)
// yes, this is unfinished...
}
}
return nil
}
init(item: E, linkedItems: [E]?) {
self.item = item
self.linkedItems = linkedItems
}
}...the compiler complains:
Playground execution failed: Spliqs.playground:70:57: error: cannot convert value of type 'E -> T' to expected argument type '_ -> _'
let itemProp = genericGet(item, getter: getter)Fair enough; maybe it's a bit silly passing the "getter" through to another "getter"... So I also tried:
let itemProp = getter(item)...at line 12, which seems to make sense, since E -> T is a getter signature, but I got a bizarre error:
Playground execution failed: Spliqs.playground:70:32: error: cannot invoke 'getter' with an argument list of type '(E)'
let itemProp = getter(item)
^
Spliqs.playground:70:32: note: expected an argument list of type '(E)'
let itemProp = getter(item)...which is totally strange because in the actual error it tells me I can't use '(E)' as the argument, and in the note it tells me it expects '(E)' as an argument.
Wha?? 🙂
(And yes, I did post this on Stack Overflow, in case anybody saw it there.)
>> The pattern E -> T is from this answer (on SO) …
Ah, I'm beginning to understand. The point is that "E -> T" is a closure type. As such, it has nothing directly to do with getters. As well as code like this:
getSomethingFromSomething(anEvent, getThing: {$0.ts})you can have code like this:
getSomethingFromSomething(anEvent, getThing: {25})(The second will produce a compile error in this form, because it doesn't allow the compiler to infer that the closure should have one parameter. You have to write "{ _ in 25}" instead. But the principle is the same as before. It's just a closure with one parameter and a return type.)
You can also, certainly, have getSomethingFromSomething be generic, such as:
struct EV {
var pitch: Int
var ts: Double
var dur: Double
}
let anEvent = EV(pitch: 61, ts: 2.55, dur: 1.0)
func getSomethingFromSomething<E,T>(thing: E, getThing: E -> T) -> T {
return getThing(thing)
}
print("pitch = \(getSomethingFromSomething(anEvent, getThing: {$0.pitch}))")
print("ts = \(getSomethingFromSomething(anEvent, getThing: {$0.ts}))")(Done in a playground, simplifying your code a bit.)
All that aside, I went back to your original piece of code, and here what's wrong:
struct MultiLink<E where E: Linkable, E: Equatable, E: Comparable> {
…
func bestTransition<E, T: NumberConvertible>(getter: E -> T, scaling: Double) -> E? {You've re-declared the generic parameter "E" for the function, and this overrides the declaration on the struct. When the compiler told you that "(E)" wasn't "(E)", it wasn't lying — you have two separate "E"s!
You should write it like this:
struct MultiLink<E where E: Linkable, E: Equatable, E: Comparable> {
…
func bestTransition<T: NumberConvertible>(getter: E -> T, scaling: Double) -> E? {Now there's only one E. 😉
P.S. Yes, you don't need all that genericGet/getThing stuff. When you have a closure parameter, you can simply apply it as if it was a function name, like using a function pointer in C.
P.P.S. Also coming in Swift 3: you'll no longer be able to omit the parentheses around a parameter list. Get used to typing "(E) -> T".