WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: Day 9 Closures: code treated like data. But how far can you take it?

Forums > 100 Days of SwiftUI

I've been familiar with callback functions for many years. But Swift's headache inducing syntax was difficult. It took a couple of passes, but I think I'll be able to read and use in-line closures in the future.

Going beyond simple closures, is there a way to coerce Swift into combining closures and enums, collections, etc?

Consider:

// Greyscale methods
func averaging()    { <code for rgb manipulation }
func lunimance()    { <different method for rgb manipulation> }
func desaturation() { <code> }
...etc...

var greyscaleMethod = luminance  // Allow the user to choose a method

image.makeGreyscale( greyscaleMethod )

It would be interesting to be able to treat a collection of similar closures/functions as we would an array of strings. This sample code seems to work.

let sortDecending = { (n1:String, n2:String) in n1 > n2 }
let sortAscending = { (n1:String, n2:String) in n1 < n2 }
let sortOrders = [sortAscending, sortDecending]
let sortSequences = [
    { (n1:String, n2:String) in n1 > n2 },
    { (n1:String, n2:String) in n1 < n2 },
]

let names = ["john", "paul", "george", "ringo"]
names
    .sorted(by: sortAscending)
    .forEach {print($0)}

names
    .sorted(by: sortDecending)
    .forEach {print($0)}

names
    .sorted(by: sortSequences[0])
    .forEach {print($0)}

But I could not get closure expressions to work in enums.

1      

This is an enum for the Rock, Paper, Scissor challenge. I added a function in this enum that requires you to provide a closure. The closure must accept a string, and return a string.

As you can see, the enum provides the string input. But the programmer decides how the output string is formatted. Copy / Paste into Playgrounds.

// Combining enums and closures
// for: @w8HAQRHkTx7r
// by:  @Obelix
// Copy-paste into Playground
// Rock, Paper, Scissors enumeration  Tip o' the hat to @roosterboy
enum Choice: Int {
    case rock     // auto assigned value 0
    case paper    // auto assigned value 1
    case scissors // auto assigned value 2

    // Let the computer make a wise choice.
    func random() -> Choice {
        Choice(rawValue: Int.random(in: 0...2))!
    }

    var token: String {  // computed variable
        if self == .rock {
            return "Rock" }
        else if self == .scissors {
            return "Scissors"
        } else {
            return "Paper"
        }
    }

    // needs a function that takes a string
    // and returns a string
    func formatChoice( _ formatWith: (String) -> String ) -> String {
        // user provided function
        // the enum is providing the string input
        // the user decides how it's formatted.
        formatWith( self.token )
    }
}

let myChoice       = Choice.rock
let computerChoice = myChoice.random() // pick a random

// Provide a closure to the enum's formatChoice function.
let myFormattedChoice = myChoice.formatChoice {
    return "I chose \($0)."  // takes a string, returns a string
}

// Provide a DIFFERENT closure to the enum's formatChoice function.
let computersFormattedChoice = computerChoice.formatChoice {
    "The computer chose \($0)." // user decides how it's formatted
}

print( myFormattedChoice )         // prints "I chose Rock."
print( computersFormattedChoice )  // prints "The computer chose Scissors."

Can you help me? What is @w8HAQRHkTx7r ? is this 'leet speak' ??

1      

If given a choice not to use my email address as a username, I pick a random string. Hence the name.

Thanks for you response. I'm still congitating on it.

What I was thinking, was something like this:

struct Person {
  var name: String
  var id: Int
}

enum Sorting {
  case nameAtoZ = { $0.name < $1.name }
  case nameZtoA = { $0.name > $1.name }
  case idLowToHigh = { $0.id < $1.id }
  case idHighToLow = { $0.id > $1.id }
  }

  var ersatz[Person] = [...]

  ersatz.sorted(Sorting.nameAtoZ)

Something like this can be done with arrays of closures, but I thought it would be neat to have Swift offer autocompletion with the names of the closures.

1      

There are a number of reasons why that won't work.

But you can accomplish something similar like this:

enum Sorting {
    static func nameAtoZ(p1: Person, p2: Person) -> Bool { p1.name < p2.name }
    static func nameZtoA(p1: Person, p2: Person) -> Bool { p1.name > p2.name }
    static func idLowToHigh(p1: Person, p2: Person) -> Bool { p1.id < p2.id }
    static func idHighToLow(p1: Person, p2: Person) -> Bool { p1.id > p2.id }
}

var ersatz: [Person] = [...]

ersatz.sorted(by: Sorting.nameAtoZ)

Using a caseless enum here means that a Sorting value can't be created. It just acts as a kind of namespace to scope the functions.

Of course, you lose some of the benefits of using an enum, probably the number one loss being you can't iterate through the static functions like you can cases (if your enum conforms to CaseIterable). Like with most things, there are tradeoffs.

2      

Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Fernando's book will guide you in fixing bugs in three real, open-source, downloadable apps from the App Store. Learn applied programming fundamentals by refactoring real code from published apps. Hacking with Swift readers get a $10 discount!

Read the book

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.