As Operator Overload

Fair enough, people would still be able to keep using initializer, computed properties and functions to convert between types, but having a language construct to do it has its advantages. For starters the standard is implicit. As far as I know, there's no official documentation stating that an initializer with no label is the standard. One would have to learn this with experience like I did. OK, we can just add that to the documentation, but I still think reusing the as operator is clearer than an initializer since an initializer can mean other things as I mentioned. OK, the as operator, under the hood, also has different mechanisms, but the end result would be the same for every case, toll-free-bridging, subtype relationships, and type conversion. You apply the operator with the type you want when you get a value with that type.

What do you mean exactly?

So your argument is that the current initializer syntax is not good because it can have meanings other than conversion, but overloading the as operator with additional meanings is better?

The API Design Guidelines are probably the closest to this. But regardless, the language is still settling and the Official Style Guide is just getting going.

I'm saying the meaning is the same. You ask for the as operator to give you another value with the other type you specified based on the current value. The "how" might be different, but the "what" that the operator is doing is the same for every case.

e.g. someone could implement func as(data: Data) -> File that saves the passed data to a file.

This is a circular argument. If you change the language so that as can be sugar for any function (T) -> U, then as can work that way. The question is, why do you think that is better than an initializer, and how would users get back the current functionality? What you propose is certainly not the same as the current meaning of as.

Is it really that strange to read:

someValue as SomeType

as:

"Hey compiler give me this value but with this other type"

?

What's your main issue with this proposal? The reuse of the as operator or a language construct to state the mapping operation?

My issue is exactly as stated:

That's what's happening currently. that is not what would happen if you allow general usage of as to convert between Types.

With Bridging, you have two way, Value-level equality. AFAIK there's no way to generically guarantee this will be the case for a user's implementation.

Well, I already explained it. It's best to have a language construct that serves only this purpose. The mapping between types. As I see it, the as operator is the one that does it. I don't think you see it that way, and I think that's why you're having issues with using the as operator, correct me if I'm wrong. That's why I asked if you had issues with reusing the as operator. Now, let's say we use another operator like @GetSwifty mentioned. Let's call it map. Your use of (T) -> U just made me realize that it is simply a map operator. For the sake of the argument, forget about potential clashing issues with the existing map functions. What do you think about:

let string: String = "Hello world!" 
let nsstring = string map NSString

Or we could even use mapto and not worry that much about clashes:

let string: String = "Hello world!" 
let nsstring = string mapto NSString

Or simply to like @GetSwifty (great username, by the way :joy:) proposed:

let string: String = "Hello world!" 
let nsstring = string to NSString

What do you think? You still prefer initializers?

Didn't understand this part.

So you mean when you use the as operator there's a hidden assumption that the memory layout would be the same? Is this true for subtyping relationships? Is this assumption in widespread use by the community, assuming it is correct?

Thanks! :slight_smile:

As far as the bridging with Objective-C, I don't know if the memory is identical bit-by-bit, but my understanding is a lot of engineering went into the bridging being as inexpensive as possible.

For down-casting, I believe the only actual difference is your compile-time interface and it has no effect at all on the value. E.g in a UITableViewController subclass:

let testSelf = self as UITableViewController

Results in testSelf having the same memory address as self. It's possible the cast isn't even visible once you get into assembly, but that's not something I know much about.

Sorry, but I don't get the argument that introducing a very limited new operator (that would serve a single purpose that can easily be done today) add clarity and simplify the language.

First, user would have to learn it, its usage, and inevitable limitations. Then we will perpetually have to justify its existence and why it should be use instead of simply using a function or an initialiser to perform the very same task.

Really it's a matter of preference.

IMO

let aString = string to NSString

reads as normal language. result = Noun Verb Object. It's easy to instantly understand what's happening. Wheras

let aString = NSString(string)

feels a lot more..."syntaxy" or mathy. it's just result = Object(Noun)

Should this be added to Foundation right now?...maybe (probably) not. It's certainly not a major pain-point and there are more important things to deal with. However users can't really add it themselves so it's the only way.

Maybe eventually we'll have somethign like Kotlin's infix operator and we can start messing with things like this :slight_smile:

Hi Paulo,

It is unlikely the core team will consider a proposal to allow customization of the behavior of as on different types.

The best way to think of the as keyword is as a way to provide "type context". That is, the following things should be seen as equivalent:

let x = foo as Bar
let y: Bar = foo

The benefit of as being it's a lot more versatile than assigning to a variable i.e. you can do this:

// f can take any type
func f<T>(_ t: T) { ... }
// fix T to be of type Bar, not type Foo
f(foo as Bar)

...without having to create a temporary. But it's best not to think of the as is an operator doing the conversion. Rather the conversion happens because the compiler is willing to convert certain types to other types implicitly, and the as is a way of spelling out the types in question. But typed variable declarations or function arguments are another way.

So, rather than propose something focusing on as, the proposal should examine the potential for user-implemented implicit conversions between types (which could be triggered by type context, including via as).

A very broad "convert anything implicitly to anything else" feature is also likely to receive a lot of push back, as it is (IMO at least) somewhat out of keeping with Swift's philosophy of strong typing. The implicit conversions we do have – to existentials, wrapping in optionals, or to/from ObjC bridged types – cause a lot of confusion for users, challenging the considerable benefits these conveniences bring. Adding to this cause of confusion would be hard to justify.

The general trend has been to go in the other direction. For example, SE-83: Remove bridging conversion behavior from dynamic casts was deferred, but is a direction the core team endorsed at the time in principle, even if it might now be impractical for source-stability reasons.

Pitches for narrower proposals than arbitrary conversion might get more traction. The idea of user-defined subtypes is one that's often brought up. But that is also a deceptively large design space (should variance of function argument/return types be allowed for example, or implicit conversion of collections of the subtype) so would be a large undertaking.

Ben

15 Likes

From the Swift API Design Guidelines:

In initializers that perform value preserving type conversions, omit the first argument label , e.g. Int64(someUInt32)

(Source: https://swift.org/documentation/api-design-guidelines/#argument-labels)

1 Like

Thank you, Ben!

I don't think this is always true. Specifically, you can use as to "cast" a concrete type to an existential:

let number = 1
let existential = number as Encodable
MemoryLayout.size(ofValue: existential) // 40

This isn't "toll-free" because number and existential have different sizes. And it also works for converting an array to an array of existentials:

let numbers = Array(1...10_000)
let encodables = numbers as [Encodable]

MemoryLayout.size(ofValue: numbers[0]) // 8
MemoryLayout.size(ofValue: encodables[0]) // 40

The compiler effectively has to turn this into a map operation, making it O(n).

Terms of Service

Privacy Policy

Cookie Policy