What is the scope of type variables?

188 views Asked by At

I hit some confusing error messages from the Swift compiler ('A' is not convertible to 'A', 'E' is not identical to 'E') and I think it's because I introduced identically named type variables in two scopes, where one scope was nested inside the other.

I'd like to get a more complete idea of how type variables work, and so I have a few related questions:

  • what is the scope of type variables in Swift?
  • ow is the scope affected by nested types and methods?
  • can type variables be shadowed?
  • what happens when a nested method introduces a type variable of the same name as an enclosing class?
  • is it possible to do something like method2 (below)? (my XCode kept crashing and I wasn't able to figure this out)

Here are a few examples to show what I'm trying to figure out:

class MyClass<A> {

    func method1<A>(a:A) -> A {
        // what does A refer to here?
        return a;
    }

    class func staticmethod<A>(a:A) -> A {
        // what does A refer to here?
        return a;
    }

    func method2() -> ((A) -> A) {
        // is this even possible?  
        // I'm not sure how to write method2's type !
        func id<A>(a:A) -> A {
            return a;
        }
        return id;
    }

}
1

There are 1 answers

0
Martin R On

The scope of the generic type of a generic class/struct/method/function is exactly the class/struct/method/function. And yes, it can be shadowed by nested definitions.

In your example, A in method1 is the generic type for that method and will be replaced by the actual type when the method is called. It shadows the generic type of MyClass<A>. Example:

let mc = MyClass<Int>()      // class instantiated with actual type Int
let x = mc.method1("foo")    // method called with actual type String
println(x) // "foo"

The same is true for the static method:

let y = MyClass<Int>.staticmethod(123.5) // method called with type Double
println(y)

In your method2, the inner function does not get a type parameter, it should be (and I have removed one pair of unnecessary parentheses):

func method2a() -> (A) -> A {
    func id(a:A) -> A {
        return a;
    }
    return id;
}

or

func method2b<A>() -> (A) -> A {
    func id(a:A) -> A {
        return a;
    }
    return id;
}

depending on whether it works with the type of the class or introduces its own generic type. Note that you can shorten that using a closure instead of an nested function, e.g.

func method2b<A>() -> (A) -> A {
    return { a in a }
}

In the first case you would use it like

let mc = MyClass<Int>()    // class instantiated with type Int, therefore 
let z = mc.method2a()(12)   // mc.method2a() has signature Int -> Int
println(z)

and in the second case you could do

let mc = MyClass<Int>()   // class instantiated with type Int, but
let z = mc.method2b()("bar") // mc.method2b() has signature String -> String
println(z)

Here the actual type of method2b<A> is inferred from the argument "bar" as String.

Generally, I would prefer to use different names for nested generic types to avoid unnecessary confusion.