Parametrize function with generic function

(Alfred Zien) #1

I've faced an issue, and I don't know, am I doing something that swift can do or not :slight_smile:
Basically, I have two generic functions, and I want to run same tests on those:

func isBazable<T: Collection>(_ q: T, _ s: T) -> Bool {...}
func isBazableImproved<T: Collection>(_ q: T, _ s: T) -> Bool {...}

func check(f: ???) {
  assert(f("foo", "baz"))
  assert(f([1, 2], [3, 4]))
}

check(isBazable)
check(isBazableImproved)

When isBazable[Improved] was defined only for strings, there was no problems with it. Now it blows my mind that it looks so simple, but I don't even know how to approach this.

#2

func isBazable<T: Collection>(_ q: T, _ s: T) -> Bool {...} doesn't define a function. It's just a mould/template/recipe from which infinite number of functions is created, one for every Collection. You can only pass specific functions around, not recipes, so unfortunately you cannot do that. :( You have to decide which one of the infinite number of functions you want to pass in, or make check a generic/template/recipe itself

#3

This is insufficient. The request in the original post is that check should require that its argument is a generic function.

To the best of my knowledge, there is currently no way in Swift to express the requirement that something must be generic.

1 Like
(John McCall) #4

That's correct; the only way to do higher-rank polymorphism in Swift is via a protocol with a generic requirement.

1 Like
(Matthew Johnson) #5

To provide a specific example:

protocol CheckHelper {
    static func call<T: Collection>(_ q: T, _ s: T) -> Bool
}
func check<Helper: CheckHelper>(_ helper: Helper.Type) -> Bool {
    assert(helper.call("foo", "baz"))
    assert(helper.call([1, 2], [3, 4]))
}

enum IsBazable: CheckHelper {
    static func call<T: Collection>(_ q: T, _ s: T) -> Bool {
        return isBazable(q, s)
    }
}
check(IsBazable.self)

enum IsBazableImproved: CheckHelper {
    static func call<T: Collection>(_ q: T, _ s: T) -> Bool {
        return isBazableImproved(q, s)
    }
}
check(IsBazableImproed.self)

It's worth pointing out that you could also make the call method an instance method which would allow the helper types to capture state if necessary and also allow check to omit saying .call when a final design for SE-0253: Callable values of user-defined nominal types is accepted.

As an aside, I think your example is a good demonstration of a use case where it really does make sense to allow "static static callables" (i.e. static func call). This allows us to encode stateless higher-rank function signatures in protocols that are reasonably convenient for users. Certainly not as nice as actual type-system higher-rank functions but a lot nicer than having to write .call everywhere.

6 Likes