Is Swift Really ObjC without the C?

It has only been weeks since Swift was announced, and it is only the first drop of Swift that the public has seen. At the same time, we are only a matter of months away from the nebulous "Fall" timeframe for when the Swift language will essentially hit v1.0 with the final drop of Xcode 6.

This has me very sad for the future of iOS and OS X development. I do not see, at this point in time, any major changes happening to Swift. There are many fundamental items about Swift that I'm just not a fan of and I think break the spirit of what ObjC was about: pragmatism.

These are the items that I think truly represent the heart of ObjC:

  1. Dynamic Typing
  2. Message Passing
  3. Extensibility (at runtime)
  4. Protocols (aka Interfaces)

The rest of the features of ObjC are really just implemented on these ideals. With the advent of Swift, we see these as the ideals:

  1. Static Typing 2. Generics 3. Dispatch tables 4. Extensibility (at compile time)

Gone is the dynamic nature of ObjC; it has instead been replaced by a rigid, generic based type system. I know the world of C

I do not think we got "ObjC without the C", rather, we got something much more like C# that compiles on clang and bridges into Cocoa a bit haphazardly.

ObjC without the C

I'm going to break down some examples of what I would have liked to have seen in Swift instead of what we got; I'll refer to that version of Swift as SwiftOC (or Swift with ObjC).

Generics

Let's start off with everyone's favorite canonical example of why we need generics: a collection of items.

In Swift, we can create strongly-typed collections:

let intArray = [1, 2, 3, 4]        // Array<Int>
let stringArray = [ "hi", "bye" ]  // Array<String>
let array = [ 3, 4.5, 8.9 ]        // NSArray, not! Array<Double>

Ok, in our imagined SwiftOC, we could have had the same code, but each type would have simply been an Array:

let intArray = [1, 2, 3, 4]        // Array
let stringArray = [ "hi", "bye" ]  // Array
let array = [ 3, 4.5, 8.9 ]        // Array

But what if I put in a wrong type? I hear this argument a lot… and well, yes, it is a possibility. It is also the possibility to put in an incorrect value, to be off by one iterating over the array, or to print the contents of the wrong array; there's a line somewhere that needs to be drawn where the programmer needs to figure out how to address potential issues in ones code. However, this is a case that is immediately found at run-time; it is not a pandemic problem that needs the rigidity of generics to solve.

What about the generic functions like sort?

func sort<T : Comparable>(array: T[]) -> T[]
func sort<T>(array: T[], pred: (T, T) -> Bool) -> T[]

That is what sort looks like today. Notice a truism about generics that often gets overlooked: all generics require a contract for the types to work; sometimes this is explicit, other times it's implicit and only inferred at compile time. It is also easy to overlook the fact that this contract is often implemented via overloaded operators, which hides the details away making it look like you get certain functionality for free—I call this "compiler magic".

In SwiftOC sort would have looked like this:

// Comp would be a Comparable interface, shortened for space reasons
func sort(array: Array) -> Array
func sort(array: Array, pred: (Comp, Comp) -> Bool) -> Array

I'll admit, there is a difference between the SwiftOC code, and, yes, the Swift version is technically more type-safe, on paper at least. The difference is that in Swift you can guarantee that each item in the array adheres to the Comparable protocol.

This is where theory and practice comes in to play though. This is fundamentally not a real-world problem that gets exposed for any length of time, if at all; it is caught early, even in a project with little-to-no testing. It's a classic compile-time vs run-time tradeoff.

Further, you could actually still implement this in a collection class if you wanted by enforcing the add methods to require the inserted items to implement Comparable.

