Swift Disadvantages Array of Any - swift

If I store a Tuple
var person = ("Steve", 22)
I cannot add more data easily into the structure.
However, if I use an array of Any
let steve: [Any] = ["Steve", 22]
I can easily add the elements.
Surely there are no real advantages to using a Tuple and we just always use an array of Any?

Any is weakly typed so you lose all of the static type checking guarantees that you get with strong types, including tuples. In addition, you cannot index out of bounds at run time on a tuple like you can with an array since the compiler knows how many components there are and it will fail to compile, and since a tuple is basically an anonymous struct, you can also name the components to make them more meaningful, which you cannot do with an array. You also pay a performance penalty for the array of Any since your data has to be boxed in the Any where as a tuple is just a struct and its components are not boxed.

Related

Tuple vs. Object in Swift

I have read about the differences in Tuples and Dictionaries / Arrays, but I have yet to come across a post on Stack Overflow explaining the difference between a Tuple and an Object in Swift.
The reason I ask is that from experience, it seems that a Tuple could be interchangeable with an Object in Swift in many circumstances (especially in those where the object only holds other objects or data), but could lead to inconsistent / messy code.
In Swift, is there a time to use a Tuple and a time to use a basic Object based on performance or coding methodologies?
As vadian notes, Apple's advice is that tuples only be used for temporary values. this plays out. If you need to do almost anything non-trivial with a data structure, including store it in a property, you probably do not want a tuple. They're very limited.
I'd avoid the term "object" in this discussion. That's a vague, descriptive term that doesn't cleanly map to any particular data structure. The correct way to think of a tuple is as being in contrast to a struct. In principle, a tuple is just an anonymous struct, but in Swift a tuple is dramatically less flexible than a struct. Most significantly, you cannot add extensions to a tuple, and adding extensions is a core part of Swift programming.
Basically, about the time you're thinking that you need to label the fields of the tuple, you probably should be using a struct instead. Types as simple as "a point" are modeled as structs, not tuples.
So when would you ever use a tuple? Consider the follow (non-existent) method on Collection:
extension Collection {
func headTail() -> (Element?, SubSequence) {
return (first, dropFirst())
}
}
This is a good use of a tuple. It would be unhelpful to invent a special struct just to return this value, and callers will almost always want to destructure this anyway like:
let (head, tail) = list.headTail()
This is one thing that tuples can do that structs cannot (at least today; there is ongoing discussion of adding struct destructuring and pattern matching to Swift).
In Swift, Tuple is a Compound Type that holds some properties together which are built up from Objects of Swift Named Types for example class, struct and enum.
I would analogize Objects of these Named Types as minerals of chemical elements ( like carbon, calcium) and Tuple is just a kind of physical mixture of these minerals( eg a pack of 1 part of calcium ore and 3 parts of carbon ore). You can easily carry around this packed tuple and add it to “heat or press” method to return “limestone” your app use in construction.

What is the advantage of using tuple over dictionary as a return type?

Beside tuple is shorter and easy to write what are the other advantages of using a tuple over a dictionary as a function's return data type.
let httpResponse0 = (404, "not found")
let httpResponse1 = ["code": 404, "status": "not found"]
Collecting some and adding a couple more:
A tuple is type-safe way of containing multiple value with multiple types
Compared to a dictionary, it's much lighter weight, particularly since the keys are entirely compile time.
Keys are optional
Keys, if used are compiler maintained and unlikely to get spelled wrong.
Disadvantages:
It's easy to get lazy and not name elements which can lead to confusion and unreadable code.
It's not Obj-c compatible
Personally I'd prefer to use a light weight object or struct instead of either in most cases. It's more explicit while still light weight.

Why isn't a Swift tuple considered a collection type?

