Sam
Sam

Reputation: 526

Problem with recursive enums in Swift 5.1

I'm learning recursive enums in Swift 5.1 with Swift documentation.

Here is a code.

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)

    func evaluate(_ expression: ArithmeticExpression) -> Int {
        switch expression {
        case let .number(value):
            return value
        case let .addition(left, right):
            return evaluate(left) + evaluate(right)
        case let .multiplication(left, right):
            return evaluate(left) * evaluate(right)
        }
    }
}

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

print(ArithmeticExpression.evaluate(product))

Screen

I think something is going wrong in the last line of code.

What does that mean?

Upvotes: 4

Views: 138

Answers (1)

Alexander
Alexander

Reputation: 63369

evaluate(_:) is an instance function of ArithmeticExpression, i.e. you have to call it on an instance of ArithmeticExpression (with that instance being what self refers to). The type of evaluate(_:) is (ArithmeticExpression) -> Int.

Swift lets you call instance functions on types. What you get back is an unbound instance function. That is to say, a function with no value bound as its self value yet. That's what you're doing when you run ArithmeticExpression.evaluate on its own. The unbound instance function that you get back has the type:

(ArithmeticExpression) -> (ArithmetricExpression) -> Int
// ^---  the "self"        ^--- the "expression" param ^--- the final return value.

By calling it and providing product as an argument (ArithmeticExpression.evaluate(product)), what you get back is a function of type (ArithmeticExpression) -> Int. This function is a bound instance function, i.e. self is now bound (it now has the value of product), but it's awaiting to be called yet again, with another ArithmeticExpression as an argument.

There's two ways to solve this to achieve what you want:

  1. Either make this a static function. A static function isn't called on an instance, it's called directly on the type, as you tried to do:

    indirect enum ArithmeticExpression {
        case number(Int)
        case addition(ArithmeticExpression, ArithmeticExpression)
        case multiplication(ArithmeticExpression, ArithmeticExpression)
    
        // Make it static here
        static func evaluate(_ expression: ArithmeticExpression) -> Int {
            switch expression {
            case let .number(value):
                return value
            case let .addition(left, right):
                return evaluate(left) + evaluate(right)
            case let .multiplication(left, right):
                return evaluate(left) * evaluate(right)
            }
        }
    }
    
    let five = ArithmeticExpression.number(5)
    let four = ArithmeticExpression.number(4)
    let sum = ArithmeticExpression.addition(five, four)
    let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
    
    print(ArithmeticExpression.evaluate(product))
    
  2. Keep evaluate as an instance function, but call it directly on the instance your want to evaluate, rather than on the type. Since self would be the expression you're interested in, you no longer need the expression parameter:

    indirect enum ArithmeticExpression {
        case number(Int)
        case addition(ArithmeticExpression, ArithmeticExpression)
        case multiplication(ArithmeticExpression, ArithmeticExpression)
    
        func evaluate() -> Int {
            switch self {
            case let .number(value):
                return value
            case let .addition(left, right):
                return left.evaluate() + right.evaluate()
            case let .multiplication(left, right):
                return left.evaluate() * right.evaluate()
            }
        }
    }
    
    let five = ArithmeticExpression.number(5)
    let four = ArithmeticExpression.number(4)
    let sum = ArithmeticExpression.addition(five, four)
    let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
    
    print(product.evaluate())
    

    I would say this is probably the more "idiomatic" version.

Upvotes: 2

Related Questions