How to store ref to any kind of function, and call with it argument list?

In the code below, I'm trying to store a reference to a function with any number of arguments, with any return type. And I want to be able to invoke it.

Help?

See the comments with "*******"

typealias Void = ()

func equalTypes(_ me: [Any.Type], _ other: [Any.Type]) -> Bool {
    guard me.count == other.count else { return false }
    
    for i in 0 ..< me.count {
        if me[i] != other[i] { return false }
    }
    return true
}

struct Function {
    var returnType: Any.Type
    var argTypes: [Any.Type]
    var function: Any // ******* any function *******
    
    func perform(_ args: [Any]) -> Any {
        // ******* call function() with args, return result *******
        return 0
    }
    
    func isCompatible(_ other: Function) -> Bool {
        return self.returnType == other.returnType &&
            equalTypes(argTypes, other.argTypes)
    }
}

func vToV() {
    print("vToV")
}

func intToInt(_ i: Int) -> Int {
    print("intToInt")
    return 4
}

let f = Function(returnType: Void.self,
                 argTypes: ,
                 function: vToV)
let r = f.perform()
print(r)

let f2 = Function(returnType: Int.self,
                 argTypes: [Int.self],
                 function: intToInt)
let r2 = f2.perform([12])
print(r2)

assert(f.isCompatible(f))
assert(!f.isCompatible(f2))

···

--
C. Keith Ray
Senior Software Engineer / Trainer / Agile Coach
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

Unfortunately we don’t have a way to invoke a function with a runtime argument list because that would require runtime code generation in the most general case.

I would hack around it by handling the common cases of no arguments, 1 argument, 2 arguments, etc up to some fixed number of arguments that you consider “enough”. Use a switch on the type of the function.

Slava

···

On Nov 3, 2017, at 2:21 PM, C. Keith Ray via swift-users <swift-users@swift.org> wrote:

In the code below, I'm trying to store a reference to a function with any number of arguments, with any return type. And I want to be able to invoke it.

Help?

See the comments with "*******"

typealias Void = ()

func equalTypes(_ me: [Any.Type], _ other: [Any.Type]) -> Bool {
    guard me.count == other.count else { return false }
    
    for i in 0 ..< me.count {
        if me[i] != other[i] { return false }
    }
    return true
}

struct Function {
    var returnType: Any.Type
    var argTypes: [Any.Type]
    var function: Any // ******* any function *******
    
    func perform(_ args: [Any]) -> Any {
        // ******* call function() with args, return result *******
        return 0
    }
    
    func isCompatible(_ other: Function) -> Bool {
        return self.returnType == other.returnType &&
            equalTypes(argTypes, other.argTypes)
    }
}

func vToV() {
    print("vToV")
}

func intToInt(_ i: Int) -> Int {
    print("intToInt")
    return 4
}

let f = Function(returnType: Void.self,
                 argTypes: ,
                 function: vToV)
let r = f.perform()
print(r)

let f2 = Function(returnType: Int.self,
                 argTypes: [Int.self],
                 function: intToInt)
let r2 = f2.perform([12])
print(r2)

assert(f.isCompatible(f))
assert(!f.isCompatible(f2))

--
C. Keith Ray
Senior Software Engineer / Trainer / Agile Coach
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

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

um... how can I cast functions taking one argument to a "generic function pointer" type?

typealias OneArgsFunction = (Any)->Any

func intToInt(_ i: Int) -> Int { return 4 }
func floatToFloat(_ f: Float) -> Float { return 0.4 }

var function : OneArgsFunction = intToInt
function = floatToFloat

error: f.playground:4:34: error: cannot convert value of type '(Int) -> Int' to specified type 'OneArgsFunction' (aka '(Any) -> Any')
var function : OneArgsFunction = intToInt
                                 ^~~~~~~~

error: f.playground:5:12: error: cannot assign value of type '(Float) -> Float' to type 'OneArgsFunction' (aka '(Any) -> Any')
function = floatToFloat
           ^~~~~~~~~~~~

···

--
C. Keith Ray
Senior Software Engineer / Trainer / Agile Coach
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

On Nov 3, 2017, at 2:24 PM, Slava Pestov <spestov@apple.com> wrote:

Unfortunately we don’t have a way to invoke a function with a runtime argument list because that would require runtime code generation in the most general case.

I would hack around it by handling the common cases of no arguments, 1 argument, 2 arguments, etc up to some fixed number of arguments that you consider “enough”. Use a switch on the type of the function.

Slava

On Nov 3, 2017, at 2:21 PM, C. Keith Ray via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

In the code below, I'm trying to store a reference to a function with any number of arguments, with any return type. And I want to be able to invoke it.

