How to create generic functions within a non-generic struct

This is what I'm trying to do unsuccessfully:

struct Foop {
func callAsFunction(val: T) -> T {
return calc(val)
}

func calc<T: Int>( _ val: T) -> T {
    let g = val + 5
    return val
}

func calc<T: Double>( _ val: T) -> T {
    let g = val + Double(5)
    return val
}

}

The types I have are going to be type aliased blocks if it matters.

You want a protocol constraint. If there is an existing protocol, use it. If not, make a new one.

func addFive<T: Numeric>(_ val: T)  -> T {
  return val + 5
}
2 Likes

To add, you want calc as a protocol member since you want to use dynamic dispatch:

protocol Calcable {
  func calc() -> Self
}

extension Double: Calcable {
  func calc() -> Double {
    self + 0.5
  }
}

struct Foo {
  func callAsFunction<T: Calcable>(val: T) -> T {
    val.calc()
  }
}
1 Like

Lantua - thanks!

OK - getting close! My types are not Numerics - they are:

typealias ArrayVoidClosure = ((NSArray?) -> Void)
typealias AnyVoidClosure = ((Any?) -> Void)
...

Please describe in words what you want. Not in code, in words.

You cannot add 5 to an ArrayVoidClosure.

If you want help achieving something, you need to explain what it is you want to achieve.

Do you mean you want to just call them? Or to treat them differently before/after calling them?

If you want to just call them, you can use generic on the related type, argument and/or return type:

func calc<T>(closure: (T?) -> Void) {
  ...
  closure(someValue)
}

If you want to treat them differently within the same function, that'd require some protocol gymnastic since you can't conform any of the components of AnyVoidClosure (You can't conform a closure or Any to any protocol). The simplest solution would be to use == on its metatype. It doesn't scale well, but I'd say it's much simpler if you have only a few types:

func calc<T>(closure: (T?) -> Void) {
  if T.self == NSArray.self {
    ...
  } else if T.Self == Any.self {
    ...
  }
}
1 Like

Was trying to make a simple example, bad choice. Here is a real world example without any generics - I keep think I should be able to make it some or mostly generic:

typealias XClosure = ((NSArray?) -> Void)
typealias YClosure = ((Any?, Error?) -> Void)

struct Str {
func callAsFunction(val: @escaping XClosure) -> XClosure {
return wrapper(val)
}
func callAsFunction(val: @escaping YClosure) -> YClosure {
return wrapper(val)
}

private func wrapper(_ originalHandler: @escaping XClosure) -> XClosure {
    let newHandler: XClosure = { a in
        DispatchQueue.main.async {
            originalHandler(a)
        }
    }
    return newHandler
}
private func wrapper(_ originalHandler: @escaping YClosure) -> YClosure {
    let newHandler: YClosure = { a, b in
        DispatchQueue.main.async {
            originalHandler(a, b)
        }
    }
    return newHandler
}

}

That'd be tricky, since functions with different arity (number of arguments) doesn't have much in common (right now). Though you can combine ones with the same arity:

/// With 1 argument
func wrapper1<T>(_ original: @escaping (T) -> ()) -> (T) -> () {
  ...
}

/// With 2 arguments
func wrapper2<T, U>(_ original: @escaping (T, U) -> ()) -> (T, U) -> () {
  ...
}
1 Like

That's a great idea! Will definitely remove lots of duplicated code!

I got it to compile, not tested if it really works or not. But interesting result. Where you had different function names, I wanted to use one name as its not obvious how many arguments each closure takes from looking at its name. When I tried that, I got an "ambiguous" warning. But, I found it I added dummy unused parameters to each function, depending on the Generic types, then it all compiles fine. So for the function with two generics:

func callAsFunction<T, U>(completionHandler originalHandler: @escaping (T, U) -> Void, trace: Trace = Trace(), dummy1: Int = 0) -> (T, U) -> Void {

Then for the one with 3:

func callAsFunction<T, U, V>(completionHandler originalHandler: @escaping (T, U, V) -> (), trace: Trace = Trace(), dummy1: Int = 0, dummy2: Int = 1) -> (T, U, V) -> ()

Anyway, this saved me from writing over a dozen non-generics functions!

Ah right, you can convert multi-argument functions to single-argument ones due to a revision to SE-0110 so the call site is rightfully ambiguous. You can't convert it back so I think that wouldn't meet your expectation since the wrapped functions become single-argument:

let foo = wrapper1 { (a: Int, b: String) in }
// Needs to call foo((0, "test")), not foo(0, "test")

You could put just one dummy variable on the single-arity function since type checker prefer functions with less defaulted arguments, and there's no ambiguity between others like 2 vs 3-arity.

func wrapper<T>(type: T.Type = T.self, _ original: @escaping (T) -> ()) -> (T) -> () {
  ...
}

func wrapper<T, U>(_ original: @escaping (T, U) -> ()) -> (T, U) -> () {
  ...
}

func wrapper<T, U, V>(_ original: @escaping (T, U, V) -> ()) -> (T, U, V) -> () {
  ...
}

While we're at it, we might as well use T.Type as the dummy variable since we can use it to guide type checker should the need arise.

Though I'd suggest that you separate (only) the single-argument function into a separate name since that's where all this mess come from, and the fix is dealing with the nuance of type checker, which is not guaranteed to stay.

Well, my post you responded to was wrong. I am using additional parameters (recording file name, line number, in this call), but the dummy parameters didn't actually help me. What did was actually defining the expected return type:

let foo: SomeClosureType = wrapper(...)

I'm overloading callAsFunction() with every wrapper (4 of them) and clang is fine with it! Still to test execution!

My explanation should still apply equally well. And I'd still suggest that you separate a one-argument case (maybe by using a different name?) since it'd otherwise create ambiguous/unexpected overload choice without type annotation.