In Swift, functions are first class citizens
The above does not come as a surprise. We’re using closures as callback handlers, and store them – frequently as optionals – save them, pass them around.
I’ve used them as a variable in an enum: depending on the value of the enum, I return a different closure.
What I haven’t done is use a closure as an associated value in an enum. I don’t think this is a particularly great idea, but once I had it, I wanted to try it:
1a) Create a new Mac application. In it, create a lightweight data structure:
import Cocoa
struct Lightweight {
let firstLine: String = "A line was written."
let secondLine: String = "Nobody cared."
}
1b) Create the enum:
import Foundation
enum BadIdea {
case plainText(text: String)
case doubleText(firstLine: String, secondLine: String)
case dontTryThisAtHome(closure: ()->())
}
2) The only other class is the ViewController. Everything else shall take place there.
2a) Start with a closure. Here, I give mine a name, because I dislike writing closures inline, so I got it out of the way early:
let courtesyClosure = { ()->() in
print("This is a random closure.")
}
2b) I want two variables (and because this is a proof of concept, and I’m trying to get into this habit, one of them is actually a let constant. So there.)
let myModel = Lightweight()
var proofOfConcept: BadIdea = .plainText(text: "Placeholder")
I’m using a placeholder to avoid writing an initialiser or handling an optional BadIdea? – there is no other reason.
2c) The interesting bit happens here:
func handleWeirdThing() {
switch proofOfConcept {
case .plainText(text: let simpleText):
print(simpleText)
case .doubleText(firstLine: let firstLine, secondLine: let secondLine):
print("\(firstLine)\n\(secondLine)")
case .dontTryThisAtHome(closure: let closure):
closure()
}
}
and now we only need to try all of these cases in viewDidLoad – first I’m setting the variable, then I make the magic happen.
override func viewDidLoad() {
super.viewDidLoad()
//proofOfConcept = .plainText(text: myModel.firstLine)
//proofOfConcept = .doubleText(firstLine: myModel.firstLine, secondLine: myModel.secondLine)
// proofOfConcept = .dontTryThisAtHome(closure: courtesyClosure)
handleWeirdThing()
}
2d) Build and run, commenting in the lines as you want.
3) And here you have the really bug-inducing part of this game, of course: make courtesyClosure a property of _the model object_, and you have .dontTryThisAtHome(closure: myModel.courtesyClosure) – this could have captured anything and then been stored in the model, and suddenly you have state that is all over the place… did I say this was a BadIdea?
I don’t think that hardcoding the function you want to execute in response to a certain action _in the model (or an associated enum)_ is evil. Unnecessary, maybe, but not evil.
I’ve actually got a potential use case for this: I have a protocol in my app, and the individual classes could be very different, and rather than having a viewcontroller handling every single possible case, I considered encoding the models’ preferred behaviour _in the model itself_, and from there it was only a short step from ‘but what if text is not enough and I want to, say, show a custom alert?’, which in turn led to this post.
This is a pattern that could be abused badly, and I’d strongly recommend against capturing values from third parties.
3a) Add the following to the Lightweight struct
let alertClosure = { ()->() in
let alert = NSAlert()
alert.messageText = "Proof of Concept"
alert.informativeText = "If you want to capture one of the properties, you need to have a proper initializer, which I am too lazy to write."
alert.addButton(withTitle: "OK")
alert.runModal()
}
3b) In viewDidLoad, set the Viewcontroller’s proofOfConcept variable to the following:
proofOfConcept = .dontTryThisAtHome(closure: thingy.alertClosure)
3c) NSAlert is a topic for another post, so this is just a quick-and-dirty version. Also note that if you run this code in viewDidLoad, you will never see the application’s window, which is … not ideal, but this is a proof of concept only; this is simply to show the possibilities you have.
Build and run. Instead of printing text to your console, you now get an alert popping up. This is advanced code branching, though not necessarily good application architecture.
I probably will never use this; I can see better architectural solutions for every potential use case I have considered (going through a large stack of app ideas and wondering whether this would solve any of my problems), but I thought it was an interesting thing that needed to be tried.
Nov 7 2017
Proof of Concept: Enum with associated function
In Swift, functions are first class citizens
The above does not come as a surprise. We’re using closures as callback handlers, and store them – frequently as optionals – save them, pass them around.
I’ve used them as a variable in an enum: depending on the value of the enum, I return a different closure.
What I haven’t done is use a closure as an associated value in an enum. I don’t think this is a particularly great idea, but once I had it, I wanted to try it:
1a) Create a new Mac application. In it, create a lightweight data structure:
import Cocoa
struct Lightweight {
let firstLine: String = "A line was written."
let secondLine: String = "Nobody cared."
}
1b) Create the enum:
import Foundation
enum BadIdea {
case plainText(text: String)
case doubleText(firstLine: String, secondLine: String)
case dontTryThisAtHome(closure: ()->())
}
2) The only other class is the ViewController. Everything else shall take place there.
2a) Start with a closure. Here, I give mine a name, because I dislike writing closures inline, so I got it out of the way early:
let courtesyClosure = { ()->() in
print("This is a random closure.")
}
2b) I want two variables (and because this is a proof of concept, and I’m trying to get into this habit, one of them is actually a let constant. So there.)
let myModel = Lightweight()
var proofOfConcept: BadIdea = .plainText(text: "Placeholder")
I’m using a placeholder to avoid writing an initialiser or handling an optional BadIdea? – there is no other reason.
2c) The interesting bit happens here:
func handleWeirdThing() {
switch proofOfConcept {
case .plainText(text: let simpleText):
print(simpleText)
case .doubleText(firstLine: let firstLine, secondLine: let secondLine):
print("\(firstLine)\n\(secondLine)")
case .dontTryThisAtHome(closure: let closure):
closure()
}
}
and now we only need to try all of these cases in viewDidLoad – first I’m setting the variable, then I make the magic happen.
override func viewDidLoad() {
super.viewDidLoad()
//proofOfConcept = .plainText(text: myModel.firstLine)
//proofOfConcept = .doubleText(firstLine: myModel.firstLine, secondLine: myModel.secondLine)
// proofOfConcept = .dontTryThisAtHome(closure: courtesyClosure)
handleWeirdThing()
}
2d) Build and run, commenting in the lines as you want.
3) And here you have the really bug-inducing part of this game, of course: make courtesyClosure a property of _the model object_, and you have .dontTryThisAtHome(closure: myModel.courtesyClosure) – this could have captured anything and then been stored in the model, and suddenly you have state that is all over the place… did I say this was a BadIdea?
I don’t think that hardcoding the function you want to execute in response to a certain action _in the model (or an associated enum)_ is evil. Unnecessary, maybe, but not evil.
I’ve actually got a potential use case for this: I have a protocol in my app, and the individual classes could be very different, and rather than having a viewcontroller handling every single possible case, I considered encoding the models’ preferred behaviour _in the model itself_, and from there it was only a short step from ‘but what if text is not enough and I want to, say, show a custom alert?’, which in turn led to this post.
This is a pattern that could be abused badly, and I’d strongly recommend against capturing values from third parties.
3a) Add the following to the Lightweight struct
let alertClosure = { ()->() in
let alert = NSAlert()
alert.messageText = "Proof of Concept"
alert.informativeText = "If you want to capture one of the properties, you need to have a proper initializer, which I am too lazy to write."
alert.addButton(withTitle: "OK")
alert.runModal()
}
3b) In viewDidLoad, set the Viewcontroller’s proofOfConcept variable to the following:
proofOfConcept = .dontTryThisAtHome(closure: thingy.alertClosure)
3c) NSAlert is a topic for another post, so this is just a quick-and-dirty version. Also note that if you run this code in viewDidLoad, you will never see the application’s window, which is … not ideal, but this is a proof of concept only; this is simply to show the possibilities you have.
Build and run. Instead of printing text to your console, you now get an alert popping up. This is advanced code branching, though not necessarily good application architecture.
I probably will never use this; I can see better architectural solutions for every potential use case I have considered (going through a large stack of app ideas and wondering whether this would solve any of my problems), but I thought it was an interesting thing that needed to be tried.
By Extelligent Cocoa • Application Design • • Tags: closures, enums, NSAlert