Aaron Phan
Aaron Phan

Reputation: 43

Error "Type of expression is ambiguous without more context" when using generics type constraint

Trying create a generic function, with the recursive object, but it got some issues. This is what I did

enum Color {
    case red, green, blue
}

enum Size {
    case small, medium, large
}

struct Product {
    let name: String
    let color: Color
    let size: Size
}

protocol Specification {
    associatedtype T
    func isSatisfied(item: T) -> Bool
}

protocol Filtering {
    associatedtype T
    func filter<S: Specification>(in items: [T], with specification: S) -> [T] where S.T == T
}

struct BetterFilter: Filtering {
    typealias T = Product
    
    func filter<S>(in items: [Product], with specification: S) -> [Product] where S : Specification, Product == S.T {
        var results = [Product]()
        for item in items {
            if specification.isSatisfied(item: item) {
                results.append(item)
            }
        }
        return results
    }
}

struct ColorSpecification: Specification {
    
    typealias T = Product
    
    let colorToFilter: Color
    
    func isSatisfied(item: Product) -> Bool {
        return item.color == colorToFilter
    }
}

struct SizeSpecification: Specification {
    
    typealias T = Product
    
    let sizeToFilter: Size
    
    func isSatisfied(item: Product) -> Bool {
        return item.size == sizeToFilter
    }
}

struct MultiSpecifications<T, Spec: Specification>: Specification where Spec.T == T {

    let listOfSpecifications: Array<Spec>
    
    func isSatisfied(item: T) -> Bool {
        listOfSpecifications.allSatisfy {
            $0.isSatisfied(item: item)
        }
    }
}

///******************* RUN ***********************///

let apple = Product(name: "Apple", color: .red, size: .small)
let banana = Product(name: "Banana", color: .green, size: .large)
let bmwCar = Product(name: "BMW", color: .blue, size: .large)
let laptop = Product(name: "Dell laptop", color: .blue, size: .large)

let allProducts: [Product] = [apple, banana, bmwCar, laptop]

/// ⛔️ THIS IS WHERE THE ERROR OCCURS
let multiSpecs = MultiSpecifications(listOfSpecifications: [
    SizeSpecification(sizeToFilter: .large),
    ColorSpecification(colorToFilter: .blue)
])

let results = betterFilter.filter(in: allProducts, with: multiSpecs)

Seem like I've been missing something declaring the MultiSpecifications struct. It keeps throwing the error /// >>>> "Type of expression is ambiguous without more context" when I create an instance of MultiSpecifications

Upvotes: 2

Views: 88

Answers (1)

Sweeper
Sweeper

Reputation: 272845

Right now, the array in MultiSpecification can only contain one type of Specification, that being Spec, but you are giving it two types of Specification - SizeSpecification and ColorSpecification.

You can make the array contain multiple types of Specifications if its type were [any Specification<T>]. And to be able to write a type like that, you'd need to make T the primary associated type:

protocol Specification<T> {
    associatedtype T
    func isSatisfied(item: T) -> Bool
}

With this change, the declaration of filter can also be more concise:

protocol Filtering {
    associatedtype T
    func filter(in items: [T], with specification: any Specification<T>) -> [T]
}

struct BetterFilter: Filtering {
    
    func filter(in items: [Product], with specification: any Specification<Product>) -> [Product] { ... }

}

Then you can do:

struct MultiSpecifications<T>: Specification {

    let listOfSpecifications: [any Specification<T>]

    ...

}

let apple = Product(name: "Apple", color: .red, size: .small)
let banana = Product(name: "Banana", color: .green, size: .large)
let bmwCar = Product(name: "BMW", color: .blue, size: .large)
let laptop = Product(name: "Dell laptop", color: .blue, size: .large)

let allProducts: [Product] = [apple, banana, bmwCar, laptop]

let multiSpecs = MultiSpecifications(listOfSpecifications: [
    SizeSpecification(sizeToFilter: .large),
    ColorSpecification(colorToFilter: .blue)
])

let results = BetterFilter().filter(in: allProducts, with: multiSpecs)

Upvotes: 1

Related Questions