R. Rincón
R. Rincón

Reputation: 361

How do I enforce that a class passed in as a parameter can be parsed from a String in Swift?

I'm trying to represent a function call so that I can make a little scripting language for creating games. Right now I'm just trying to set up the interfaces between all the protocols and classes that I need. I have a class, FunctionCall<T>. It has a method execute() that executes the current function and returns an instance of type T?. FunctionCall also has a an array of instances of type FunctionCall to represent any parameters. It also has a field, stringRepresentation, which is a string representation of the function call, which the user will enter. This string might be something like createNode(named: myCircle) or, in the base case, it might be just a literal, like myCircle. To be clear, myCircle in this case is a String. I'm probably going to choose not to use quotation marks around Strings in my little scripting language.

My problem is with the execute() method. I want to return an instance of type T. So far, I've thought of enforcing that T conforms to a protocol (let's call it Parseable) that enforces that it has a method that takes a String and returns an instance of type T. The problem I found with that approach is that I have no way of creating such a method, because I don't have a way to reference the type that will be implementing the protocol from within the protocol. In other words, if T is SKShapeNode, I have no way or referencing SKShapeNode from within Parseable, so that I can indicate that the return type must be an SKShapeNode. The other approach I found is to make Parseable have a required initializer that takes a String. This works when structs implement the protocol, but not with classes. The problem I get when I try to implement the protocol in a class is that the class wants me to make the initializer required, but I can't do that because I can't put a required initializer in an extension.

I want the FunctionCall class to look something like this

class FunctionCall<T: Parseable> {
    var parameters = [FunctionCall]()

    var stringRepresentation: String!

    init(stringRepresentation: String) {
        self.stringRepresentation = stringRepresentation
    }

    func execute() -> T? {
        guard let indexOfFirstLeftParen = stringRepresentation.firstIndex(of: "("),
            let indexOfLastRightParen = stringRepresentation.lastIndex(of: ")") else {
                // then this is a literal value, because no function is being called
                return T(string: stringRepresentation)
        }

        // TODO: implement

        return nil
    }
}

Upvotes: 2

Views: 91

Answers (1)

Sweeper
Sweeper

Reputation: 272385

The problem I found with that approach is that I have no way of creating such a method, because I don't have a way to reference the type that will be implementing the protocol from within the protocol.

Actually, that problem can be solved by using Self, which refers to whatever the conforming type is:

// I think this is a better name
protocol ConvertibleFromString {
    static func from(string: String) -> Self?
}

// implementation
extension Int : ConvertibleFromString {
    static func from(string: String) -> Int? {
        return Int(string)
    }
}

For non-final classes, you have to create a final subclass like this:

final class SKSpriteNodeFinal : SKSpriteNode, ConvertibleFromString {
    static func from(string: String) -> SKSpriteNodeFinal? {
        ...
    }
}

Note that this prevents your protocol from being used as a variable's type:

var foo: ConvertibleFromString? = nil // error

But I don't think that will be a problem, as you only use ConvertibleFromString as generic constraints, which is OK.

Upvotes: 3

Related Questions