class Array {
  func insert(#item: Comparable, atIndex: Int) {}
}

There you go, all items that go into the array meet the requirements of sort. In fact, you could require and provide a default behavior for all types within the language and provide default behaviors for particular types. This again would alleviate most of the problems that were trying to be solved with generics in the above example.

Yes, generics can be useful in particular cases. However, they are not helpful, ironically, in the generic use case. I would argue that simply sticking with a stronger-type base instead of fully type-safe and generic based language would have been a more pragmatic route to take.

Let's take a look at another example where generics really are not helpful: JSON.

var resp = {
  "stat": "ok",
  "blogs": {
    "blog": [
      {
        "id" : 73,
        "name" : "Bloxus test",
        "needspassword" : 0,
        "url" : "http://remote.bloxus.com/"
      },
      {
        "id" : 74,
        "name" : "Manila Test",
        "needspassword" : 1,
        "url" : "http://flickrtest1.userland.com/"
      }
    ]
  }
}

That's a real-world JSON response from a web-services API. Note the mix of strings and numbers; even if they were all strings, they are only string representations of a number.

Swift's type-safety mechanics offer absolutely no help here. In fact, we really need to use the Cocoa types to do anything useful with this because what we have is an untyped dictionary: Dictionary<String, AnyObject!> or NSDictionary. In order to write safe code here, you have to do the exact same thing in either language: check for a null value and check for a value that can be converted to the expected type.

let blogs = resp["blogs"]?["blog"]  // ? could be done in both Swift
                                    // and SwiftOC

At the end of the day, there are essentially two worlds of programming languages when it comes to types: static and dynamic. The Apple platform and the entire web platform have run on dynamic code with relatively little problems because of the type system. However, in one move, I believe Apple did the biggest disservice to ObjC programmers: fundamentally shifted them from a world of flexibility to a world of rigidity based on the guise of "safety" and generic programming.

Simplicity

ObjC was a fairly simple language—it added on a few, well defined mechanics of top of already concise language: C. There were virtually no special rules to apply to different parts of the language. However, with Swift, we are opened up to the world of complexity. Yes, the syntax is concise and fairly easy to grasp, however, you are entering a world that is deceptively simple.

let optval : Bool? = false
if optval           { "1" }
if optval!          { "2" }
if optval == false  { "3" }
if optval! == false { "4" }
if optval == true   { "5" }
if optval! == true  { "6" }

What happens in the above code? Yes, this is a trick question and a source of some discussion on the developer forums. The answer is this:

"1"
"3"
"4"

The reason? Well, optional values can be implicitly compared to non-optional values thanks to this overloaded operator:

func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool

So that check to see if your optional value has a value or not better not use the == operator; if so, you'll end up with the wrong results. This is extremely error prone and very hard to track down because you cannot actually debug the flow to understand what is going on here; these are compiler choices not run-time decisions you can see on the stack—more "compiler magic".

Operator overloading is often cited as a powerful, if not good, addition to any language. However, it can be the source of some of the most non-obvious programming errors and is far worse then non-generic typed arrays. When the incorrect values show up in the array, things go boom quick. When these types of errors happen, they are subtle, sporadic, and often hard to repro without the exact set of data that went into the failing code path.

Let's look at another:

var intValue = 2
var doubleValue = 1.1

func add(x: Int, y: Int) -> Int { ... }
func add(x: Double, y: Double) -> Double { ... }

add(intValue, doubleValue)

Which function gets called?

If you answered, neither, you are correct! Because of Swift's type-safety, neither of these are valid. This could be a good thing such that no numbers are coerced improperly to different types, but pragmatically, this results in code that should look like this:

let radius = 4
let pi = 3.14159
var area = pi * radius * radius

Into code that looks like this:

let radius = 4
let pi = 3.14159
var area = pi * Double(radius) * Double(radius)

That's not simpler and I would argue adds no safety, but rather, detracts from what the code is supposed to really be doing. Sure radius could have been a double, but it's not always the case that one is performing math operations on the same types of values.

Also in the above is method overloading. This is relatively safe, but does promote the loss of intention from the functions. If we had:

func add(x: Int, y: Int) -> Int { ... }
func add(s1: String, s2: String) -> String { ... }
// the operator version
func +(x: Int, y: Int) -> Int { ... }
func +(s1: String, s2: String) -> String { ... }

What is the string version supposed to do? Parse the numbers from the string and return string representation of the result? Append the string? It would be much clearer to instead choose better names:

func append(string: String, to: String) -> String { ... }

This is much clearer, especially since the named parameters are part of the signature. Swift seems to be moving away from this though, especially in the context of items like overloading operators. I do not think that is a good thing. It's also against part of what has made Cocoa such a good framework to work with: self documenting code.

Syntax and Style

This is an area that I think Swift did a fairly decent job on. It's cleaner, in most cases more straight-forward, and offers some helpful shortcuts for various patterns.

There is one thing that I would have actually like Swift to address: curly braces.

Curly braces have long been the source of discussions, or rather, "holy wars". What line do they belong on, which level of indentation do they get, do we use them for one-liners? Swift had a perfect opportunity to absolve this problem by simply removing them from the language. The truth of the matter is that we simply do not need them anymore. Also, it would have imposed a style on the language that would have enforced consistently across codebases which would have only increased readability and pattern matching.

func compare(x: Int, y: Int) -> Int {
  if x < y { return -1 }
  else if x > y
  {
    return 1
  }
  else
    {
    return 0
    }
}

That's the kind of mixture of braces that we've all seen. And sometimes in the same code file! YUCK! I think Swift would have gained a lot if it took a page out of Python's book:

func compare(x: Int, y: Int) -> Int
  if x < y
    return -1
  elseif x > y
    return 1
  else
    return 0
  end
end

Sure, the end keywords are not actually necessary for a parser. However, they provide visual clarity to the end of blocks. Whitespace does an insufficient job at this, especially when scanning code quickly. But if we want to be purists, then we would have this:

func compare(x: Int, y: Int) -> Int
  if x < y
    return -1
  elseif x > y
    return 1
  else
    return 0

Of course, both should not be supported. I do think that either one would have been preferable though.

Summary

I think there is a lot more to discuss about the language and how I think Apple has missed the mark of "Objective-C without the C". Instead, it really fells like we got a form of C# 2.0 or the core of the C

Unfortunately, I do not think that much of this will change with later drops. Instead, we'll see bug fixes and refinements of what exists today. It is very un-Apple to drop such a huge and fundamentally different change on our laps as they have been the company of evolutionary refinement.

There is no doubt in my mind that Swift will win over many, but will they be from the long-time Apple developer crowd or will they be the converts from the Android and Windows world? ObjC has long been a misunderstood language to them because many were unwilling to really embrace the paradigm. Apple seems to have bent to their will instead of providing us with a truly great ObjC 3.0. This saddens me greatly.

Chris Lattner (@clattner_llvm, the guy behind the Swift language), said this about the language on Twitter:

Swift is a pragmatic, not a religious, language. It tries to get the defaults right, but allows you to change them if it doesn't suit you.

: .quote

I have to wonder, what is the pragmatic goal of Swift? From where I sit, it's gets all of the defaults wrong because the defaults should have been about the interaction with the Cocoa API. Until there are native Swift versions of those, all of code looks and feels much messier and far less elegant than what we have today.

Is Swift Really ObjC without the C?