Problematic Swift Arrays

In the Apple developer forums, there is a lot of talk about how the semantics of arrays are messed up in the current beta builds of Xcode 6. Yes, they are messed up, but many are missing the actual problem and are trying to solve the symptoms.

Example issue with arrays:

let array = [5, 1, 0, 10]
array[1] = 0  // just changed a value in the "const" array, ok...
array += 5    // compile time error: good

var sortedArray = sort(array) // original array updated, oops!
sortedArray.append(4)

sortedArray   // prints: [0, 0, 5, 10, 4]
array         // prints: [0, 0, 5, 10]

Ok, we can hopefully see a series of errors here.

  1. Constant arrays can have their positional values changed, but not appended to 2. Arrays that were constant in one function scope are indeed not constant when passed to another function

So what's the real problem here? There are actually two problems that end up causing this issue:

  1. Arrays are structs and structs have value-type semantics. The entire metadata for the array, such as the startIndex, endIndex, and the array buffer pointer all get copied on assignment. This is ripe for bugs, as we have seen above. 2. Lack of true immutable types.

I'm going to assert that issue #2 is the true source of the problem. Let's look at the example above but change two assertions about the language:

  1. Classes have the same mutating attribute that can be applied to them as structs. 2. All parameters into functions are immutable unless the inout attribute is specified.

First, the basic definition of the array looks like this:

struct Array<T> {
  var startIndex : Int, endIndex : Int
  var buffer : ArrayBuffer<T>
}

It's important to note that the ArrayBuffer is essentially a class (technically it's a struct, but internally it uses a class to store the contents of the array).

When a copy of the array is made, all of the values of the array get copied; that is:

  1. The numerical value of startIndex 2. The numerical value of endIndex 3. The pointer to the array buffer, not the contents of the array

Let's take a look at a simple function to reverse an array:

func reverseArray<T>(array : T[]) -> T[] {
  var copy = array.copy()
  var left = 0, right = array.endIndex - 1
  while left < right {
    var swap = copy[left]
    copy[left] = copy[right]
    copy[right] = swap
    left++; right--
  }
  return copy
}

Now, this method works correctly with today's implementation Swift. It works because of the bolded line: it creates a deep-copy of the array and not just a copy of the pointer to the array contents.

If I wrote the function as such:

func reverseArray<T>(array : T[]) -> T[] {
  var copy = array
  var left = 0, right = array.endIndex - 1
  while left < right {
    var swap = copy[left]
    array[left] = copy[right]
    copy[right] = swap
    left++; right--
  }
  return copy
}

This method now suffers from the problem of updating the array that is being passed in (I also introduce another bug on line 6 to show the other failure case). This is the real problem!

Now, if the Swift language implemented the two items I mentioned above, the above function would not have compiled. Instead, the following would be true:

  1. Line 2 would have a compiler error for assigning an immutable type to a mutable type (remember, I claim that parameters should be immutable by default, only overridden by the inout attribute) 2. Line 6 would also have a compiler error because the subscript methods would have needed to be marked with mutating in order for assignment to take place.

The problem is not with arrays in Swift; the problem is in how Swift does not have a mechanism to stop classes from mutating its members.

I'll leave you with this one last example, put it in your playground and you'll see immediately that arrays are just a specific example of this limitation:

class CPerson {
  var name : String
  init(name : String) { self.name = name }
}

struct SPerson {
  var age : Int
  var person : CPerson
  mutating func haveBirthday() {
    println("Happy birthday!")
    age++
  }
}
let david = SPerson(age: 32, person: CPerson(name: "David"))
var frank = SPerson(age: 35, person: CPerson(name: "Frank"))

//david.age = 23            // compiler error, as it should
david.person.name = "Sally" // this SHOULD be a compiler error,
                            // but classes do not support
                            // 'mutating' or const in general
david.person.name           // prints "Sally"

frank.age = 23
frank.person.name = "David"
frank.person.name // prints "David"
Problematic Swift Arrays

Swift – The Future for App Developers?

