Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's going on with this generic function?

Sorry for the generic title, it's hard to describe the problem without examples.

Suppose I define the following generic function that is constrained to Equatable types:

func test<T: Equatable>(expect expected: T, run: () -> T) {
    let value = run()
    if value == expected {
        print("OK")
    } else {
        print("Expected: \(expected), Actual: \(value)")
    }
}

Here's an example of using said function:

test(expect: 100) { 10 * 10 } // prints "OK"
test(expect: 1000) { 10 * 10 } // prints "Expected: 1000, Actual: 100"

And, of course, I can store the value instead of using literals:

let e = 100
test(expect: e) { e } // prints "OK"

So far so good, everything works as expected (no pun intended).

Now let's try this with an array:

test(expect: [1, 2]) { [1, 2] } // prints "OK"

Once again, things work out.

But now we try this:

let a = [1, 2]
test(expect: a) { a } // error: cannot convert value of type '() -> [Int]' to expected argument type '() -> _'

So the question I have been building up to is: Why doesn't this work?

Playground correctly infers the type of a to be [Int], so where does the expectation of () -> _ come from?

Trying a bunch of variations of the last example:

test(expect: a) { return a }
test(expect: a) { return a as [Int] }
test(expect: a as [Int]) { return a as [Int] }
test(expect: [1, 2]) { a }
test(expect: [1, 2] as [Int]) { a }

They all result in the same problem. For some reason, Swift seems to think the function expects () -> _.

So maybe it's just because arrays aren't Equatable, but this works:

let a = [1, 2]
[1, 2] == [1, 2]
a == a

I thought I understood generics pretty well, and I'm completely stumped by this. Is this a bug in Swift or a bug in my definition of test()? Can the goal even be accomplished?

The Solution

Thanks to @Sulthan's answer below, I was able to write another version of this function to handle the array case (and any SequenceType for that matter):

public func test<T: SequenceType where T.Generator.Element: Equatable>(expect expected: T, run: () -> T) {
    let result = run()
    // Note: zip() will stop at the shorter array, so this implementation isn't correct, don't use it (it will incorrectly end up saying [1] == [1,2]). This code is just here to demonstrate the function's generic constraint.
    let eq = zip(expected, result).filter(!=).isEmpty
    if eq {
        print("OK")
    } else {
        print("Expected: \(expected), Actual: \(result)")
    }
}

let a: [Int] = [1, 2]
test(expect: [1,2]) { a } // prints "OK"
test(expect: [1,3]) { a } // prints "Expected: [1, 3], Actual: [1, 2]"
like image 306
vopilif Avatar asked Nov 23 '15 21:11

vopilif


People also ask

What does it mean for a function to be generic?

A generic function is a function that is declared with type parameters. When called, actual types are used instead of the type parameters.

What is generic function example?

The print() function is an example of a generic function. A generic function is simply a function that performs a common task by dispatching its input to a particular method-function that is selected on the basis of the class of the input to the generic function.

How do you call a generic function?

To call a generic method, you need to provide types that will be used during the method invocation. Those types can be passed as an instance of NType objects initialized with particular . NET types.

What is generic example?

For example, classes like HashSet, ArrayList, HashMap, etc., use generics very well. There are some fundamental differences between the two approaches to generic types.


1 Answers

Arrays don't automatically conform to Equatable, even if their values are Equatable. However, when you use an array literal directly, the compiler tries to match the type and converts the array to an NSArray which does conform to Equatable.

like image 76
Sulthan Avatar answered Nov 11 '22 18:11

Sulthan