DRY in array initialization

In the Chart section of the SwiftUI manual, we see a data array made from literals as so:

var stackedBarData: [ToyShape] = [
    .init(color: "Green", type: "Cube", count: 2),
    .init(color: "Green", type: "Sphere", count: 0),
    .init(color: "Green", type: "Pyramid", count: 1),
    .init(color: "Purple", type: "Cube", count: 1),
    .init(color: "Purple", type: "Sphere", count: 1),
    .init(color: "Purple", type: "Pyramid", count: 1),
    .init(color: "Pink", type: "Cube", count: 1),
    .init(color: "Pink", type: "Sphere", count: 2),
    .init(color: "Pink", type: "Pyramid", count: 0),
    .init(color: "Yellow", type: "Cube", count: 1),
    .init(color: "Yellow", type: "Sphere", count: 1),
    .init(color: "Yellow", type: "Pyramid", count: 2)
]

That's a whole of of repetition. Now, when I make structs I often make an initializer which leaves out the labels, so we could get to here:

var stackedBarData: [ToyShape] = [
// ToyShape(color: String, type: String, count: Int)
   .init("Green", "Cube", 2),
   .init("Green", "Sphere",  0),
   .init("Green", "Pyramid", 1),
...
]

My idea is that in the narrow context of an array literal, it'd be nice if we could go all the way to:

var stackedBarData: [ToyShape] = [
// ToyShape(color: String, type: String, count: Int)
    ("Green", "Cube", 2)
    ("Green", "Sphere",  0)
    ("Green", "Pyramid", 1)
...
]

Has anyone played with that idea? I wouldn't be surprised, but I don't recall seeing it. I feel like it fits the Swift idiom of being able to optionally leave out assumable things.

Clarity over brevity: the information is redundant on every line, but it’s important once.

It would be nice™ to have a guaranteed optimization for

var stackedBarData: [ToyShape] = [
    ("Green", "Cube", 2),
    ("Green", "Sphere",  0),
    ("Green", "Pyramid", 1)
].map { .init(color: $0.0, type: $0.1, count: $0.2) }

but I suspect that’s pretty involved.

5 Likes

Yeah, I definitely like the idea of having to mention those parameters labels at least once, but I'm not sure of the most satisfying way to do it without straining the idiom.

I toyed with:

var stackedBarData: [ToyShape] = [
    init (color: String, type: String, count: Int) {
    ("Green", "Cube", 2)
   ...
    }
]

but rejected it.

I think IDE support might be the best solution here, much as that pains my purist soul.

You could do it with a result builder, I suppose:

var stackedBarData: [ToyShape] = array {
  .init(color: $0.0, type: $0.1, count: $0.2)
} from: { // @ArrayBuilder or whatever
  ("Green", "Cube", 2)
  ("Green", "Sphere",  0)
  ("Green", "Pyramid", 1)
}

but that would be building the result element by element, and wouldn’t have the total count on hand.

…And of course the new expressions macros could handle it.

The DRY form is

var stackedBarData = [
  ("Green", [2, 0, 1]),
  ("Purple", [1, 1, 1]),
  ("Pink", [1, 2, 0]),
  ("Yellow", [1, 1, 2])
]
  .flatMap { color, counts in
    zip(["Cube", "Sphere", "Pyramid"], counts).map {
      ToyShape(color: color, type: $0, count: $1)
    }
  }

but Xcode can't indent it properly.