Tuple "Upcasting" in Swift

Tuple upcasting in Swift

Tuples cannot be cast, even if the types they contain can. For example:

let nums = (1, 5, 9)
let doubleNums = nums as (Double, Double, Double) //fails

But:

let nums : (Double, Double, Double) = (1, 5, 9) //succeeds

The workaround in your case is to cast the individual element, not the Tuple itself:

let tuple = ("String", true)
let anyTuple = (tuple.0, tuple.1 as Any)
// anyTuple is (String, Any)

This is one of the reasons the Swift documentation notes:

Tuples are useful for temporary groups of related values. They are not suited to the creation of complex data structures. If your data structure is likely to persist beyond a temporary scope, model it as a class or structure, rather than as a tuple.

I think this is an implementation limitation because Tuples are compound types like functions. Similarly, you cannot create extensions of Tuples (e.g. extension (String, Bool) { … }).


If you're actually working with an API that returns (String, Any), try to change it to use a class or struct. But if you're powerless to improve the API, you can switch on the second element's type:

let tuple : (String, Any) = ("string", true)

switch tuple.1 {

case let x as Bool:
print("It's a Bool")
let boolTuple = (tuple.0, tuple.1 as! Bool)

case let x as Double:
print("It's a Double")
let doubleTuple = (tuple.0, tuple.1 as! Double)

case let x as NSDateFormatter:
print("It's an NSDateFormatter")
let dateFormatterTuple = (tuple.0, tuple.1 as! NSDateFormatter)

default:
print("Unsupported type")
}

If the API returns Any and the tuple isn't guaranteed to be (String, Any), you're out of luck.

typecasting tuples in swift

Instead of separately trying to match each component with if-let combinations, you can just do the same in a more swift-y way:

switch (a, b) {
case (let a as String, _):
// Do whatever you need to do with a
return true
case (_, let b as String):
// Do whatever you need to do with b
return true
case (let a as NSNumber, let b as NSNumber):
// Do whatever you need to do with a and b
return false
default:
return true
}

Note also that the type of downcast you are trying to do is currently impossible - look at this swift bug: tuple 'as?' downcast broken

Cannot express tuple conversion '([Subclass], Int, String)' to '([Superclass], Int, String)'

The problem has indeed to do with the fact that although Swift can implicitly convert [WeaponItems] into [Items], it fails to do so when these types come as the components of tuples. E.g. see this: Tuple "upcasting" in Swift

There are several ways to "solve" this problem.

Easiest is:

func iterateWPItems() -> ([Items], Int, String) {
let (composition, cost, message) = iterateItems(iterationItems: WeaponItems.weaponItems, removeItem: removeWeaponItem, addItem: addWeaponItem, calculateEfficiency: calcWeaponDemage)
return (composition, cost, message)
}

Alternatively, you can change iterateItems to return just the tuple you expect, that is ([Items], Int, String):

func iterateItems<T: Items>(iterationItems: [T], removeItem: (T) -> Void, addItem: (T) -> Void, calculateEfficiency: () -> Void) -> ([Items], Int, String) {
...
return (bestComposition as! [Items], bestBuildCost, customMessage)
}

Better still, from the way it looks I do not see why iterateItems has to be a generic method. If that's indeed the case, then simply changing it to:

func iterateItems(iterationItems: [Items], removeItem: (Items) -> Void, addItem: (Items) -> Void, calculateEfficiency: () -> Void) -> ([Items], Int, String) {
...
return (bestComposition as! [Items], bestBuildCost, customMessage)
}

.. should also help.

Cannot pattern match a tuple of subtypes

(Update: In Swift 3.1, available with Xcode 8.3 beta, your code now behaves as expected.)

This is a bug, and is tracked by SR-1423. However, according to that report, the issue has now been fixed on the master branch of the Swift repository, so all should be well when Swift 3.1 comes around (expected release date is "spring of 2017").

However, until then, a simple solution is just to check the type of each element in the tuple individually:

switch (l, r) {
case (is A, is A):
print("(l, r) is (A, A)")
default:
print("failed to match tuple")
}

Or if you need to use l and r in the case:

switch (l, r) {
case let (l as A, r as A):
print("(\(l), \(r)) is (A, A)")
default:
print("failed to match tuple")
}

