Reputation: 1007
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
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