Myself, like most developers watching the WWDC keynote, I'm sure were shocked when Swift was announced. I think many of us have longed for a world where header files were a thing of the past, a syntax that could be freed from the C days of coding, and a generally safer language where it is harder to make little mistakes that could cause hours of debugging over a stupid mistake.

However, once developers started to play around with Swift, we realized a few sad truths:

  1. While the language is "ready for use", it is only so in the most basic of ways. Many features are missing, there are many rough edges, and there seems to be some fundamental design flaws in the language. And lots of editor and compiler crashes.
  2. The language promised performance, and while it may be faster than ObjC, many in the community are having a really hard time finding where that is true. Apple, please publish the code for your benchmarks so we can see the kind of Swift code we are supposed to be writing.
  3. The baggage of C may be mostly gone, but the language has clear design trade-offs to support better integration into ObjC. This doesn't seem like a win to be but rather the easiest way to bridge Swift and ObjC.

Before diving into the specifics, let me be explicitly clear: I think Swift is a step, a great number of steps in fact, in the right direction. And if some of flaws are fixed, the language will be a great improvement for the future of iOS and Mac development.

Header Files—Where are you?

Header files are gone! The only thing header files were good for, in my opinion, were create a nice contract for external consumers of your code. Other than that, they were a nightmare, especially in large projects. I've spent many hours tracking down symbols because other developers would simply not put all of their dependencies in the header files… I'm ecstatic that is no longer a problem!

The other great thing about no header is that we can take code that looked like this:

@interface Person {
  @property (copy, nonatomic) NSString *firstName;
  @property (copy, nonatomic) NSString *lastName;

  - (instancetype)initWithFirstName:(NSString *)firstName
                           lastName:(NSString *)lastName;
  - (NSString *)fullName;
@end

@implementation Person {
  - (instancetype)init() {
    return [self initWithFirstName:@"" lastName:@""];
  }
  - (instancetype)initWithFirstName:(NSString *)firstName
                           lastName:(NSString *)lastName {
    self.firstName = firstName == nil ? @"" : firstName;
    self.lastName = lastName == nil ? @"" : lastName;
  }

  - (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@",
            firstName, lastName];
  }
@end

And define it as this:

class Person {
  var firstName : String
  var lastName : String
  var fullName : String { return "\(firstName) \(lastName)" }

  init(firstName : String = "", lastName : String = "") {
    self.firstName = firstName
    self.lastName = lastName
  }
}

This is such a big win. There are also some implicit differences about this contract:

