Skip to content

Instantly share code, notes, and snippets.

@chockenberry
Created July 16, 2024 23:20
Show Gist options
  • Save chockenberry/d6c08e9916442d11a0c69fb01454c063 to your computer and use it in GitHub Desktop.
Save chockenberry/d6c08e9916442d11a0c69fb01454c063 to your computer and use it in GitHub Desktop.
Ugly Swift
import UIKit
enum Name {
case foo(Foo)
case bar(Bar)
struct Foo {
let name: String
}
struct Bar {
let count: Int
}
}
let names = [Name.foo(Name.Foo(name: "hello")), Name.bar(Name.Bar(count: 123)), Name.bar(Name.Bar(count: 999))]
// The goal: to get the first Name.Bar item from names, and more importantly, to make the code readable.
func firstBar_TakeOne(in names: [Name]) -> Name.Bar? {
if case let .bar(bar) = names.first(where: { name in
if case .bar(_) = name {
return true
}
return false
}) {
return bar
}
return nil
}
func firstBar_TakeTwo(in names: [Name]) -> Name.Bar? {
let initialResult: Name.Bar? = nil
let result = names.reduce(into: initialResult) { result, name in
guard result == nil else { return }
if case let .bar(bar) = name {
result = bar
}
}
return result
}
if let bar = firstBar_TakeOne(in: names) {
bar.count
}
if let bar = firstBar_TakeTwo(in: names) {
bar.count
}
@ZPedro
Copy link

ZPedro commented Jul 17, 2024

I would personally go with:

if let bar = names.extractFirst({ // from answer above
    guard case let .bar(barVal) = $0 else { // fold all other cases into:
        return nil;
    }
    return barVal;
}) {
    bar.count;
}

because I assume struct Name.Bar's role is nailed down enough; if it isn't, what Jared Sinclair said.

@d-ronnqvist
Copy link

Building on the for-loop solution from @ezfe: you can make the variable binding in the loop condition:

func firstBar(in names: [Name]) -> Name.Bar? {
    for case .bar(let bar) in names {
        return bar
    }
    return nil
}

This will iterate over names until it finds the first .bar(_) value and return its associated value (bound to bar in the loop condition). If names doesn't contain any .bar(_) values it will return nil.

@chriskrycho
Copy link

I would tend to reach for something like this:

let firstBar = names
    .lazy
    .compactMap({
        switch $0 {
        case let .bar(bar): bar
        default: nil
        }
    })
    .first;

The combination of .lazy and .first should mean you only iterate as far through the collection as you need to, and the result is an Optional<Name.Bar>.

@ezfe
Copy link

ezfe commented Jul 17, 2024

If you use the asBar or barValue helper values others have contributed above, you could then write it as:

return names.lazy.compactMap(\.asBar).first

and still get nearly optimal performance. A regular for loop will probably be marginally faster for small collections though but not by much.

@pyrtsa
Copy link

pyrtsa commented Jul 17, 2024

Watch out, the performance of that construct may surprise you: https://forums.swift.org/t/adding-firstas-to-sequence/36665/17

@chriskrycho
Copy link

Ahhhh, overloads fun. 🙃 The first(where: { _ in true }) hack is terrible, too 😂 but if abstracted over and provided with a very good explanatory comment, I guess I could live with it. 🙃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment