Compound variable names

Sure, I’m not trying to propose actionable concrete alternatives here—just expressing a preference for us to use the key path syntax under discussion for compound-name members and bound methods if it turns out to be in conflict with the unbound/uncurried method usage.

And I’m expressing a preference for having a clear path forward for access to unbound, uncurried methods regardless of what happens with this proposal. I think the proposal will be stronger if it addresses this point.

1 Like

I'm a little confused as to what you're looking for in this "clear path forward." As far as I'm aware, the design space for the syntax these unbound, uncurried method references is basically wide open—the key-path-based '\.f(x:)' syntax is one suggested option, but there's no reason that it couldn't be, say, #unboundUncurried(S.f(x:)), or an unbounded number of alternatives.

It seems out-of-scope for this proposal to explore the design space of a largely unrelated feature that simply happens to share a tentative syntax for one aspect. Especially so when, IMO, the only reasonable way to form a key path to a compound name is as \.f(x:). I don't see the alternatives of "you can't form a key path to a member with a compound name" and "key paths which refer to a compound name use a different syntax than 'normal' members" as tenable options.

I'd be happy to add a paragraph to the KeyPath and compound names section that mentions this potential overlap/conflict, but if you could provide some more concrete suggestions about how you'd like to see this consideration addressed in the proposal I'm all ears!

1 Like

A more broad thought that I'm interested in the community's feedback on: is there any reason why changing a declaration from

func foo(x: Int) {}

to

let foo(x:): (Int) -> Void = {}

should not be an API-compatible change? I can't think of any off the top of my head. Should such a change be ABI-compatible?

"may" or "must"?

This seems to conflict with the later statement "Compound names are not permitted to appear in any position which would result in them being the external label to a function parameter."

I don't believe it is either of these in general, nor can be. The simplest demonstration I can think of is:

struct S {
    let foo: (Int) -> Void
}

let offset = MemoryLayout<S>.offset(of: \S.foo)  // Okay

versus

struct S {
    func foo(_ : Int) {}
}

let offset = MemoryLayout<S>.offset(of: \S.foo)  // Nonsense

I think the same would be true of a class type.

I think "may" is appropriate, since you could also call the variable by referencing the name and immediately applying it, as you can do with methods/functions today:

f(x1:...xn:)(arg1, ..., argn)

Good catch! I had thought that the parameter production was defined as

parameter → external-parameter-name local-parameter-name_opt type-annotation

but turns out it's

parameter → external-parameter-name_opt local-parameter-name type-annotation

so we can exclude the compound name from the external name from the external name in the grammar. Nice!

Even simpler, this proposal promises key paths to members with compound names which don't exist at all for methods (yet...). I think we could (?) promise that the method -> compound member transformation is API-compatible, though I don't know if its worth it to make that a guarantee (even if it ends up being the case in practice).

I wonder—does any promise like that exist today for turning func foo() {} into var foo: () -> Void?

I would like to have an understanding of whether this feature would in fact conflict with use of this syntax for unbound, uncurried methods or not. Hopefully someone from the core team can chime in and the answer can be included in the proposal text for reviewers to consider when evaluating this proposal.

2 Likes

FWIW, regardless of this proposal we'd have to have some way to deal with the fact that \Foo.bar may refer to the bar member of an instance with type Foo, or the unbound, uncurried method bar of type Foo—however that gets resolved ShouldTM be able to extend to compound names as well.

I'm quite saddened nothing came about from this proposal, especially now that closure properties are more relevant than ever in things like TCA clients.

Currently one must do this:

struct FileClient {
	var read: ReadEffect
}

struct ReadEffect {
	typealias ReadAction = (URL, Data.ReadingOptions) throws -> Data

	private let read: ReadAction

	init(_ read: @escaping ReadAction) {
		self.read = read
	}

	func callAsFunction(from url: URL, options: Data.ReadingOptions) throws -> Data {
		try read(url, options)
	}
}

// ...
let data = fileClient.read(url: url, options: [.uncached])

instead of just this:

struct FileClient {
	var read(url:options:): (URL, Data.ReadingOptions) throws -> Data
}

// ...
let data = fileClient.read(url: url, options: [.uncached])
8 Likes

I agree, the lack of labels in closures is a big whole in Swift syntax.

That said, I'm not a fan of the solution here either, feels quite weird and hard to explain why the name of the var now has a syntax like a func "selector". I would love to just see the labels in the closure arguments as a normal function. But I guess that ship has long sailed.

2 Likes

Exactly what I thought: agree in principle, but would be much more logical to have labels on closure arguments:

var op: (lhs: Int, rhs: Int) -> Int
x = op(lhs: 1, rhs: 2)

func foo(opToUse op: (lhs: Int, rhs: Int) -> Int) {
  x = op(lhs: 1, rhs: 2)
}
foo(opToUse: +)

That'd effectively be syntactic sugar as explained in the proposal.

In my original example, it'd look like this:

struct FileClient {
	var read: (url: URL, options: Data.ReadingOptions) throws -> Data
    
    // synthesized as:
    var read(url:options:): (URL, Data.ReadingOptions) throws -> Data
}

// ...
let data = fileClient.read(url: url, options: [.uncached])

I see, but is it possible to promote this syntax sugar to be "the thing", and get rid of somewhat awkward looking main form of "var read(url:options:)" ?

Yes, perhaps most people would prefer using a nicer syntax sugar but still there'll be sources using the awkward form, which is not ideal to have in swift, all IMHO.

You do have a point.

I wonder if there's any scenario where the syntactic sugar being the only form would work against us.

If it turns out to be solid, I'd be happy to have it as the only form.

I don't see it as "awkward". A fully qualified function name has always included labels like that. This is valid Swift code today, for example:

func read(url: URL, options: Data.ReadingOptions) throws -> Data {/* … */}
var g: (URL, Data.ReadingOptions) throws -> Data = read(url:options:)
g(someURL, someOptions)

The 2nd line demonstrates that "g", and "read(url:options:)" share the same role in the syntax. It'd be natural for them to swap position.

5 Likes

Can we proceed with the proposal? It looks good especially with the syntactic sugar
Allow declaration of argument labels inline with the function type

1 Like

Does the Swift community / devs have any resources to push this proposal again? It would make many many APIs so much nicer to use.

8 Likes

I would love to see this effort revived as well. This, as well as @Douglas_Gregor's function body macros, would enable some fascinating mocking scenarios.

2 Likes