Tuple member extraction in closure arguments

Well, you can use short closure syntax:

tupleArray.sort { $0.1 < $1.1 }

See the official guide about short closure syntax, the .1 is just tuple index access.

Seq.cast tuple values from obj to string

You could write it slightly nicer as:

seq { yield (box "key", box "val") }
|> Seq.map (fun (k, v) -> string k, string v)

Imagine, however, that you have a Tuple2 module:

module Tuple2 =
// ... other functions ...

let mapBoth f g (x, y) = f x, g y

// ... other functions ...

With such a mapBoth function, you could write your cast as:

seq { yield (box "key", box "val") } |> Seq.map (Tuple2.mapBoth string string)

There's no Tuple2 module in FSharp.Core, but I often define one in my projects, containing various handy one-liners like the one above.

Swift Pattern match on Array Any

Unfortunately casting between generic types like Array is not fully supported (yet). There are also odd situations even if you want to upcast:

let emptyStringArray : [String] = []
emptyStringArray as [Any] // succeeds

let stringArray : [String] = ["Bob", "Roger"]
stringArray as [Any] // error! due to the elements?!

let intArray = [1, 2, 3]
intArray as [Any] // error

let customStructArray : [myStruct] = []
customStructArray as [Any] // '[myStruct]' is not convertible to '[Any]'

There is also no good workaround without using a protocol. If you really want to have this dynamic behavior you could use reflections with the reflect() function. In Swift 2 they are more powerful, but it is still not a good solution.

Edit:

A solution with a protocol which gets adopted by all Arrays through an extension (only for your specific case):

protocol ArrayType {
var anyValues: [Any] { get }
}

extension Array: ArrayType {
var anyValues: [Any] {
return self.map { $0 as Any }
}
}

// now the switch gets rewritten as
switch any {
case let array as ArrayType:
let anyArray = array.anyValues
return "Array"
case let array as NSArray:
return "NSArray"
default:
return "Default"
}

Why can I successfully cast a Swift array struct to AnyObject?

You can cast Array<_> to AnyObject on Apple platforms because of Objective-C bridging.

The Swift compiler and runtime on Apple platforms can convert any Swift type to some sort of reference object. In general, a value type is converted to (wrapped in) an instance of __SwiftValue which is a subclass of NSObject. However, the Swift standard library uses some undocumented language features to customize certain conversions:

  • An Array<_> casts to an NSArray.
  • A Dictionary<_, _> casts to an NSDictionary.
  • A String casts to an NSString.
  • A number or boolean casts to an NSNumber.

Why are doubles printed differently in dictionaries?

As already mentioned in the comments, a Double cannot store
the value 1.1 exactly. Swift uses (like many other languages)
binary floating point numbers according to the IEEE 754
standard.

The closest number to 1.1 that can be represented as a Double is

1.100000000000000088817841970012523233890533447265625

and the closest number to 2.3 that can be represented as a Double is

2.29999999999999982236431605997495353221893310546875

Printing that number means that it is converted to a string with
a decimal representation again, and that is done with different
precision, depending on how you print the number.

From the source code at HashedCollections.swift.gyb one can see that the description method of
Dictionary uses debugPrint() for both keys and values,
and debugPrint(x) prints the value of x.debugDescription
(if x conforms to CustomDebugStringConvertible).

On the other hand, print(x) calls x.description if x conforms
to CustomStringConvertible.

So what you see is the different output of description
and debugDescription of Double:

print(1.1.description) // 1.1
print(1.1.debugDescription) // 1.1000000000000001

From the Swift source code one can see
that both use the swift_floatingPointToString()
function in Stubs.cpp, with the Debug parameter set to false and true, respectively.
This parameter controls the precision of the number to string conversion:

int Precision = std::numeric_limits<T>::digits10;
if (Debug) {
Precision = std::numeric_limits<T>::max_digits10;
}

For the meaning of those constants, see std::numeric_limits:

  • digits10 – number of decimal digits that can be represented without change,
  • max_digits10 – number of decimal digits necessary to differentiate all values of this type.

So description creates a string with less decimal digits. That
string can be converted to a Double and back to a string giving
the same result.
debugDescription creates a string with more decimal digits, so that
any two different floating point values will produce a different output.



Related Topics



Leave a reply



Submit