The Secret to Swift is Enums

I’ve found the CS193P (Developing iOS 8 Apps with Swift) iTuneU class really helpful in wrapping my old Objective-C head around Apple’s new Swift programming language. Yes, I know we’re at iOS9 but the fundamentals of the class are still relevant and coaxing the code to compile in iOS 9/Swift 2.0 is a fun little last in and of itself.

The big revelation for me is how core Swift’s enums are to understanding and using the language properly. When Apple decided to improve it’s development environment it could have done so  in a million different directions. Apple  could have  just patched up Objective-C. Apple could have moved over to  Java or Scala or even JavaScript. (I was hoping for  JavaScript as Node.js and ECMAScript 6 have proved that JavaScript is an industrial strength language with a real future.)

So when I learned that Apple created a new language for iOS and Mac OS X (and now WatchOS and tvOS) application development I was intrigued and disappointed and hopeful all at the same time. Many parts of Swift seems easy and cool but some parts were down right intellectually challenging: Classes and Structs? Super fancy Enums? Optionals? Strings with out a simple function to get their length? What the heck is going on here?

Swift seemed promising. But it was also changing rapidly over the last year. I fooled around with it but when I hit optionals and weak references I realized the language was not going to sugarcoat the problems of null pointers and memory management–it was trying to make them more tractable.

Optionals were especially vexing. var amount: Double? was not a Double. It was a potential Double. Amount! had better be a Double or it would crash my program. If it was’t for Xcode’s automated static code analysis I never would have been able to put the ? and ! in the right places after the right variable names. And I didn’t like that feeling.

But then I found CS193P and Professor Paul Hergarty’s remarkable ability to explain things simply.

Enums in Swift are amazing little machines. They have little in common with C Enum other than you can use them to define collections of related constants. Enums are types with values that evaluate to themselves. But  you can associate  a “raw value”, like a number or a string, or more a advanced value, like Tuples or Arrays or Dictionaries. And with advanced types come functions and closures.

Yikes. These are not your father’s Enums. As Professor Hergarty’s says, Swift Enums are types used for when you want to switch between values. And, point of fact, Swift Enums are defined like a Switch structure with case statements. Each of the cases, except with raw values, can be of different types.

And boom! That explains Optionals. When you have a value that could be nonexistent or of some type, an Optional is just an Enum that wraps up it safely so you can pass it around without crashing. ? and ! are just syntactic shortcuts for accessing the different states an Optional variable might be in. Imagining that Optionals are Enums gave me the handle my mind needed to process the whole concept.

In CS193P Professor Paul Hergarty uses an Enum to power his “Calculator Brain”. In any other programming language an Enum would be used to make the code more readable. But in Swift you can use an Enum as the glue to associate states with types with functions. I have a feeling that this Enum-driven mechanism is the cleanest way to write a Swift state machine. It’s still a little rough around the edges and verbose. But it’s a super reusable pattern good not only for the mathematical operations of a calculator but for managing the states of a web server, business application, or game.

//: Playground - noun: a place where people can play

import UIKit
import Foundation

class CalculatorBrain {
    
    enum Op: CustomStringConvertible {
        case Operand(Double)
        case BinaryOperation(String, (Double, Double) -> Double)
        
        var description: String {
            get {
                switch self {
                case .Operand(let operand):
                    return "\(operand)"
                case .BinaryOperation(let symbol, _):
                    return symbol
                }
            }
        }
    }
    
    var opStack = [Op]()
    var knownOps = [String:Op]()
    
    init() {
        func learnOp (op: Op) {
            knownOps[op.description] = op
        }
        
        learnOp(Op.BinaryOperation("*", *))
        learnOp(Op.BinaryOperation("+", +))
        learnOp(Op.BinaryOperation("-", {$1 - $0}))
        learnOp(Op.BinaryOperation("/", {$1 / $0}))
    }
    
    func evaluate() -> Double? {
        let (result, _) = evaluate(opStack)
        return result
    }
    
    func evaluate(ops: [Op]) -> (result: Double?, remainingOps: [Op]) {
        if !ops.isEmpty {
            var remainingOps = ops
            let op = remainingOps.removeLast()
            
            switch op {
            case .Operand(let operand):
                return (operand, remainingOps)
            case .BinaryOperation(_, let operation):
                let op1Evaluation = evaluate(remainingOps)
                if let operand1 = op1Evaluation.result {
                    let op2Evaluation = evaluate(op1Evaluation.remainingOps)
                    if let operand2 = op2Evaluation.result {
                        return (operation(operand1, operand2), op2Evaluation.remainingOps)
                    }
                }
            }
        }
        return (nil, ops)
    }
    
    func pushOperand(operand: Double) -> Double? {
        opStack.append(Op.Operand(operand))
        return evaluate()
    }
    
    func popOperand() -> Double? {
        if !opStack.isEmpty {
            opStack.removeLast()
        }
        return evaluate()
    }
    
    func performOperation(symbol: String) -> Double? {
        if let operation = knownOps[symbol] {
            opStack.append(operation);
        }
        return evaluate()
    }
    
    func showStack() -> String? {
        return opStack.map{ "\($0)" }.joinWithSeparator(" ")
    }
}

var cb = CalculatorBrain()

print(cb.knownOps)

cb.showStack()
cb.pushOperand(1)
cb.pushOperand(2)
cb.performOperation("+")
cb.pushOperand(3)
cb.pushOperand(4)
cb.performOperation("/")
cb.pushOperand(5)
cb.pushOperand(6)
cb.performOperation("*")
cb.pushOperand(7)
cb.pushOperand(8)
cb.performOperation("-")
cb.showStack()

You should  watch all the videos  in CS193P to get the full picture. But in the meantime I’ve created a simplified version of Professor Hergarty’s CalculatorBrain suitable for a Swift Playground (above). As you fool around with the code keep in mind the following points:

  • CalculatorBain is a class and when it’s instantiated it’s initializer creates a dictionary of operations that it knows about based on type. You can add more known operations by writing a  little code in one place. In the sample code the operations for addition, subtraction, division, and multiplication are written very concisely using Swift’s shortcut syntax for closures with default parameter names. (This reminds me of the best features  Perl.)
  • The custom type Op is an Enum that is self documenting and has values associated with either a Double (for operands) or a Dictionary (for operations  indexed by name). So simple lookups via  a case statement are all that is needed to process input and output a result.
  • The two Evaluate functions are recursive, calling each other until every operand or operation needed in the Ops stack is consumed. Evaluate basically does this:
    • Until the Ops stack is empty pull an item off the end of the stack.
    • If that item is an Operand return it and the remainder of the Ops stack.
    • If that item is an operation dig into the Ops stack until you can get two operands and then execute the function associated with the operation using the operands.
    • The return the result with the remaining Ops stack.
  • If I had to write this code without using recursion and without Swift’s muscular Enums, well, there would be a lot more code and it would be more “hard coded”.

The only improvement I can think of would be to declare the known operations as a literal Dictionary. But the Swift compiler isn’t ready for that and complains: “Expression was too complex to be solved in reasonable time”. I guess if Swift can’t do something swiftly it doesn’t want to do it at all!

 


Posted

in

, ,

by