Swift: Enums That Use Closures

Swift: Enums that use closures?

Solution

If we want to add a closure to an enum first of all lets define the type of the closure.

typealias Logic = () -> (String)

Then the enum:

enum Rule {
case SimpleRuleA(Logic)
case SimpleRuleB(Logic)
case BigFatComplicatedRule(Logic)
}

That's it! Let's see now how to use this.

Usage

Let's create a couple of Logic(s):

let logic0 : Logic = { return "Logic 0" }
let logic1 : Logic = { return "Logic 1" }

And now a function to process a Rule

func processRule(rule:Rule) -> String {
switch rule {
case .SimpleRuleA(let logic): return "Simple Rule A, logic: \(logic())"
case .SimpleRuleB(let logic): return "Simple Rule B, logic: \(logic())"
case .BigFatComplicatedRule(let logic): return "Big Fat Complicated Rule, logic: \(logic())"
}
}

Finally let's combine every possible rule with every possible Logic...

let aWithLogic0 = Rule.SimpleRuleA(logic0)
let aWithLogic1 = Rule.SimpleRuleA(logic1)
let bWithLogic0 = Rule.SimpleRuleB(logic0)
let bWithLogic1 = Rule.SimpleRuleB(logic1)
let fatWithLogic0 = Rule.BigFatComplicatedRule(logic0)
let fatWithLogic1 = Rule.BigFatComplicatedRule(logic1)

... and let's test it

processRule(aWithLogic0) // "Simple Rule A, logic: Logic 0"
processRule(aWithLogic1) // "Simple Rule A, logic: Logic 1"
processRule(bWithLogic0) // "Simple Rule B, logic: Logic 0"
processRule(bWithLogic1) // "Simple Rule B, logic: Logic 1"
processRule(fatWithLogic0) // "Big Fat Complicated Rule, logic: Logic 0"
processRule(fatWithLogic1) // "Big Fat Complicated Rule, logic: Logic 1"

Is this solution close to what you had in mind?

Can you use closures in an enum as params in Swift?

Swift enums cannot have closures as rawValues, so you may need to bind each case and its corresponding closure in other ways, for example:

enum CalcOptions {
case add
case subtract
case multiply
case divide
}
extension CalcOptions {
var operation: (Int, Int)->Int {
switch self {
case .add: return {$0 + $1}
case .subtract: return {$0 - $1}
case .multiply: return {$0 * $1}
case .divide: return {$0 / $1}
}
}
}

func calculator (n1: Int, n2: Int, option: CalcOptions) -> Int {
return option.operation(n1, n2)
}
print(calculator(n1: 2, n2: 3, option: .add)) //->5

Or, if you are using Swift 5+, you can make your enum callable:

@dynamicCallable
enum CalcOptions {
case add
case subtract
case multiply
case divide

func dynamicallyCall(withArguments args: [Int])->Int {
let n1 = args[0], n2 = args[1]
switch self {
case .add:
return n1 + n2
case .subtract:
return n1 - n2
case .multiply:
return n1 * n2
case .divide:
return n1 / n2
}
}
}

func calculator(n1: Int, n2: Int, operation: CalcOptions) -> Int {
return operation(n1, n2)
}
print(calculator(n1: 2, n2: 3, operation: .multiply)) //->6

Can a Swift enum have a function/closure as a raw value?

It's not as straight forward, but you could use OptionSet, see this page:

Unlike enumerations, option sets provide a nonfailable init(rawValue:) initializer to convert from a raw value, because option sets don’t have an enumerated list of all possible cases. Option set values have a one-to-one correspondence with their associated raw values.

Could be something like this:

func doSomething() {}
func doSomethingElse() {}

struct MyClosures: OptionSet {

static let closureOne = MyClosures(rawValue: doSomething)
static let closureTwo = MyClosures(rawValue: doSomethingElse)

let rawValue: () -> Void

init(rawValue: @escaping () -> Void) {
self.rawValue = rawValue
}

init() {
rawValue = {}
}

mutating func formUnion(_ other: __owned MyClosures) {
// whatever makes sense for your case
}

mutating func formIntersection(_ other: MyClosures) {
// whatever makes sense for your case
}

mutating func formSymmetricDifference(_ other: __owned MyClosures) {
// whatever makes sense for your case
}

static func == (lhs: MyClosures, rhs: MyClosures) -> Bool {
// whatever makes sense for your case
return false
}
}

And so you can use it as:

let myClosures: MyClosures = [ .closureOne, .closureTwo ]

HOWEVER looking at your explanation in the comment:

So I'm trying to find the most efficient way to run a function given the state of a variable.

I think what you actually want is some sort of state machine. Some examples are available here and here

enum and closure combination

The correct syntax for what you are trying to do is:

if case let Operation.BinaryOperation(operation) = plus {
operation(3.0, 2.0) // results in 5.0
}

You need to get the associated value of the enum.


Note that your closures can be simplified to

let plus = Operation.BinaryOperation(+)
let minus = Operation.BinaryOperation(-)
let multiply = Operation.BinaryOperation(*)
let divide = Operation.BinaryOperation(/)

