swifttuplesswift5

How to do a Tuple extension in current Swift? It seems to be available experimentally


Some code,

typealias Brio = (String, String, String)

extension Brio {
    static var divider: Brio {
        return ("_", "_", "_")
    }
    static var isDivider: Bool {
        return (self.0 == "_")
    }
}

Tuple extension must be written as extension of '(repeat each Element)',

Tuple extension must declare conformance to exactly one protocol

Tuple extensions are experimental

enter image description here

I really couldn't figure it out from say this chat,

https://forums.swift.org/t/pitch-user-defined-tuple-conformances/67154


I don't understand the following sort of stuff but if relevant:

fattie@m2max203mbp16 ~ % swift --version
swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0
fattie@m2max203mbp16 ~ % 

Coin use case:

Here's an example of where extending tuples would be sweet

// Setup the screen selector button
screensButton.showsMenuAsPrimaryAction = true
let noms = [
    Brio("CA", "Cats", "cat.fill"),
    Brio("DO", "Dogs", "fido.circle"),
    Brio("DO", "Horsies", "equine.circle"),
    .divider,
    Brio("DEV", "Dev reload", "link"),
    Brio("DX", "Dev, clear", "circle"),
    .divider,
    Brio("XX", "Example", "circle.dotted"),
    Brio("XX", "Example", "link"),
]
var elements = noms.chompchomp(){ k in self.screensButtonProcess(k) }
screensButton.menu = UIMenu(children: elements)

enter image description here

Having to use a class is annoying. It would be more purer with tuples, eg,

let noms = [
    ("CA", "Cats", "cat.fill"),
    ("DO", "Dogs", "fido.circle"),
    ("DO", "Horsies", "equine.circle"),
    .divider,
    ("DEV", "Dev reload", "link"),

Footnote, here's the kodes for chompchomp, done with tears as a class Brio rather than tuples:

///A "BUTTON TRIO". Internal code, title, system image.
///
///Use `[Brio]` to make menus. Just put `.divider` where you want dividers.
///
///OR just use `buildBrios { }` if using the matching @resultBuilder
struct Brio {
    let c: String
    let t: String
    let i: String
    var isDivider: Bool = false
}

extension Brio {
    init(_ x: String, _ y: String, _ z: String) {
        self.init(c: x, t: y, i: z)
    }
    ///When you type-out a `Brio` array just put `.divider` where you want dividers.
    static var divider: Brio {
        return Brio(c: "_", t: "_", i: "_", isDivider: true)
    }
}

extension [Brio] {
    ///Chew on an array of `Brio` and spit out the UIMenuElement array. Recall that to create dividers in UIKit menus, basically each run (other than the first) is marked as inline.
    func chompchomp(with: @escaping (_ tapped: String)-> ()) -> [UIMenuElement] {
        var brios = self
        var result = brios.chomp().elements{ b in with(b) }
        var run = brios.chomp().elements(with: { b in with(b) })
        while !run.isEmpty {
            result.append(UIMenu(options: .displayInline, children: run))
            run = brios.chomp().elements(with: { b in with(b) })
        }
        return result
    }
    ///Simply give the next run of menu items removing that run.
    mutating func chomp() -> [Brio] {
        let spl = self.split(maxSplits: 1, whereSeparator: { $0.isDivider })
        self = (spl.count < 2) ? [] : Array(spl[1])
        return (spl.count < 1) ? [] : Array(spl[0])
    }
    ///Alchemize one run of Brio into an array of UIMenuElement.
    func elements(with: @escaping (_ tapped: String)-> ()) -> [UIMenuElement] {
        return self.map({ brio in
            return UIAction(title: brio.t, image: UIImage(systemName: brio.i)) { _ in
                with(brio.c)
            }
        })
    }
}

* it's hilarious that cat.fill actually exists!


Solution

  • Extensions on tuples must conform tuples of any number of elements to a protocol. Your use case only makes sense to use 3-tuples, so a tuple extension isn't very suitable.

    You should keep your Brio struct, and create a result builder for Brios,

    @resultBuilder
    enum BrioBuilder {
        static func buildExpression(_ expression: (String, String, String)) -> Brio {
            let (c, t, i) = expression
            return Brio(c, t, i)
        }
        
        static func buildExpression(_ expression: Divider) -> Brio {
            Brio.divider
        }
        
        static func buildBlock(_ components: Brio...) -> [Brio] {
            components
        }
    }
    
    func buildBrios(@BrioBuilder block: () -> [Brio]) -> [Brio] {
        block()
    }
    

    Then you can achieve a usage like this:

    // noms will be of type [Brio]
    let noms = buildBrios {
        ("CA", "Cats", "cat.fill")
        ("DO", "Dogs", "fido.circle")
        ("DO", "Horsies", "equine.circle")
        Divider()
        ("DEV", "Dev reload", "link")
    }
    

    You cannot use the .divider syntax in a result builder (See here). You can write Brio.divider, or use a separate struct (I have borrowed the Divider from SwiftUI here).

    You can extend this further by implementing buildArray, buildIf, etc in the result builder. This would allow you to use loops and if statements in the result builder closure. For more information, see the Swift Evolution proposal.