Swift 3 Closure Overload Resolution

Swift 3 closure overload resolution

This is probably due to the change in the default "escaping" behaviour for closure parameters.

If you change the specific function to:

func f(_ a:@escaping (Int)->Int) 
{
print("Narrow")
}

it will print "Narrow" as expected (this is the same change that you probably had to make in several other places that were more obvious)

Is it possible to overload static method by closure signature in Swift 3?

Yes you can overload static method by closure type in Swift 3, but you
need to specify the type of the parameter for the first function as
its parameters partially matches with that of second function

 Some.doSomething(first: "") { (number:[Int]?) in

}

Some.doSomething(first: "") { (number, value) in

}

Is there shorthand to specify a specific overload when passing a function as a closure-based argument?

In Swift, the "base name" of a method is the method name without any arguments:

  • Foo.init(x:y:z:)Foo.init
  • Foo.bar(_:)Foo.bar
  • Foo.baz(baz:)Foo.baz
  • Foo.quux()Foo.quux

When referring to a method by name (rather than calling it), Swift will allow you to refer to it by its base name, so long as the usage is not ambiguous:

struct Foo {
func f(intValue: Int) {}
}

let f = Foo().f

However, when there are multiple methods with the same base name, you can run into situations with ambiguity:

struct Foo {
func f(intValue: Int) {}
func f(stringValue: String) {}
}

let f = Foo().f // error: ambiguous use of 'f'

When this happens, you can either:

  1. Use explicit typing to help disambiguate the methods, if possible:

    let f1: (Int) -> Void = Foo().f
    let f2: (String) -> Void = Foo().f

    Or,

  2. Refer to the method by its fully-qualified name (name with parameter names included):

    let f1 = Foo().f(intValue:)
    let f2 = Foo().f(stringValue:)

In your case, because both methods have the same type signature, you can't use approach (1) to disambiguate between the calls, and will have to resort to (2).

The issue you ran into is that the parameter name you were using was slightly off:

// ❌ Foo.init(doubleValue:)
// ✅ Foo.init(valueToDouble:)

let foos = values.map(Foo.init(valueToDouble:)) // ✅

This does work, and will work as a shorthand instead of having to call the method directly inside of a closure argument.

Ambiguous method overload with closures in Swift, but only when closure returns a value

You are misunderstanding what's happening. It's not about returning a value. Simplify, for a clearer demonstration of that:

func call8(_: UInt8) -> String { "8" }
func call16(_: UInt16) -> String { "16" }

func ƒ(_ call: (UInt8) -> String) { call(8) }
func ƒ(_ call: (UInt16) -> String) { call(16) }

ƒ(call8) // "8"
ƒ(call16) // "16"

Instead, it's all about multiple lines. Read all about it!

https://forums.swift.org/t/multiline-closure-is-not-inferring-the-return-type/40013

As for the fix, you've got to explicitly type, for disambiguation. /p>

(string, arg: UInt8)
(string, arg: UInt16)

Nested closure resolution different between methods and properties?

I believe it is a bug. If you change the map for a Expando it behaviors differently:

f = {
g = {
{ -> keySet() }()
}

g.delegate = new Expando(a: 1000, b: 900, c: 800, keySet: { 'g keyset' })
g.resolveStrategy = Closure.DELEGATE_ONLY
g()

}

f.delegate = new Expando(a: 90, x: 9, y: 1, keySet: { 'f keyset' })

assert f() == 'g keyset'

f = {
g = {
{ -> keySet() }()
}

g.delegate = [a: 1000, b: 900, c: 800]
g.resolveStrategy = Closure.DELEGATE_ONLY
g()

}

f.delegate = [a: 90, x: 9, y: 1]

assert f().toList() == ['a', 'b', 'c'] // fails :-(

Maybe filling a JIRA?

How does typecasting/polymorphism work with this nested, closure type in Swift?

It's all about variance and Swift closures.

Swift is covariant in respect to closure return type, and contra-variant in respect to its arguments. This makes closures having the same return type or a more specific one, and same arguments or less specific, to be compatible.

Thus (Arg1) -> Res1 can be assigned to (Arg2) -> Res2 if Res1: Res2 and Arg2: Arg1.

To express this, let's tweak a little bit the first closure:

import Foundation

