Enums and Dynamic Member Lookup

It seems to me like you could define a method or subscript that takes a value of your enum type:

class FontBook {
  subscript(fontName: FontName) -> UIFont { ... }
}

let font = fontBook[.heading1]

Even with more checking, dynamic member lookup still obscures the underlying meaning of the code, since it's not really accessing a member, but passing a value derived from the member name to a special member, and the implicit members would not necessarily be discoverable via code completion, automatically-generated documentation or other standard tooling.

4 Likes

It seems both too magical and too limited to tie this to enums. I think this sort of compile-time validation should be based around the (previously pitched) compile time evaluation feature that will hopefully be added in future. This would allow much greater flexibility around how you validated the lookup, e.g. the SIMD “.xxyy” syntax that @dan-zheng mentioned would be better expressed by checking that the string only contains x, y, z, and w characters, instead of making an enum with all 256 possible combinations.

To hone on the vector swizzle use case: @dynamicMemberLookup plus compile-time evaluation is an interesting solution that avoids code bloat. Code completion isn't supported though, as stated by @Joe_Groff. I wonder if there may be other efficiencies or implementation challenges.

I feel the obvious approach is to use gyb to exhaustively define all .xxyy members. The only downside (not that it's an insignificant one) is massive code explosion: for a VectorN type of length N, there are N^N swizzles. I wonder what's the exact code size impact of such aggressive gybbing, and whether it's acceptable.

I don't think you would want them to appear in code completion anyway though, because it would both look ridiculous and not be very helpful. In my mind, there would perhaps be some more obvious swizzle method, a subscript taking an n-tuple of indices or whatever makes sense, and the shorthand member variables would just be an optional convenience where discoverability isn't that important.

Ah, good point. I agree that a single "swizzle method" endpoint is ideal for code completion.

Even with more checking, dynamic member lookup still obscures the underlying meaning of the code, since it's not really accessing a member, but passing a value derived from the member name to a special member, and the implicit members would not necessarily be discoverable via code completion, automatically-generated documentation or other standard tooling.

Yes we can use a standard subscript, and the syntactic sugar deliberately obscures the underlying meaning of the code, but that's the point. At the call site the user shouldn't need to know how it's implemented. Your point applies just as well to the original dynamic member lookup functionality.

It seems both too magical and too limited to tie this to enums. I think this sort of compile-time validation should be based around the (previously pitched) compile time evaluation feature that will hopefully be added in future. This would allow much greater flexibility around how you validated the lookup, e.g. the SIMD “.xxyy” syntax that @dan-zheng mentioned would be better expressed by checking that the string only contains x, y, z, and w characters, instead of making an enum with all 256 possible combinations.

I think that is a separate issue. In the the code I've given as an example the enum exists already. It's used elsewhere as a normal enum. What I'm suggested would just neaten the two the call site looks and abstract away from how the font is retrieved (ie maybe in a later version of the framework, dynamic member lookup isn't used and instead individual methods are generated or written).

This would just be another tool in the swift language that people could use if they wanted to help reduce the need for things like GYB.

// currently crashes at runtime (in FontName.init(stringLiteral:)
let font2 = fontBook.fooBar

I think if it crashes at runtime when an invalid name is specified then this is pretty much the same as just using the index as a string. But it's interesting that this is almost possible. it implies that this feature should be implemented as it 90% there with a bug.

I feel @jawbroken is trying to make the point (correct me if I'm wrong) that @compilerEvaluable plus current @dynamicMemberLookup functionality is a sufficient means for achieving the same end as enum-typed subscript(dynamicMember:) indices.

But the @compilerEvaluable solution is preferable because it involves less surface area: it doesn't involve extending @dynamicMemberLookup functionality, it only involves combining the existing @dynamicMemberLookup functionality with an orthogonal compile-time evaluation language feature.

OTOH, much of @compilerEvaluable isn't implemented yet (in particular, more work is needed to support compile-time string operations, needed here). :smiley:


@Joe_Groff's solution is also concise, type-safe, and everything we want (e.g. subscript(fontName: FontName) -> UIFont is a single endpoint for code completion).

2 Likes

Yeah, that's basically what I meant. In a future utopia where compile-time evaluation is pervasive, you could validate the call against an existing enum by simply trying to initialise it from the string raw value and checking if the failable initialiser returned nil or not. This gives you something like @Dante-Broggi's solution but with the force unwrap at runtime in the FontName initialiser being replaced by compile-time evaluation.

2 Likes

Quite the opposite, actually. The justification of the original dynamic member lookup functionality was precisely that it's needed to support the accessing of members; the argument was that member lookup should look like member lookup even in the interop setting, and that writing it as a subscript, for example, obscures the meaning of the code.

In your example, there's no dynamism required (in fact, the use case is precisely to constrain the options to a particular list known at compile time), and it's not a true member lookup. I would agree with @Joe_Groff that @dynamicMemberLookup isn't the right feature for this functionality.

1 Like

Also note that, in evaluating @dynamicMemberLookup, we extensively evaluated alternative solutions to dynamic language interop with the potential for better compiler integration, and only proceeded when we established that they wouldn't give as good results for the primary use case of importing libraries written in Python or other dynamic languages.

Sorry to deviate from the original topic, but what are your thoughts on vector swizzles?

There's no dynamism there either (all members are statically known), but the number of members is much larger, and there may be real negative consequences of defining all N^N members (e.g. code size explosion).

I wonder if that too might be best written as a subscript.

2 Likes

Aha. We can use the same approach from this thread and use a Swizzle enum whose cases are all the swizzles.

I strongly suspect an explosive number of enum cases is less detrimental than an explosion of swizzle computed properties (with getters and setters).

2 Likes

Why not a subscript that takes multiple arguments?

vector[.x, .x, .y, .y]
3 Likes

Nice. We're getting into minutia now, but I feel a small SwizzleComponent enum with cases x, y, z, etc. is nicer than a large Swizzle enum whose cases are the combinations of all orderings (e.g. .xxyy).

SwizzleComponent has a much smaller surface area and is more composable. I think it's harder to pattern match on "concatenated" .xxyy cases.

A related idea is to use OptionSet? That would enable grouping of related swizzles. Need to think more about it.


I think we should continue further discussion of swizzles on the SIMD proposal thread. Folks there may have more ideas.

There is probably a better solution for swizzles, yeah, especially if you don't need extreme brevity. Compile-time validation of dynamic member lookup and method calls would still be useful for the intended purpose of interoperating with dynamic languages, though. For example, you could use it enforce basic syntactic rules around allowed member/method names or the relative ordering of labelled and unlabelled parameters, etc.

Definitely. As others noted, it seems likely that compile-time evaluation of assertions could grow to address that niche.

Compile time evaluation of dynamic member lookup plus ˋ#errorˋ sounds amazing, can‘t wait. :slight_smile: