Generic Class with Class-Bound Constraint Cannot Be Parametrized by Class-Bound Protocol

Can you explain/solve these 'generic constraint' compiler errors?

This is an incorrect use of a protocol. Protocols associated types are determined by the implementer of the protocol, not the user of the protocol. Generics are determined by the user of the protocol. There is no automatic wrapping of protocols into generics (that feature will be called "existential containers" and we don't know when it will come if ever).

[Listener] is not a complete type. what is the ValueType?

For this particular case, there is no reason for a protocol. It captures exactly one function. You should just pass the function:

class Binding<T> {
var value: T?
var listeners:[(Binding<T>, T) -> ()] = []
func fire() {
if let value = value {
listeners.forEach { $0(self, value) }
}
}

init() {}
}

If you really need the protocol, you can lift it into a type eraser (AnyListener):

protocol Listener {
associatedtype ValueType
func call(_ binding:Binding<ValueType>, value:ValueType)
}

struct AnyListener<ValueType>: Listener {
let _call: (Binding<ValueType>, ValueType) -> ()
init<L: Listener>(_ listener: L) where L.ValueType == ValueType {
_call = listener.call
}
func call(_ binding: Binding<ValueType>, value: ValueType) {
_call(binding, value)
}
}

class Binding<T> {
var value:T?
var listeners: [AnyListener<T>] = []
func fire() {
if let value = value {
listeners.forEach { $0.call(self, value: value) }
}
}
}

let listener = ... some listener ....
binding.listeners.append(AnyListener(listener))

Or you could just make AnyListener into a more concrete struct and get rid of the protocol. But for such a simple case I'd pass the function usually.

Protocol function with generic type

Status of the features needed to make this work:

  • Recursive protocol constraints (SE-0157) Implemented (Swift 4.1)
  • Arbitrary requirements in protocols (SE-0142) Implemented (Swift 4)
  • Generic Type Aliases (SE-0048) Implemented (Swift 3)

Looks like this is currently not possible without introducing boxed types (the "type erasure" technique), and is something looked at for a future version of Swift, as described by the Recursive protocol constraints and Arbitrary requirements in protocols sections of the Complete Generics Manifesto (since generic protocols are not going to be supported).

When Swift supports these two features, the following should become valid:

protocol Parser {
associatedtype Result
associatedtype SubParser: Parser where SubParser.Result == Result

func parse() -> ParserOutcome<Result, SubParser>
}

enum ParserOutcome<Result, SubParser: Parser where SubParser.Result == Result> {
case result(Result)
case parser(P)
}

With generic typealiases, the subparser type could also be extracted as:

typealias SubParser<Result> = Parser where SubParser.Result == Result

How to pass a class type as a function parameter

You are approaching it in the wrong way: in Swift, unlike Objective-C, classes have specific types and even have an inheritance hierarchy (that is, if class B inherits from A, then B.Type also inherits from A.Type):

class A {}
class B: A {}
class C {}

// B inherits from A
let object: A = B()

// B.Type also inherits from A.Type
let type: A.Type = B.self

// Error: 'C' is not a subtype of 'A'
let type2: A.Type = C.self

That's why you shouldn't use AnyClass, unless you really want to allow any class. In this case the right type would be T.Type, because it expresses the link between the returningClass parameter and the parameter of the closure.

In fact, using it instead of AnyClass allows the compiler to correctly infer the types in the method call:

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: T.Type, completionHandler handler: ((T) -> ())) {
// The compiler correctly infers that T is the class of the instances of returningClass
handler(returningClass())
}

Now there's the problem of constructing an instance of T to pass to handler: if you try and run the code right now the compiler will complain that T is not constructible with (). And rightfully so: T has to be explicitly constrained to require that it implements a specific initializer.

This can be done with a protocol like the following one:

protocol Initable {
init()
}

class CityInfo : NSObject, Initable {
var cityName: String?
var regionCode: String?
var regionName: String?

// Nothing to change here, CityInfo already implements init()
}

Then you only have to change the generic constraints of invokeService from <T> to <T: Initable>.

Tip

If you get strange errors like "Cannot convert the expression's type '()' to type 'String'", it is often useful to move every argument of the method call to its own variable. It helps narrowing down the code that is causing the error and uncovering type inference issues:

let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self

CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/

}

Now there are two possibilities: the error moves to one of the variables (which means that the wrong part is there) or you get a cryptic message like "Cannot convert the expression's type () to type ($T6) -> ($T6) -> $T5".

The cause of the latter error is that the compiler is not able to infer the types of what you wrote. In this case the problem is that T is only used in the parameter of the closure and the closure you passed doesn't indicate any particular type so the compiler doesn't know what type to infer. By changing the type of returningClass to include T you give the compiler a way to determine the generic parameter.

Java two-level parameterized type inference

For the first compilation issue, your problem comes from the fact that you try to assign it to some variable, whose type does not match.

The simplest approach is to start from the useList() call to make sure that this simple code compiles:

List<? extends Number> numList = makeList();
useList(numList);

If you check this code, it compiles already.

Now ask your IDE to assign the result to a local variable. The IDE will compute the type of the expression for you to declare the variable, and voilà:

Set<? extends NumberLists<? extends List<? extends Number>, ? extends Number>> paramSet =
useList(numList);

(If possible, get rid of the L type on NumbersList, that will simplify things a lot)

For the paraNL1.add(numList); call, it cannot work because of the wildcard: the compiler has no way to check that paraNL1 accepts your unknown type of list.

Even if you fix the declaration by following the same process:

NumberLists<? extends List<? extends Number>, ? extends Number> paraNL1 =
paramSet.iterator().next();

paraNL1.add(numList); // will still not compile

You see that you are trying to use paraNL1 as a consumer (it consumes numList), and PECS tells you that you must declare it with super for that to work.

Indeed, the compiler knows that there must exist some type L extends List<? extends Number> for your NumberLists, but it does not know it and has no way to check that it matches the type of numList. L could be for example LinkedList<Float> while numList could be ArrayList<Integer>.

Edit: to make it work, you might be able to use a wilcard capture helper method like this:

private static <T extends Number, L extends List<T>> void helper(L numList) {
Set<NumberLists<L, T>> paramSet = useList(numList);
NumberLists<L, T> paraNL1 = paramSet.iterator().next();
paraNL1.add(numList);
}

(this even allows you to get rid of the ? extends NumerLists in the Set declaration)

and call it with

List<? extends Number> numList = makeList();
helper(numList);

On Java generics lower bound usage: ? super T

Here's an example:

The following snippet passes compilation with the signature <T> void copy(List<? super T> dest, List<? extends T> src) but doesn't work with the signature <T> void copy(List<T> dest, List<? extends T> src):

YourClass obj = new YourClass ();
List<HashMap<String,String>> lhm = new ArrayList<>();
List<Map<String,String>> lm = new ArrayList<>();
obj.<HashMap<String,String>>copy (lm,lhm);

Defining generic type based on method (structural typing)

The problem is that

def getC = 100

is not the same function signature as

def getC() = 100

A and B have getC without () so you need to remove the () from the type constraint:

def readC[T <: {def getC: Int}](obj: T) = {
obj.getC
}

The better way to do this is to use a typeclass. Check online for a number of good tutorials on how to do this.

Type constraint for higher-kinded type in Scala

The reason why your attempt did not work is because you have a kind mismatch.
The following:

def functorLaw[A, F[_] :Arbitrary] (fn :Functor[F]) :Prop = forAll { (fa :F[A]) => true }

is just a syntactic sugar for:

def functorLaw[A, F[_]] (fn :Functor[F])(implicit evidence: Arbitrary[F]) :Prop = forAll { (fa :F[A]) => true }

So in essence, the problem is that your method expects an implicit value of type Arbitrary[F] where F is an higher-order type (F[_]), but that does not make sense because Arbitrary does not take an higher order type:

// T is a first order type, it has the kind *
// Simply put, it is not a type constructor
class Arbitrary[T]

For your code to compile as is (and make sense), Arbitrary would have to be declared something like this:

// T is a type constructor, it has the kind * -> *
class Arbitrary[T[_]]

Now for how to fix it.
In your case, the actual arbitrary values that you want are of type F[A], not F (which should go without saying as it's not a concrete type, but a type constructor), so the implicit you need is of type Arbitrary[F[A]]:

def functorLaw[A, F[_]] (fn :Functor[F])(implicit arb: Arbitrary[F[A]]) :Prop = forAll { (fa :F[A]) => true }

And because F[A] does not occur in the type parameter list (there is A and F, but not F[A]), the "context bound" syntactic sugar can not be used and we have to leave it at using an explicit (!) implicit parameter list.



Related Topics



Leave a reply



Submit