Can I assign enums to closure parameters in Swift

Because Message.ratesAvailable has type (Data?) -> Message

which you can see for yourself:

import Foundation

enum Message {
case inputChanged(String?)
case ratesAvailable(data: Data?)
case reload
}

print(type(of: Message.ratesAvailable)) // => (Optional<Data>) -> Message

Understanding Enums with Functions as Associated Values in Swift

As @luk2302 notes, hunting for use cases of random types is, quite literally, "a solution in search of a problem." But perhaps it helps expand the mind about what is possible.

That said, embedding a function in an enum is just as useful as any other function parameter. For instance, you could use it to pass a recovery function in an error type:

enum Error: ErrorType {
case Temporary(message: String, recovery: () -> Void)
case Final(message: String)
}

func reconnect() {}

let err = Error.Temporary(
message: "Network down",
recovery: { reconnect() }
)

switch err {
case let .Temporary(message, recovery):
print(message)
recovery()
case let .Final(message):
print(message)
fatalError()
}

Or you could pass "how to convert to the next state" in any state machine (which is a very common use for enums).

enum State {
case Login(next: (password: String) -> State)
case Connecting(next: (Connection) -> State)
case Connected(next: (Connection) -> State)
case Disconnected(next: () -> State)
}

That would allow each state conversion to directly control the next state conversion, which in some cases is nicer than making all that logic centralized (especially if legal transitions is path-depenedent).

But again, as @luk2302 says, function values are just values. It would be a very unfortunate special case if enums could contain any value but functions.

Objective-C Closure to use Swift enum

To be compatible with objective-c enum must be inherited from Int, like

@objc public enum Status: Int {
case unknown
case ok
case failed
}

make sure generated bridge header file "YOURPROJECT-Swift.h" contains

typedef SWIFT_ENUM(NSInteger, Status, closed) {
StatusUnknown = 0,
StatusOk = 1,
StatusFailed = 2,
};

then in your .m file

#import "YOURPROJECT-Swift.h"

...

+ (void)fetch:(void (^_Nonnull)(Status success))completion
{
// do anything needed
}

Clean/Build - all compiled well. Tested with Xcode 11.2.

How do you check elegantly case enum in the .first(where:) closure in Swift?

first won't do anything useful. I think you want contains.

items.contains { Item.foo ~= $0 }

Regardless, you'll need this:

/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum: Equatable, AssociatedValue>(
case: (AssociatedValue) -> Enum,
instance: Enum
) -> Bool {
Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}

/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum, AssociatedValue>(
case: (AssociatedValue) -> Enum,
instance: Enum
) -> Bool {
Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}

/// Match non-`Equatable` `enum` cases without associated values.
public func ~= <Enum>(pattern: Enum, instance: Enum) -> Bool {
guard (
[pattern, instance].allSatisfy {
let mirror = Mirror(reflecting: $0)
return
mirror.displayStyle == .enum
&& mirror.children.isEmpty
}
) else { return false }

return .equate(pattern, to: instance) { "\($0)" }
}
public extension Mirror {
/// Get an `enum` case's `associatedValue`.
static func associatedValue<AssociatedValue>(
of subject: Any,
_: AssociatedValue.Type = AssociatedValue.self
) -> AssociatedValue? {
guard let childValue = Self(reflecting: subject).children.first?.value
else { return nil }

if let associatedValue = childValue as? AssociatedValue {
return associatedValue
}

let labeledAssociatedValue = Self(reflecting: childValue).children.first
return labeledAssociatedValue?.value as? AssociatedValue
}

/// Get an `enum` case's `associatedValue`.
/// - Parameter case: Looks like `Enum.case`.
static func associatedValue<Enum: Equatable, AssociatedValue>(
of instance: Enum,
ifCase case: (AssociatedValue) throws -> Enum
) rethrows -> AssociatedValue? {
try associatedValue(of: instance)
.filter { try `case`($0) == instance }
}

/// Get an `enum` case's `associatedValue`.
/// - Parameter case: Looks like `Enum.case`.
static func associatedValue<Enum, AssociatedValue>(
of instance: Enum,
ifCase case: (AssociatedValue) throws -> Enum
) rethrows -> AssociatedValue? {
try associatedValue(of: instance).filter {
.equate(try `case`($0), to: instance) {
Self(reflecting: $0).children.first?.label
}
}
}
}
public extension Optional {
/// Transform `.some` into `.none`, if a condition fails.
/// - Parameters:
/// - isSome: The condition that will result in `nil`, when evaluated to `false`.
func filter(_ isSome: (Wrapped) throws -> Bool) rethrows -> Self {
try flatMap { try isSome($0) ? $0 : nil }
}
}
public extension Equatable {
/// Equate two values using a closure.
static func equate<Wrapped, Equatable: Swift.Equatable>(
_ optional0: Wrapped?, to optional1: Wrapped?,
using transform: (Wrapped) throws -> Equatable
) rethrows -> Bool {
try optional0.map(transform) == optional1.map(transform)
}
}


Related Topics



Leave a reply



Submit