fferri
fferri

Reputation: 18950

how to solve "Protocol ... can only be used as a generic constraint..."

Consider this fragment of code:

public protocol Evaluable {
    typealias ReturnType
    func eval(s: State) -> ReturnType
}

protocol AlgebraicExpr : Evaluable {
}

public struct Const : AlgebraicExpr {
    var value: Int = 0

    public func eval(s: State) -> Int {
        return value
    }
}

public struct Add : AlgebraicExpr {
    let a, b: AlgebraicExpr

    public func eval(s: State) -> Int {
        return a.eval() + b.eval()
    }
}

it is invalid because Add cannot have an AlgebraicExpr variable.

But I really need Add to store two AlgebraicExprs. How would you solve this in Swift?

For completeness, State is just a struct, for example:

public struct State {
    var env = Dictionary<String, Int>()

    init(_ d: Dictionary<String, Int> = [:]) {
        env = d
    }

    subscript(s: String) -> Int? {
        get {return env[s]}
        set {env[s] = newValue}
    }
}

Upvotes: 0

Views: 272

Answers (1)

Logan
Logan

Reputation: 53142

You can't store two protocols that rely on Self, or a typealias because when abstracted as a protocol, the functionality is undetermined. Type Evaluable has no real meaning without understanding the value of ReturnType, so it's not possible for the system to function logically and understand what should be returned.

Take for instance something like this (not valid)

let thing: Evaluable = ...
let result = thing.eval(state) // What should return type be?

The return type can't be inferred through the protocol alone, it requires an implementation by a specified type that gives more knowledge about its behavior by defining the typealias ReturnType

The reason these can function as generic constraints is that the implementation can be inferred through the generic parameters.

func genericConstrained<T: Evaluable>(evaluable: T, state: State) -> T.ReturnType {
    return evaluable.eval(state)
}

In this example, when we call genericConstrained on a particular conforming object, all of its other functionality can be inferred.

tl;dr

If however, you also constrain your package, you could do as you wish:

public struct Add<ExpressionType : AlgebraicExpr> : AlgebraicExpr {
    let a, b: ExpressionType

    public func eval(s: State) -> ExpressionType.ReturnType {
        return a.eval() + b.eval()
    }
}

Then use like

let add: Add<SomeConformingType> = ...

Upvotes: 3

Related Questions