Help?

See the comments with "*******"

typealias Void = ()

func equalTypes(_ me: [Any.Type], _ other: [Any.Type]) -> Bool {
    guard me.count == other.count else { return false }
    
    for i in 0 ..< me.count {
        if me[i] != other[i] { return false }
    }
    return true
}

struct Function {
    var returnType: Any.Type
    var argTypes: [Any.Type]
    var function: Any // ******* any function *******
    
    func perform(_ args: [Any]) -> Any {
        // ******* call function() with args, return result *******
        return 0
    }
    
    func isCompatible(_ other: Function) -> Bool {
        return self.returnType == other.returnType &&
            equalTypes(argTypes, other.argTypes)
    }
}

func vToV() {
    print("vToV")
}

func intToInt(_ i: Int) -> Int {
    print("intToInt")
    return 4
}

let f = Function(returnType: Void.self,
                 argTypes: ,
                 function: vToV)
let r = f.perform()
print(r)

let f2 = Function(returnType: Int.self,
                 argTypes: [Int.self],
                 function: intToInt)
let r2 = f2.perform([12])
print(r2)

assert(f.isCompatible(f))
assert(!f.isCompatible(f2))

--
C. Keith Ray
Senior Software Engineer / Trainer / Agile Coach
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

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

(Int) -> Int is not a subtype of (Any) -> Any, because a value of the latter type can be called with an argument type that is not an Int, for example a String:

let fn: (Int) -> Int = …
let fn2 = (Any) -> Any = fn // pretend this works

fn2(“hi”) // what does this do?

I think you’ll need to do something with generics where you wrap the original function value in a thunk that tests the argument type first, eg

func erase<T, U>(fn: (T) -> U) -> (Any) -> Any {
  return { any in fn(arg as! T) }
}

I haven’t thought this through properly since it’s late, but it might be a good starting point.

Slava

···

On Nov 3, 2017, at 7:01 PM, C. Keith Ray <keithray@mac.com> wrote:

um... how can I cast functions taking one argument to a "generic function pointer" type?

typealias OneArgsFunction = (Any)->Any

func intToInt(_ i: Int) -> Int { return 4 }
func floatToFloat(_ f: Float) -> Float { return 0.4 }

var function : OneArgsFunction = intToInt
function = floatToFloat

error: f.playground:4:34: error: cannot convert value of type '(Int) -> Int' to specified type 'OneArgsFunction' (aka '(Any) -> Any')
var function : OneArgsFunction = intToInt
                                 ^~~~~~~~

error: f.playground:5:12: error: cannot assign value of type '(Float) -> Float' to type 'OneArgsFunction' (aka '(Any) -> Any')
function = floatToFloat
           ^~~~~~~~~~~~

--
C. Keith Ray
Senior Software Engineer / Trainer / Agile Coach
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

On Nov 3, 2017, at 2:24 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

Unfortunately we don’t have a way to invoke a function with a runtime argument list because that would require runtime code generation in the most general case.

I would hack around it by handling the common cases of no arguments, 1 argument, 2 arguments, etc up to some fixed number of arguments that you consider “enough”. Use a switch on the type of the function.

Slava

On Nov 3, 2017, at 2:21 PM, C. Keith Ray via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

In the code below, I'm trying to store a reference to a function with any number of arguments, with any return type. And I want to be able to invoke it.

Help?

See the comments with "*******"

typealias Void = ()

func equalTypes(_ me: [Any.Type], _ other: [Any.Type]) -> Bool {
    guard me.count == other.count else { return false }
    
    for i in 0 ..< me.count {
        if me[i] != other[i] { return false }
    }
    return true
}

struct Function {
    var returnType: Any.Type
    var argTypes: [Any.Type]
    var function: Any // ******* any function *******
    
    func perform(_ args: [Any]) -> Any {
        // ******* call function() with args, return result *******
        return 0
    }
    
    func isCompatible(_ other: Function) -> Bool {
        return self.returnType == other.returnType &&
            equalTypes(argTypes, other.argTypes)
    }
}

func vToV() {
    print("vToV")
}

func intToInt(_ i: Int) -> Int {
    print("intToInt")
    return 4
}

let f = Function(returnType: Void.self,
                 argTypes: ,
                 function: vToV)
let r = f.perform()
print(r)

let f2 = Function(returnType: Int.self,
                 argTypes: [Int.self],
                 function: intToInt)
let r2 = f2.perform([12])
print(r2)

assert(f.isCompatible(f))
assert(!f.isCompatible(f2))

--
C. Keith Ray
Senior Software Engineer / Trainer / Agile Coach
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

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