  • Both the firstName and the lastName are enforced, at compile time, to never be allowed to be set to nil.
  • The member fullName is actually a computed property and not a method. This is only semantically different, but it provides an explicit statement of being a property of the class instead of a method that does work on the class.

Syntax—Nearly Perfect

I know I'm one of the few that actually enjoy the square-bracket syntax, well, as long as the nesting doesn't get too crazy. However, Swift takes us nearly all of way into the syntax of some of the more functional languages and some other scripting languages, like Python. This is just something you're going to have to get over (unless we can convince Apple to change its mind!).

Safe-by-default has also greatly added clarity to switch-statements as well. The ability to fallthrough while preventing accidental fallthroughs can only be seen as a win.

Closures are a nice addition, though semantically the same as blocks. However, we start to tread into some murky waters when looking at closures, the trailing closure syntax, and braces in general.

Swift, unfortunately, is still a curly-brace based language. This means that we write code like this:

var name = "David"
if name != "David" {
   println("Why is your name not David?")
}
else
{
  println("That's a great name!")
}

Now, I've purposely done something that will cause many holy wars across any code review: mismatched braces! Which line should the curly brace be on? Of course, the first is correct… or is it the second. I believe Swift should have simply fixed this issue as it's actually not even important.

var name = "David"
if name != "David"
  println("Why is your name not David?")
else
  println("That's a great name!")
endif

I think this is actually cleaner and creates less options for the developer. While options can be good, they should only be there if needed. Curly-braces have long not been needed in the world of programming.

Let's look at another place where this is actually an issue.

func measure(block: () -> ()) -> NSTimeInterval {
  let startTime = NSDate()
  block()
  let endTime = NSDate()
  return endTime.timeIntervalSinceDate(startTime)
}

Here we have a method that takes a closure and returns the amount of time it takes to run it.

measure({
  var x = 10 + 2
})

measure(
{
 var x = 10 + 2
})

measure() {
  var x = 10 + 2
}

measure()
{
  var x = 10 + 2
}

Unfortunately, only three of these are actually valid code; the last version is not valid and will result in two compiler errors. This is a parser limitation. Instead, had Swift gone away from curly braces all together, this problem could have been avoided.

func measure(block: () -> ()) -> NSTimeInterval =>
  let startTime = NSDate()
  block()
  let endTime = NSDate()
  return endTime.timeIntervalSinceDate(startTime)

measure() =>
  var x = 10 + 2

measure() => var x = 10 + 2

Now, this syntax allows for a common syntax for defining functions and closures and it works with trailing closures. There is a trade off though. If you look at the above, it really doesn't allow for defining those closures as a parameter though.

measure(=>
 var x = 10 + 2
)

That is terrible, do not do it.

let fn = () -> () => var x = 10 + 2
measure(fn)

That's the trade-off, and one I would have gladly taken. It's consistent and helps us write the code in a narrow set of ways.

Named Parameters—Um… What?

The named parameter implementation has got to be right up there as one of the roughest edges in Swift. I really do not like the fact that the API author is the one that dictates whether the name is required or not. I can see why this feature was added—interop with ObjC naming. But I do think it's the wrong design, especially for Swift-only code.

Let's take a look at the choices:

func add(x : Int, y : Int) { x + y }
class Calculator {
  class func add(x : Int, y : Int) { x + y }
  class func add2(#x : Int, _ y : Int) { x + y }
}

add(3, 6)                  // note: no names allowed
Calculator.add(3, y: 9)    // note, the x: is not present
Calculator.add2(x: 3, 9)   // note, the y: cannot be present

There are actually different defaults for the name requirements; functions are different than class members. The above is actually the only valid way to call those methods.

The following should be valid:

add(x: 3, y: 6)
Calculator.add(3, 9)
Calculator.add(x: 3, y: 9)

This is one of the places where the ObjC baggage comes across very strongly over into Swift. The Session 404: Advanced Swift WWDC talk gives an example of this and walks us through a story. In that story, we are told that this is just too verbose:

let wallWestOfHouse = Thing(location: westOfHouse,
  name: "wall",
  longDescription: "The plaster has crumbled away, leaving the wood beneath to rot.")

And that it would be really nice to be able simply write this:

let wallWestOfHouse = Thing(westOfHouse, "wall", "The plaster has crumbled away, leaving the wood beneath to rot.")

I agree, it should be. However, my argument is that both should actually be completely valid without putting that in the API and forcing one use or the other. In certain contexts the missing named annotations could be beneficial, but in others, it may not matter.

Not the end of the world, but I think this could have been cleaner.

Immutability—Or Lack Thereof…

At first, when I started to hear the mention of preference for immutable types, immutable by default, only use var when necessary, my hopes were high. There are such great things you can do in the compiler to optimize code that is dealing with immutable objects. However, as the details started to come out, it because clear that Swift doesn't actually have immutable types, only immutable values. This is very dissapointing.

This is the one part of Swift that I cannot seem to wrap my head around this decision and it's clearly not an oversight because there is documentation showing off the feature and how it works.

Let's take a look:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

Source: Swift Programming Langauge: Classes and Structures

The let keyword is supposed define a constant such that "the value of a constant cannot be changed once it is set." What Swift has done though, is take that literally. For all object types, the "value" is the pointer to memory.

In the code sample above, everything is just fine because the memory address for tenEighty is never being re-assigned. I think this is a fundamental design flaw. Essentially, in Swift, it is impossible to create an immutable type. This has got to be addressed before going public.

Arrays come out as a very interesting case of this issue. The reason is that an array is actually a struct, but that struct has a pointer to the collection.

let items = [ "Bob", "Frank", "Sue" ]
items[0] = "Henry"
items

Paste that into a playground and you'll see the output is this:

[ "Bob", "Frank", "Sue" ]
"Henry"
[ "Henry", "Frank", "Sue" ]

However, the following code is not valid:

items += "Sally"

The idea that a developer has to understand how something is implemented to understand what is truly immutable about a type and what is not is broken. The fact that the "constant" array is not semantically immutable, is also broken.

In fact, nearly every useful optimization that we can make about parallelization and thread safety goes out the window because of this fundamental issue with Swift: there is not such thing as an immutable type.

Probably the most baffling reason for me is that Swift already has the correct foundation built to offer the exactly right thing!

struct Point {
  var x = 0.0, y = 0.0
  mutating func moveByX(deltaX: Double, y deltaY: Double) {
    x += deltaX
    y += deltaY
  }
}

var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)

let otherPoint = Point(x: 2.0, y: 5.0)
otherPoint.moveByX(10.0, y: 4.0)        // compiler error!

If Swift simply required every method on classes that updated member variables to need the mutating annotation, this problem would be largely solved. The only remaining piece would be the way in which to specificy a constant as a parameter to a method.

Summary

All in all, I'm really excited about Swift and the potential that it brings to the table. I've railed a bit on the weaknesses I've found in the language, mostly because I hope Apple will address them. Being able to play a role, however small it is, for a language that I will probably be using for a great deal of years to come on a platform I love to author on in is a very exciting prospect indeed.

Thanks Apple for a truly epic WWDC! And thanks to all of the Swift authors at Apple that will now have to hear from our criticisms and complaints about the language that you've worked very hard on and impressively kept under wraps for four years.

Swift – The Future for App Developers?

Benchmarking

If you are going to go out and claim that someone else is lying (see: http://www.splasmata.com/?p=2798), you really should back up your claims with provable and reviewable facts. In the case of benchmarks, you need to provide the following:

  1. The source code you used—this should be common sense. People should be able to peer review your work.
  2. Compiler and compiler flags used; without these, your results are meaningless.
  3. The average and standard deviation of your results.
  4. Test methodology used

This is the bare minimum. The author is missing all but #4.

For all of the following benchmarks, I used the Xcode 6 Beta build with the new XCTest performance feature. Tests were conducted on a Mid 2012 Retina MacBook Pro: 2.7GHz Intel Core i7 and 16 GB RAM. All tests were run under the standard Release mode settings.

So let’s look at the author’s claims:

Loop a million times

Swift: 0.0036s

Objective-C: .0021s (1.7x faster)

Since we have no code, we’ll have write our own.

Swift Timing (all three versions): 0.001s, 11 STDEV (gist)

ObjC Timing: 0.002s, 23% STDEV (gist)

Swift came out ahead here, but not by any statistically significant margin. Also, the author made the following claims:

  1. for _ in 0..999999 was “glacial”. The gist above shows this form to have identical performance to the other forms.
  2. ++ was really slow, yet the numbers say the same thing—identical performance to the other forms.

Seems like we’ve hit the basic problem: compiling with optimizations off—DEBUG builds.

Let’s check out the next one.

Increment

Swift: 0.024s

Objective-C: 0.0023s (10.4x faster)

The author claims that Swift has a performance problem with the ++ operator. Let’s check it out… oh wait, already did above. Yep, no issue in release builds. If we run the same tests under the debug configuration, we can see why the author is making the claims he made… oops!

We’ll take a look at one more:

Append native integer to native array

Swift: 6.51s

Objective-C: 0.023s (283x faster)

Ok, that looks like it hurts… let’s see if it is true.

Swift: 0.271s, 6% STDEV (gist)

ObjC: 0.039s, 38% STDEV (gist)

Looks like ObjC comes out on top here, but not by anywhere near the margins claim by the author.

Moral of the story, it’s best not to call people liars when your “facts” are poorly backed up. Also, benchmarks should always be peer-reviewable.

Swift has lots of potential issues with it, but performance over ObjC does not seem to be one of the big concerns.

Benchmarking