Why Is Generic Specialization Lost Inside a Generic Function

Why is generic specialization lost inside a generic function

Specialization is not a replacement for inheritance. It should be used to improve performance, not change behavior.

For example, distance(from:to:) is usually O(k), where k is the distance. For RandomAccessCollection it can be performed in O(1) due to a specialization. But the result is the same either way.

Specialization is done at compile-time based on the information the compiler has. In your example, the compiler can see that boolArray is a [Bool], and so it uses the specialized extension. But inside of isBool, all that the compiler knows is that array is an Array. It doesn't know when compiling the function what kind of Array will be passed. So it picks the more general version to cover all cases.

(The compiler may create multiple versions of isBool in the binary for optimization purposes, but luckily I haven't found any situations where this impacts what extensions or overloads are called. Even if it actually creates an inlined, Bool-specific version of isBool, it will still use the more general Array extension. That's a good thing.)

Leaving your extensions in place, the following would do what you expect (though I don't encourage this):

func isBool<Element>(_ array: [Element]) -> Bool {
array.isBool
}

func isBool(_ array: [Bool]) -> Bool {
array.isBool
}

Now isBool is overloaded and the most specific one will be selected. Within the context of the second version, array is known to be [Bool], and so the more specialized extension will be selected.

Even though the above works, I would strongly recommend against using specialized extensions or ambiguous overloads that change behavior. It is fragile and confusing. If isBool() is called in the context of another generic method where Element is not known, it again may not work as you expect.

Since you want to base this on the runtime types, IMO you should query the type at runtime using is. That gets rid of all the ambiguity. For example:

extension Array {
var isBool: Bool { Element.self is Bool.Type }
}

func isBool<Element>(_ array: [Element]) -> Bool {
array.isBool
}

You can make this much more flexible and powerful by adding a protocol:

protocol BoolLike {}
extension Array {
var isBool: Bool { Element.self is BoolLike.Type }
}

Now, any types you want to get "bool-like" behavior just need to conform:

extension Bool: BoolLike {}

This allows you all the flexibility of your extensions (i.e. the isBool code doesn't need to know all the types), while ensuring the behavior is applied based on runtime types rather than compile-time types.


Just in case it comes up, remember that protocols do not conform to themselves. So [BoolLike] would return isBool == false. The same is true for an extension with where Element: BoolLike. If you need that kind of thing to work, you need to deal with it explicitly.

extension Array {
var isBool: Bool {
Element.self is BoolLike.Type || Element.self == BoolLike.self
}
}

Why is specialization in C# generics limited?


What does it mean by "specialization"? Is it not the same as instantiation of a generic type with a specific type argument?

Author explains in the portion of his answer dedicated to Java generics that

specialization of a generic type [is] the ability to use specialized source code for any particular generic argument combination.

In other words, it is an ability to do something special if a generic type parameter is of a specific type. Supplying an implementation of List<T> that represents individual elements as bits when you instantiate the type as List<bool> would be an example of specialization.

What does it mean by "the degree of specialization is limited"?

Author means that although you can write things like

if (typeof(T) == typeof(bool)) {
...
}

your abilities to respond to a combination of type arguments are limited, because any decision on a type combination has to be made at run-time.

Why is it "a result of the fact that a generic type definition is compiled before any reification happens"?

Because reification is done in CLR, well after C# compiler is out of the picture. The compiler must produce a generic type definition for CLR to use as a "template" for making closed constructed types for instances of a generic class.

Why is generic type information lost in generic functions with a Comparable constraint?

It's not the String. Your closure { $0.hasPrefix("A") } has the return type Bool, which is assigned to U. Bool is Equatable, but not Comparable.

You probably want the closure to return Bool, but selectComparable to return U.

Edit

Here's evidence that returning a String (which is Comparable) instead of a Bool (not Comparable) will compile:

func selectComparable<T: Comparable, U: Comparable>(x:T, f:(T) -> U) -> U {
return f(x)
}
var b4 = selectComparable("ABC") { (str: String) -> String in str }

Swift - Cannot explicitly specialize a generic function

Give the closure an explicit type to fix T:

let task = AsyncTask.background{ (progress: Float -> Void, fulfill: MyAwesomeObject -> Void, reject: NSError -> Void, configure: SwiftTask.TaskConfiguration) -> Void in
let obj = MyAwesomeObject()
//-- ... do work here
fulfill(obj)
}

How to implement specialized versions of a generic function?

Remember that you always implement a trait for something. Therefore, trait implementation must always contain for clause:

impl SomeTrait for Something

If there is no for, then it is not a trait implementation. In your case impl Foo<i32> is not an implementation of Foo for i32 or whatever you think it is; it is an inherent method declaration clause on the bare trait object type Foo<i32>.

What you actually want is possible to do using Self type parameter:

trait Foo { 
fn foo(a: Self, b: Self, c: Self);
}

impl Foo for i32 {
fn foo(a: i32, b: i32, c: i32) {}
}

impl Foo for i16 {
fn foo(a: i16, b: i16, c: i16) {}
}

fn main() {
Foo::foo(1i32,2,3);
Foo::foo(1i16,2,3);
}

This code works.

Note that now Foo is implemented for a certain type. The type a trait is implemented for is available via the implicit Self type parameter, and you can see how it is used in foo() declaration.



Related Topics



Leave a reply



Submit