Thanks, that's working for me, though I have to specify the return type explicitly to avoid compile error.

typealias TwoArgsFunction = (Any,Any)->Any

func intToInt(_ i: Int, d: Double) -> Int {
    print("intToInt \(i) \(d)")
    return 4
}

func typeEraseTwo<Arg1T,Arg2T,R>(r: R.Type,
        fn: @escaping (Arg1T,Arg2T)->R) -> TwoArgsFunction {
    return {
        guard let arg1 = $0 as? Arg1T else {
                fatalError("\(Arg1T.self) wrong type \(type(of:$0)) \($0) ")
        }
        guard let arg2 = $1 as? Arg2T else {
                fatalError("\(Arg2T.self) wrong type \(type(of:$1)) \($1) ")
        }
        return fn(arg1, arg2)
    }
}

var function : TwoArgsFunction = typeEraseTwo(r: Int.self, fn: intToInt)
let r = function(12, 5.5)
print(r)
// prints "intToInt 12 5.5" and "4"

...and Xcode just crashed while I was typing in a playground.

···

--
C. Keith Ray
Senior Software Engineer / Trainer / Agile Coach
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

On Nov 3, 2017, at 10:31 PM, Slava Pestov <spestov@apple.com> wrote:

(Int) -> Int is not a subtype of (Any) -> Any, because a value of the latter type can be called with an argument type that is not an Int, for example a String:

let fn: (Int) -> Int = …
let fn2 = (Any) -> Any = fn // pretend this works

fn2(“hi”) // what does this do?

I think you’ll need to do something with generics where you wrap the original function value in a thunk that tests the argument type first, eg

func erase<T, U>(fn: (T) -> U) -> (Any) -> Any {
  return { any in fn(arg as! T) }
}

I haven’t thought this through properly since it’s late, but it might be a good starting point.

Slava

On Nov 3, 2017, at 7:01 PM, C. Keith Ray <keithray@mac.com <mailto:keithray@mac.com>> wrote:

um... how can I cast functions taking one argument to a "generic function pointer" type?

typealias OneArgsFunction = (Any)->Any

func intToInt(_ i: Int) -> Int { return 4 }
func floatToFloat(_ f: Float) -> Float { return 0.4 }

var function : OneArgsFunction = intToInt
function = floatToFloat

error: f.playground:4:34: error: cannot convert value of type '(Int) -> Int' to specified type 'OneArgsFunction' (aka '(Any) -> Any')
var function : OneArgsFunction = intToInt
                                 ^~~~~~~~

error: f.playground:5:12: error: cannot assign value of type '(Float) -> Float' to type 'OneArgsFunction' (aka '(Any) -> Any')
function = floatToFloat
           ^~~~~~~~~~~~

--
C. Keith Ray
Senior Software Engineer / Trainer / Agile Coach
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

On Nov 3, 2017, at 2:24 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

Unfortunately we don’t have a way to invoke a function with a runtime argument list because that would require runtime code generation in the most general case.

I would hack around it by handling the common cases of no arguments, 1 argument, 2 arguments, etc up to some fixed number of arguments that you consider “enough”. Use a switch on the type of the function.

Slava

On Nov 3, 2017, at 2:21 PM, C. Keith Ray via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

In the code below, I'm trying to store a reference to a function with any number of arguments, with any return type. And I want to be able to invoke it.

Help?

See the comments with "*******"

typealias Void = ()

func equalTypes(_ me: [Any.Type], _ other: [Any.Type]) -> Bool {
    guard me.count == other.count else { return false }
    
    for i in 0 ..< me.count {
        if me[i] != other[i] { return false }
    }
    return true
}

struct Function {
    var returnType: Any.Type
    var argTypes: [Any.Type]
    var function: Any // ******* any function *******
    
    func perform(_ args: [Any]) -> Any {
        // ******* call function() with args, return result *******
        return 0
    }
    
    func isCompatible(_ other: Function) -> Bool {
        return self.returnType == other.returnType &&
            equalTypes(argTypes, other.argTypes)
    }
}

func vToV() {
    print("vToV")
}

func intToInt(_ i: Int) -> Int {
    print("intToInt")
    return 4
}

let f = Function(returnType: Void.self,
                 argTypes: ,
                 function: vToV)
let r = f.perform()
print(r)

let f2 = Function(returnType: Int.self,
                 argTypes: [Int.self],
                 function: intToInt)
let r2 = f2.perform([12])
print(r2)

assert(f.isCompatible(f))
assert(!f.isCompatible(f2))

--
C. Keith Ray
Senior Software Engineer / Trainer / Agile Coach
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

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