Sevan Golnazarian
Sevan Golnazarian

Reputation: 1007

Simplifying Swift Enum

After experimenting with currying in Swift, I came up with the code below. I want to see if it's possible to simplify this enum Operate. Currently, I need to initialize like this:

let multiply = Operate.Multiply.op

I would prefer to have each case have an associated value that directly returns a closure without having to do this hacky switch block. Is this possible?

Here's some code that you can run in a Swift playground:

import Foundation

enum Operate {
    case Plus
    case Minus
    case Multiply
    case unsafeDivide

    var op: (Double) -> (Double) -> Double {
        get {
            switch self {
            case .Plus:
                return { n in
                    return { n + $0}
                }
            case .Minus:
                return { n in
                    return { n - $0}
                }
            case .Multiply:
                return { n in
                    return { n * $0}
                }
            case .unsafeDivide:
                return { n in
                    return { n / $0 }
                }
            }
        }
    }
}

let multiply = Operate.Multiply.op
let plus = Operate.Plus.op
let unsafeDivide = Operate.unsafeDivide.op
// 3 + (16 * 2) -> 35
plus(3)(multiply(16)(2))

Bonus: How can I handle errors with unsafeDivide in a 'Swiftly' manner, that is, prevent this:

let unsafeDivide = Operate.unsafeDivide.op
unsafeDivide(2)(0)

Upvotes: 2

Views: 491

Answers (1)

Sweeper
Sweeper

Reputation: 273380

What you seem to be doing is currying. You remove a lot of duplicated code by extracting a curry function:

func curry<A,B,C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
    return { a in { b in f(a, b) } }
}

// ...

var op: (Double) -> (Double) -> Double {
    switch self {
    case .plus: // please follow Swift naming conventions, enum cases start with a lowercase
        return curry(+)
    case .minus:
        return curry(-)
    case .multiply:
        return curry(*)
    case .unsafeDivide:
        return curry(/)
    }
}

That already looks a lot nicer. You seem to not like switch statements, so here's how you'd do it with a dictionary:

var op: (Double) -> (Double) -> Double {
    let dict: [Operate: (Double, Double) -> Double] =
        [.plus: (+), .minus: (-), .multiply: (*), .unsafeDivide: (/)]
    return curry(dict[self]!)
}

In fact, you can use the new callAsFunction feature in Swift 5.2 to omit even the word op on the caller side:

func callAsFunction(_ a: Double) -> (Double) -> Double {
    op(a)
}

This allows you to do:

Operator.multiply(2)(3)

Using associated values is another way:

enum Operate {
    case plus(Double)
    case minus(Double)
    case multiply(Double)
    case unsafeDivide(Double)

    func callAsFunction(_ b: Double) -> Double {
        switch self {
        case .plus(let a):
            return a + b
        case .minus(let a):
            return a - b
        case .multiply(let a):
            return a * b
        case .unsafeDivide(let a):
            return a / b
        }
    }
}

But I personally don't like it because having associated values means that you can't simply use == to compare enum values, among other restrictions.


Preventing dividing by 0 at compile time is impossible, because the values you pass in might not be compile time constants. If you just want to check for compile time constants, then you might need a static code analyser like SwiftLint. At runtime, division of the Double 0 is well-defined by the IEEE standard anyway. It won't crash or anything.

Upvotes: 3

Related Questions