Smashing Swift

I have been playing with [Swift][] ever since Apple announced it at this year’s WWDC. I was lucky enough to be there, and I spent a good chunk of my time in the labs, coding and asking the engineers who created it questions. There is nothing like being among the first to get your hands on a language, and being among the first to explore it. Even if you immediately find ways to break all the things.

• • • • •

In the past, I have loved two things in programming languages: code-as-data, as in Lisps, and strong type systems, as in Haskell and Scala. Coincidentally these are functional languages. I say coincidentally, because their functional aspect is not the primary reason I loved them. What I loved was their ability to simplify a part of the general problem of programming.

Lisps simplify syntax and implementation details; Haskell and Scala simplify logical correctness. Specifying a type system upfront allows a developer to do away with several large classes of common errors and allows her to focus on the internal logic of the problem, shaped by the types she defines and helped by a compiler that alerts her if she attempts to work inconsistently within her type system.

These two aspects are [not mutually exclusive][qi], but typically at most one is found in a language. Swift opted for generics and a static type checker. And I started trying to implement things.

• • • • •

The first thing I noticed was that there is no concept of a [functor][] in Swift. Long story short, a functor is a data type for which a mapping function makes sense. In a Swift environment, it would look something like this:

protocol Functor {
    typealias T
    func map<P>(mappingFunction: T -> P) -> Self<P>
}

If this looks like a regular map function for an array, that’s because it is: Arrays are functors. But dictionaries can be functors too. Suppose you have a Dictionary<Int, String> mapping jersey numbers to names for a team of football players. If you have a function String -> PhoneticGuide that maps strings to English pronunciations, you can use a mapping function to get a Dictionary<Int, PhoneticGuide> of jersey numbers to phonetic guides for the names of the players. The mapping function would look like this:

extension Dictionary<K, V> {

    func map<P>(mappingFunction: V -> P) -> Dictionary<K, P> {

        var newDict:Dictionary<K, P> = [:]
        for (key, value) in self {
            newDict[key] = mappingFunction(value)
        }

        return newDict
    }

}

And usage would look like this:

let numbersToNames:Dictionary<Int, String> = … // some dictionary
let phoneticGuide:String -> PhoneticGuide = … // some function

let numbersToGuides = numbersToName.map(phoneticGuide)

Simple enough. And Functor allows any type that adopts it to support that operation—and therefore to inform users that it does.

Except there’s a catch. The protocol I specified above is not possible in Swift. Try it: it will fail, because you can’t parametrize functions defined in protocols; in other words, the P is not supported. Bummer.

• • • • •

Okay, so functors didn’t work out. Next, I tried [reimplementing][Thunderbolts] the BFTask API of [Bolts][] in Swift. It’s a prime candidate: the functionality is powerful, but standard usage is often cluttered by error handling. Or worse, it isn’t and errors are ignored. I tried to use generics to do better and reworked the API. At its core, I created a Result enum like so:

/*
A wrapper around the specific result returned by a computation. Allows the consistent treatment of errors, including the short-circuiting of computations that are dependent on each other.
*/
enum Result<ResultType> {
    /*
    Describes a normal outcome: the computation was carried through to
    its completion and yielded a result.
    */
    case Normal(ResultType)

    /*
    Describes an error condition: the computation failed.
    */
    case Error(String)

    /*
    Describes a user-initiated cancellation of the task.
    */
    case Cancelled
}

With the use of a couple of mapping methods a lot like the one I showed above, I can abstract away error handling and cancellation and let the user focus on the sequence of tasks she wanted to execute. But, you guessed it, that doesn’t work.

The problem here isn’t as bad as before: the language does support the construct. The compiler, however, doesn’t. (Yet.) If you paste that into a Swift file, you will find that the compiler crashes on you, and tells you the IR (intermediate representation) is not yet implemented for this type of construct. Bummer again.

• • • • •

Finally, I decided to learn something brand new. At my brother’s suggestion (so really, this is all his fault), I decided to implement something resembling Scala’s [Streams][] in Swift. Streams are lazy sequences that compute elements 0 through N when element N is requested. To prevent re-computation, they then memoize them; at least the Scala implementation I was basing my work on does. So here’s a draft of a Swift Stream type:

class Stream<P> {
    var memoizedElements:P[]

    init(initialElements:P[] = []) {
        self.memoizedElements = initialElements
    }
}

Again, simple enough: A Stream contains a variable array to hold memoized elements, and can accept pre-computed elements (this is useful if we want to do operations like mapping on streams without recomputing the whole stream every time).

What? You think it doesn’t work? I could do with a little less negativity over here. I could. Except I can’t. The default array causes another, different compiler crash—this one an all-out segfault. Bummer. Bummer, bummer, bummer.

(Update: @mkonutgan points out I should clarify: the cause of the crash is the empty array passed as a default parameter. @maxpow4h on HN suggested a workaround.)

• • • • •

So in three attempts I have run into three things that break the compiler at the type system level. One of them was unsupported by the language, period. The second is theoretically supported but not yet implemented. The third segfaults the Swift compiler. Just what kind of insane demands am I placing on the that poor program?

To me, the things I tried weren’t insane; they seemed like the obvious things to try. And when I compared Scala’s [Promises] API to my [Thunderbolts][] API, I was pleasantly surprised by the similarities—I’m not coming up with lunatic (revolutionary…) ways of doing things; I am discovering the discovered. And yet, the writers of Swift have obviously not used constructs like that in their own code.

That’s fascinating. It’s the kind of thing that makes me want to speculate. But the thing with Apple is that it’s a black box—there can be any number of reasons why they chose to limit these features, and it is an insult to people who are far more knowledgeable about languages than I am to suggest that they don’t understand what I’m trying to do. So I am merely left with a question mark.

• • • • •

Question mark aside, I love Swift. I love what it already lets me do, and I love what the compiler crashes let me hope it will do, once it is stable. And given how active the Swift developers are on the Apple Developer Forums, I even have hope for the things it will let me do as they improve it. Yet until these things happen, I have to admit: I feel like I’m working with a fine porcelain vase that I can smash by breathing at it wrong. But how much it is, after all, to have anything to smash.

[Swift]: https://developer.apple.com/swift/
[qi]: http://en.wikipedia.org/wiki/Qi_(programming_language)
[functor]: http://learnyouahaskell.com/functors-applicative-functors-and-monoids
[Streams]: http://www.scala-lang.org/api/2.10.2/index.html#scala.collection.immutable.Stream
[Promises]: http://www.scala-lang.org/api/2.10.2/index.html#scala.concurrent.Promise
[Bolts]:https://github.com/BoltsFramework/Bolts-iOS
[Thunderbolts]: https://github.com/nomothetis/Thunderbolts

 
2,817
Kudos
 
2,817
Kudos

Now read this

Addendum: Deriving the Third Monad Law From Nested Comprehensions

The third monad law declares that the following identity must hold for a monad M, where a:M<A>, f: A -> M<B>, and g: B -> M<C>: (a >>=- f) >>=- g == a >>=- { b in f(x) >>=- g } To motivate... Continue →