let nsErrorHandler: (CustomStringConvertible) -> NSError = { _ in
return NSError(domain: "", code: 0, userInfo: nil)
}
var anyHandler: (Int) -> Error = nsErrorHandler

The above code works because Int conforms to CustomStringConvertible, while NSError conforms to Error. Any would've also work instead of Error as it's even more generic.

Now that we established that, let's see what happens in your two blocks of code.

The first block tries to assign a more specific argument closure to a less specific one, and this doesn't follow the variance rules, thus it doesn't compile.

How about the second block of code? We are in a similar scenario as in the first block: closures with one argument.

  • we know that String, or Void, is more specific that Any, so we can use it as return value
  • (Int) -> Void is more specific than (Any) -> Void (closure variance rules), so we can use it as argument

The closure variance is respected, thus intResolver and stringResolver are a compatible match for anyResolver. This sounds a little bit counter-intuitive, but still the compile rules are followed, and this allows the assignment.

Things complicate however if we want to use closures as generic arguments, the variance rules no longer apply, and this due to the fact that Swift generics (with few exceptions) are invariant in respect to their type: MyGenericType<B> can't be assigned to MyGenericType<A> even if B: A. The exceptions are standard library structs, like Optional and Array.

Closure conversion and separate compilation of higher-order function calls

This is a pretty deep question with a lot of ramifications, and I don't want to write a scholarly article here. I will just scratch the surface and will point you to more information elsewhere. I am basing my response on personal experience with the Glorious Glasgow Haskell Compiler and with Standard ML of New Jersey, as well as scholarly papers written about those systems.

The key distinction made in an ambitious compiler is the distinction between known calls and unknown calls. For languages with higher-order functions, a secondary but still important distinction is whether the call is fully saturated (which we can decide only at a known call site).

  • A known call means a call site where the compiler knows exactly what function is being called an how many parameters it expects.

  • An unknown call means the compiler can't figure out what function might be called.

  • A known call is fully saturated if the function being called is getting all the parameters it expects, and it is going straight to code. If the function is getting fewer arguments than it expects, the function is partially applied and the call results only in the allocation of a closure

For example, if I write the Haskell functions

mapints :: (Integer -> a) -> [a]
mapints f = map f [1..]

then the call to map is known and fully saturated.

If I write

inclist :: [Integer] -> [Integer]
inclist = map (1+)

then the call to map is known and partially applied.

Finally, if I write

compose :: (b -> c) -> (a -> c) -> (a -> c)
compose f g x = f (g x)

then the calls to f and g are both unknown.

The main thing mature compilers do is optimize known calls. In your classification above this strategy falls mostly under #2.

  • If all call sites to a function are known, a good compiler will create a special-purpose calling convention just for that function, e.g., passing arguments in just the right registers to make things work out nicely.

  • If some but not all call sites of a function are known, the compiler may decided it worthwhile to create a special-purpose calling convention for the known calls, which will either be inlined or will use a special name known only to the compiler. The function exported under the name in the source code will use a standard calling convention, and its implementation is typically the thin layer which makes an optimized tail call to the specialized version.

  • If a known call is not fully saturated, the compiler just generates code to allocate the closure right there in the caller.

The representation of closures (or whether first-class functions are handled by some other technique such as lambda lifting or defunctionalization) is largely orthogonal to the handling of known vs unknown calls.

(It may be worth mentioning an alternative approach, used by MLton: it is a whole-program compiler; it gets to see all the source code; it reduces all functions to first order using a technique I've forgotten. There are still unknown calls because general control-flow analysis in higher-order languages is intractable.)


Regarding your final questions:

  • I think this issue is just one facet of the messy problem called "how to compile first-class functions". I've never heard a special name for just this issue.

  • Yes, there are other approaches. I've sketched one and mentioned another.

  • I'm not sure if there are any great, broad studies on tradeoffs, but the best one I know of, which I recommend very highly, is Making a Fast Curry: Push/Enter vs. Eval/Apply for Higher-Order Languages by Simon Marlow and Simon Peyton Jones. One of the many great things about this paper is that it explains why the type of a function does not tell you whether a call to that function is fully saturated.


To wrap up your numbered alternatives: number 1 is a nonstarter.
Popular compilers use a hybrid strategy related to numbers 2 and 3.
I've never heard of anything resembling number 4; the distinction between known and unknown calls seems more useful than distinguising top-level functions from arguments of function type.



Related Topics



Leave a reply



Submit