In Swift, why isn't a tuple considered a collection type?
Of course this is fussbudget territory, but I find a certain amount of fussing helps me understand, retain, and organize what I'm learning.
I don't have inside knowledge of the motivations, but I can hopefully present some deeper understanding.
Type Safety
One of the primary goals of Swift is to enforce programming best practices to minimize human mistakes. One of these best practices is to ensure we always use specific types when we know them. In Swift, variables and constants always have explicit types and collections prefer to hold, say Strings, than AnyObjects if you know that it will be storing Strings.
Notably, every collection type in Swift may only hold one type of element. This includes Array, Dictionary, and Set.
Tuples are Compound Types
In Swift, there are two types of types: Named Types and Compound Types. Most types, including collection types, are named. Compound types, on the other hand, contain multiple named types in unique combinations. There are only two Compound Types: Function Types and Tuple Types. Tuple Types are different from collections in that:
Multiple Named Types can be bundled together in a Tuple Type without using AnyObject.
Each position can be given a textual label.
We can always anticipate how many values a tuple will be holding because we declared each position.
For more information, see the Types chapter of The Swift Programming Language.
Tuples Represent Objects
We typically don't think of a collection type as an object in itself. Instead, we think of it as a collection of objects. A tuple's labeled positions and multi-type functionality enable it to function more like an object than any collection. (Almost like a primitive Struct)
For example, you might consider an HTTP error to be a tuple with an error code (Int) and a description (String).
Tuples are often used as primitive approaches to temporary objects, such as returning multiple values from a function. They can be quickly dissected without indexing.
Additionally, each Tuple type has its own explicit type. For example, (Int, Int) is an entirely different type from (Int, Int, Int), which is an entirely different type from (Double, Double, Double).
For more on this, see "Tuples" in the The Basics chapter of The Swift Programming Language.
Tuples are Used in Fundamental Language Syntax
When we think of collection types, we think again of collections. Tuples, however, are used in fundamental places in the language that make Compound Type a more fitting title for them. For example, the Function Type is a Compound Tye that is used anytime you specify a function or closure. It is simply a tuple of parameters and a tuple of return values. Even Void is just a typealias for (), an empty tuple.
Additionally, tuples are in language syntax to temporarily bind values. For example, in a for-in loop you might use a tuple to iterate over a dictionary:
let dict = [1: "A", 2: "B", 3: "C"]
for (_, letter) in dict {
doSomething()
}
Here we use a tuple to iterate over only the values of the dictionary and ignore the keys.
We can do the same in Switch statements (excerpt from The Swift Programming Language):
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("(0, 0) is at the origin")
case (_, 0):
print("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
print("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
print("(\(somePoint.0), \(somePoint.1)) is inside the box")
default:
print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
}
// prints "(1, 1) is inside the box”
Because tuples are used in such fundamental places in language syntax, it wouldn't feel right for them to be bundled together with collection types.
SequenceType & CollectionType
One last thing to note is that a fundamental feature of CollectionType, which inherits much of this from SequenceType, is the ability to provide a safe Generator for iteration. For example, a for-in loop is possible because collections define a way to get the next item. Because tuples do not consist of the same types, and have a guaranteed number of items (after declaration), using them on the right of a for-in loop (instead of a traditional collection) would be less than intuitive.
If using it in a for-in loop, something designed to thrive with collections, seems less-than-intuitive for a tuple, than a tuple may deserve a different category.
For details on CollectionType, check out SwiftDoc.org. Note that a Generator providing iteration is required. Iterating a tuple would not be type safe and impose many unnecessary complexities, but making a tuple a collection is an interesting concept. It just might be too fundamental for that.

How safe are swift collections when used with invalidated iterators / indices?

I'm not seeing a lot of info in the swift stdlib reference. For example, Dictionary says certain methods (like remove) will invalidate indices, but that's it.
For a language to call itself "safe", it needs a solution to the classic C++ footguns:
get pointer to element in a vector, then add more elements (pointer is now invalidated), now use pointer, crash
start iterating through a collection. while iterating, remove some elements (either before or after the current iterator position). continue iterating, crash.
(edit: in c++, you're lucky to crash - worse case is memory corruption)
I believe 1 is solved by swift because if a collection stores classes, taking a reference (e.g. strong pointer) to an element will increase the refcount. However, I don't know the answer for 2.
It would be super useful if there was a comparison of footguns in c++ that are/are not solved by swift.
EDIT, due to Robs answer:
It does appear that there's some undocumented snapshot-like behavior going on
with Dictionary and/or for loops. The iteration creates a snapshot / hidden
copy of it when it starts.
Which gives me both a big "WAT" and "cool, that's sort of safe, I guess", and "how expensive is this copy?".
I don't see this documented either in Generator or in for-loop.
The below code prints two logical snapshots of the dictionary. The first
snapshot is userInfo as it was at the start of the iteration loop, and does
not reflect any modifications made during the loop.
var userInfo: [String: String] = [
"first_name" : "Andrei",
"last_name" : "Puni",
"job_title" : "Mad scientist"
]
userInfo["added_one"] = "1" // can modify because it's var
print("first snapshot:")
var hijacked = false
for (key, value) in userInfo {
if !hijacked {
userInfo["added_two"] = "2" // doesn't error
userInfo.removeValueForKey("first_name") // doesn't error
hijacked = true
}
print("- \(key): \(value)")
}
userInfo["added_three"] = "3" // modify again
print("final snapshot:")
for (key, value) in userInfo {
print("- \(key): \(value)")
}
As you say, #1 is not an issue. You do not have a pointer to the object in Swift. You either have its value or a reference to it. If you have its value, then it's a copy. If you have a reference, then it's protected. So there's no issue here.
But let's consider the second and experiment, be surprised, and then stop being surprised.
var xs = [1,2,3,4]
for x in xs { // (1)
if x == 2 {
xs.removeAll() // (2)
}
print(x) // Prints "1\n2\n3\n\4\n"
}
xs // [] (3)
Wait, how does it print all the values when we blow away the values at (2). We are very surprised now.
But we shouldn't be. Swift arrays are values. The xs at (1) is a value. Nothing can ever change it. It's not "a pointer to memory that includes an array structure that contains 4 elements." It's the value [1,2,3,4]. At (2), we don't "remove all elements from the thing xs pointed to." We take the thing xs is, create an array that results if you remove all the elements (that would be [] in all cases), and then assign that new array to xs. Nothing bad happens.
So what does the documentation mean by "invalidates all indices?" It means exactly that. If we generated indices, they're no good anymore. Let's see:
var xs = [1,2,3,4]
for i in xs.indices {
if i == 2 {
xs.removeAll()
}
print(xs[i]) // Prints "1\n2\n" and then CRASH!!!
}
Once xs.removeAll() is called, there's no promise that the old result of xs.indices means anything anymore. You are not permitted to use those indices safely against the collection they came from.
"Invalidates indices" in Swift is not the same as C++'s "invalidates iterators." I'd call that pretty safe, except the fact that using collection indices is always a bit dangerous and so you should avoid indexing collections when you can help it; iterate them instead. Even if you need the indexes for some reason, use enumerate to get them without creating any of the danger of indexing.
(Side note, dict["key"] is not indexing into dict. Dictionaries are a little confusing because their key is not their index. Accessing dictionaries by their DictionaryIndex index is just as dangerous as accessing arrays by their Int index.)
Note also that the above doesn't apply to NSArray. If you modify NSArray while iterating it, you'll get a "mutated collection while iterating" error. I'm only discussing Swift data types.
EDIT: for-in is very explicit in how it works:
The generate() method is called on the collection expression to obtain a value of a generator type—that is, a type that conforms to the GeneratorType protocol. The program begins executing a loop by calling the next() method on the stream. If the value returned is not None, it is assigned to the item pattern, the program executes the statements, and then continues execution at the beginning of the loop. Otherwise, the program does not perform assignment or execute the statements, and it is finished executing the for-in statement.
The returned Generator is a struct and contains a collection value. You would not expect any changes to some other value to modify its behavior. Remember: [1,2,3] is no different than 4. They're both values. When you assign them, they make copies. So when you create a Generator over a collection value, you're going to snapshot that value, just like if I created a Generator over the number 4. (This raises an interesting problem, because Generators aren't really values, and so really shouldn't be structs. They should be classes. Swift stdlib has been fixing that. See the new AnyGenerator for instance. But they still contain an array value, and you would never expect changes to some other array value to impact them.)
See also "Structures and Enumerations Are Value Types" which goes into more detail on the importance of value types in Swift. Arrays are just structs.
Yes, that means there's logically copying. Swift has many optimizations to minimize actual copying when it's not needed. In your case, when you mutate the dictionary while it's being iterated, that will force a copy to happen. Mutation is cheap if you're the only consumer of a particular value's backing storage. But it's O(n) if you're not. (This is determined by the Swift builtin isUniquelyReferenced().) Long story short: Swift Collections are Copy-on-Write, and simply passing an array does not cause real memory to be allocated or copied.
You don't get COW for free. Your own structs are not COW. It's something that Swift does in stdlib. (See Mike Ash's great discussion of how you would recreate it.) Passing your own custom structs causes real copies to happen. That said, the majority of the memory in most structs is stored in collections, and those collections are COW, so the cost of copying structs is usually pretty small.
The book doesn't spend a lot of time drilling into value types in Swift (it explains it all; it just doesn't keep saying "hey, and this is what that implies"). On the other hand, it was the constant topic at WWDC. You may be interested particularly in Building Better Apps with Value Types in Swift which is all about this topic. I believe Swift in Practice also discussed it.
EDIT2:
#KarlP raises an interesting point in the comments below, and it's worth addressing. None of the value-safety promises we're discussing are related to for-in. They're based on Array. for-in makes no promises at all about what would happen if you mutated a collection while it is being iterated. That wouldn't even be meaningful. for-in doesn't "iterate over collections," it calls next() on Generators. So if your Generator becomes undefined if the collection is changed, then for-in will blow up because the Generator blew up.
That means that the following might be unsafe, depending on how strictly you read the spec:
func nukeFromOrbit<C: RangeReplaceableCollectionType>(var xs: C) {
var hijack = true
for x in xs {
if hijack {
xs.removeAll()
hijack = false
}
print(x)
}
}
And the compiler won't help you here. It'll work fine for all of the Swift collections. But if calling next() after mutation for your collection is undefined behavior, then this is undefined behavior.
My opinion is that it would be poor Swift to make a collection that allows its Generator to become undefined in this case. You could even argue that you've broken the Generator spec if you do (it offers no UB "out" unless the generator has been copied or has returned nil). So you could argue that the above code is totally within spec and your generator is broken. Those arguments tend to be a bit messy with a "spec" like Swift's which doesn't dive into all the corner cases.
Does this mean you can write unsafe code in Swift without getting a clear warning? Absolutely. But in the many cases that commonly cause real-world bugs, Swift's built-in behavior does the right thing. And in that, it is safer than some other options.

How to cast [Int8] to [UInt8] in Swift

I have a buffer that contains just characters
let buffer: [Int8] = ....
Then I need to pass this to a function process that takes [UInt8] as an argument.
func process(buffer: [UInt8]) {
// some code
}
What would be the best way to pass the [Int8] buffer to cast to [Int8]? I know following code would work, but in this case the buffer contains just bunch of characters, and it is unnecessary to use functions like map.
process(buffer.map{ x in UInt8(x) }) // OK
process([UInt8](buffer)) // error
process(buffer as! [UInt8]) // error
I am using Xcode7 b3 Swift2.
I broadly agree with the other answers that you should just stick with map, however, if your array were truly huge, and it really was painful to create a whole second buffer just for converting to the same bit pattern, you could do it like this:
// first, change your process logic to be generic on any kind of container
func process<C: CollectionType where C.Generator.Element == UInt8>(chars: C) {
// just to prove it's working...
print(String(chars.map { UnicodeScalar($0) }))
}
// sample input
let a: [Int8] = [104, 101, 108, 108, 111] // ascii "Hello"
// access the underlying raw buffer as a pointer
a.withUnsafeBufferPointer { buf -> Void in
process(
UnsafeBufferPointer(
// cast the underlying pointer to the type you want
start: UnsafePointer(buf.baseAddress),
count: buf.count))
}
// this prints [h, e, l, l, o]
Note withUnsafeBufferPointer means what it says. It’s unsafe and you can corrupt memory if you get this wrong (be especially careful with the count). It works based on your external knowledge that, for example, if any of the integers are negative then your code doesn't mind them becoming corrupt unsigned integers. You might know that, but the Swift type system can't, so it won't allow it without resort to the unsafe types.
That said, the above code is correct and within the rules and these techniques are justifiable if you need the performance edge. You almost certainly won’t unless you’re dealing with gigantic amounts of data or writing a library that you will call a gazillion times.
It’s also worth noting that there are circumstances where an array is not actually backed by a contiguous buffer (for example if it were cast from an NSArray) in which case calling .withUnsafeBufferPointer will first copy all the elements into a contiguous array. Also, Swift arrays are growable so this copy of underlying elements happens often as the array grows. If performance is absolutely critical, you could consider allocating your own memory using UnsafeMutablePointer and using it fixed-size style using UnsafeBufferPointer.
For a humorous but definitely not within the rules example that you shouldn’t actually use, this will also work:
process(unsafeBitCast(a, [UInt8].self))
It's also worth noting that these solutions are not the same as a.map { UInt8($0) } since the latter will trap at runtime if you pass it a negative integer. If this is a possibility you may need to filter them first.
IMO, the best way to do this would be to stick to the same base type throughout the whole application to avoid the whole need to do casts/coercions. That is, either use Int8 everywhere, or UInt8, but not both.
If you have no choice, e.g. if you use two separate frameworks over which you have no control, and one of them uses Int8 while another uses UInt8, then you should use map if you really want to use Swift. The latter 2 lines from your examples (process([UInt8](buffer)) and
process(buffer as! [UInt8])) look more like C approach to the problem, that is, we don't care that this area in memory is an array on singed integers we will now treat it as if it is unsigneds. Which basically throws whole Swift idea of strong types to the window.
What I would probably try to do is to use lazy sequences. E.g. check if it possible to feed process() method with something like:
let convertedBuffer = lazy(buffer).map() {
UInt8($0)
}
process(convertedBuffer)
This would at least save you from extra memory overhead (as otherwise you would have to keep 2 arrays), and possibly save you some performance (thanks to laziness).
You cannot cast arrays in Swift. It looks like you can, but what's really happening is that you are casting all the elements, one by one. Therefore, you can use cast notation with an array only if the elements can be cast.
Well, you cannot cast between numeric types in Swift. You have to coerce, which is a completely different thing - that is, you must make a new object of a different numeric type, based on the original object. The only way to use an Int8 where a UInt8 is expected is to coerce it: UInt8(x).
So what is true for one Int8 is true for an entire array of Int8. You cannot cast from an array of Int8 to an array of UInt8, any more than you could cast one of them. The only way to end up with an array of UInt8 is to coerce all the elements. That is exactly what your map call does. That is the way to do it; saying it is "unnecessary" is meaningless.