ST-0026: TaskLocal test trait

I'll reiterate again from a Concurrency library standpoint:

It definitely should not be =, there's a reason the APIs don’t use = because it's scoped, and I would not want to have the weird divide in api shapes.

The thing this api should "look and feel" similar to is withValue(_:operation:file:line:) | Apple Developer Documentation d which does not have labels:

$featureEnabled.withValue(true) { }

So really the closest equivalent would be

@Test(
  .taskLocal($featureEnabled, true),
)

// or 


@Test(
  .taskLocal($featureEnabled, withValue: true),
)

I would really object against inventing some new "creative" ways to label these things;

No "being" or "bound to", no patterns with \. since it is then "different" and we should not be making this different just for fun. It should be recognizably the same concept and thing as the existing API this is invoking -- therefore, no labels is fine.

4 Likes

Argument labels are one of Swift’s superpowers, and the feature I miss in every other programming language. But they should be used wisely. Their purpose is to communicate information that is not already clear at the point of use, not to maximize explicitness or make every call read like English prose.

On code being read more often than it is written: I see this used as an argument for additional explicitness, but the reverse should be considered equally: any extra spelling must also be read repeatedly. A spelling that resolves uncertainty reduces reading cost, but a spelling that merely restates what the reader knows increases it. Verbosity itself can lead to a loss of clarity, which Ben Cohen laid out well in one of my favorite posts on the forum.

And the API Design Guidelines themselves are nuanced: "Clarity at the point of use is your most important goal", "Include all the words needed to avoid ambiguity", and "Omit needless words".

I don’t think "label all other arguments" should decide this question in isolation, as though the guidelines were a mechanical decision tree. They contain several principles that must be weighed at the use site. This contextual judgement matters especially in declarative lists of test traits, where repeated labels accumulate and compete with the information that varies.

There is also a broader guideline I find relevant: "Embrace precedent. Don’t optimize terms for the total beginner at the expense of conformance to existing culture."

As has been mentioned, there is a significant corpus of relevant precedent in APIs such as .environment(_:_:) and .focusedValue(_:_:) from SwiftUI and syntax.with(\.property, value) from SwiftSyntax. SwiftUI is not open source, but one of the most widely used Swift frameworks and precedent concerns the public API and the culture experienced by its users. Test traits are in a similarly declarative context, so I think that precedent is meaningful here.

Further, once one understands what a task local is (as one must to use this trait) I don’t see how .taskLocal($theme, .minimal) is ambiguous. withValue: may provide a small amount of guidance on first encounter, but that initial learning cost is better addressed by learning the general TaskLocal API than by permanently adding ceremony to every use of this Testing-specific convenience.

Lastly, while forum reactions are neither votes nor a substitute for argument, I think the asymmetric response to Brandon’s side-by-side examples is at least some evidence of how the two spellings land with readers.

4 Likes

I want to emphasize that SwiftSyntax is explicitly and intentionally not idiomatic Swift, so I wouldn't want to draw inspiration from it here.

That doesn't negate your broader point, but I'd prefer not to try to justify one name or another based on what SwiftSyntax does. :slightly_smiling_face:

2 Likes

This proposal has been accepted with revisions!

1 Like