Implicit Casts for Verified Type Information

I think this is a distinction without a difference. If you have:

print(obj.count)

inside the scope, you have something of the form (expression).count. Without your proposed change, the expression is of type Any. With your change, the expression (aka obj as! String) is of type String. For all intents and purposes, the type of the expression at the use site has changed, though of course the type of the parameter obj hasn't changed formally at the outer level.

I'm sympathetic to your goal, but I feel there's a better solution lurking just offstage here. We haven't found it yet.

1 Like

I'm using Kotlin everyday and still find this feature more a burden that something helpful.

It forces writing inconsistent code.
When it doesn't work (often), it forces introduction of a local variable, and when is works, introducing an intermediate local variable is pointless and confusing for the code reader.

And I'm not taking about contract which make guessing when it will work even more complex, as it make some functions magically work (functions with contract annotations).

If I where asking for a change, that would be on the Kotlin side, not on the Swift side. Kotlin lacks a clean canonical way to "unbox" optional or perform "check cast". If it had one, I would advise to always use it for the sack of code consistency and forget the implicit behaviour.

2 Likes

@QuinceyMorris This has been asserted a couple times in the comments above, but it's not accurate. The fact the variable's type has not changed impacts, for example, method resolution:

func talk(_ obj: Any) {
  if  obj is String {
    printMe(obj)
  }

  printMe(obj)
}

func printMe(_ obj: Any) {
  print("\(obj) is Any")
}

func printMe(_ obj: String) {
  print("\(obj) is String")
}

talk("Hello")

// Output:
Hello is String
Hello is Any

I appreciate you sharing your experience using it with Kotlin. That is completely inconsistent with my common case, but I obviously can't argue with what you are experiencing.

I use it all the time when working with parameters inside of switch and when, and have never been confused by it. You can certainly create convoluted use cases, but as I mentioned above, the same is true for many Swift features include fundamental things like type inferencing with array and dictionary literals.

Why is the output different if the type didn't change? In today's Swift the type doesn't change, and it prints "Hello is Any" twice. What would you expect if the type DID change?

1 Like

Implicit casting.

printMe(obj as! String)

would do the same thing. The Kotlin feature allows eliding as! String when a test for type has been done immediately prior.

1 Like

Because it is cast to a String the first time due to it being inside the if obj is String statement's block. Try applying an as! String to an instance of Any when you call printMe. Function resolution will pick printMe(_ obj: String) if you call it again without the cast it will select printMe(_ obj: Any). That is all that is happening.

I see @Avi beat me to the answer, but I'm leaving my response as well in case the additional explanation is useful. I've clearly created some confusion about how this works. Apologies if I didn't explain it well.

Applying an as! String is by definition changing the type.

What is it changing the type of? The expression known as obj.

1 Like

I believe that's an incorrect perspective. That cast will fail at runtime if the type isn't already String. It is not correct to say the type is being changed.

Are we talking about static type (the one by which compiler type-checks your code) or the dynamic type (the one returned by the type(of:) method) ? I thought that we're talking about static type in this thread. as! changes the static type.

1 Like

It does not change the type of obj. obj keeps its type. It forces the type of the expression (obj as! String)

printMe(obj as! String)
printMe(obj)

The second call will print Any as the type of obj never changed.

Not according to this (assuming it's inside if obj is String { ... } ) :

How is the type changing? That is standard Swift overload resolution based on the dynamic type. Is that what you mean by the type is changed?

Standard Swift overload resolution is based on static type, not dynamic (unless we're using protocol requirements, closure properties, or class inheritance)

It will print

Hello is Any

Hello is Any

even though the dynamic type is String

It will use String's map method. The method resolution is clear, and already defined. Within your if block it will use the method defined in the implicit cast (in your case, as! String). Note that you could have also tested for nil to product the same effect.

? This isn't in anyway specific to Any. Also, people who write interpreters, code analyzers, compilers, frameworks and many other things frequently have to deal with Any.

Your code is incorrect.

func talk(_ obj: Any) {
  if  obj is String {
    printMe(obj as! String) // <-- make 'as! String' implicit
  }

  printMe(obj)
}
Hello is String
Hello is Any

The pitch is suggesting that we borrow a feature from Kotlin that makes the marked code above unnecessary.

1 Like

Exactly.

Yes, but the feature he's pitching would convert that code to be equivalent to this. Which does print:

Hello is String
Hello is Any
2 Likes

I'm not sure what people are trying to argue.

  • That as! doesn't change the type?
  • That it's not the type of expression known as obj is being changed

That it only changes the type of the expression in the if statement, not the type of the variable.

Note that I'm not actually endorsing this, just trying to make sure that everyone is on the same page.