Unfamiliarity is often mistaken for complexity. In this talk, Abizer Nasir explains why a little knowledge of Haskell helps us to think about Swift. We learn about the importance of immutability, containing side effects, owls, and why the type system is your friend. It might be unfamiliar, and at times it might even be hard, but it is certainly worth doing.
Why Haskell? (0:00)
I’m here to talk about what Haskell taught me about writing Swift. Why Haskell? I came to programming late in life, and I’m not actually very good at learning programming languages. A friend of mine at university told me about this language that was very pure, and showed me some examples, so I thought I’d give it a go. So, I tried it, and surprisingly, once I got over the type system I found that I could actually write this.
So, what is Haskell? The Haskell Wiki describes it as a “polymorphically statically typed, lazy, purely functional language.” How does this translate into Swift? “Polymorphically”, that would be Swift’s generics side. Swift is also statically typed, but not compared to how crazily Haskell is statically typed. Swift has some laziness, but nowhere near the sort of laziness that Haskell has. Lastly, forget Swift being purely functional — Haskell is probably the purest of the pure functional languages. I believe it was developed as a research programming language, and apocryphally, they didn’t have a way to get code in and out of the system. They couldn’t figure out how to create a pure functional I/O system until they came up with the I/O monad.
Favor Immutability (1:27)
When writing Swift, favor immutability, don’t change values. In Haskell, everything is an immutable value. It’s a lot easier to maintain the code and to be thread safe. Things don’t change from under you, so it becomes a lot easier to reason about things.
Wherever possible, favor structs over classes. If you do have a struct, try not to mutate it. I look at structs as a contract that says, “I’m a value and I am not going to change. I will never change myself, so please don’t do that to me.”
The Owl Problem (2:08)
You may have heard of the owl problem, which is something that comes up in programming languages. Here are the instructions for drawing an owl: “Draw a circle. Then draw the rest of the owl.” Haskell has a similar saying: map a function over the list, and then write the rest of your program.
There is a lot of list programming in Haskell. You can do a lot of crazy operations on lists, including pattern matching and guards. These operations are a lot more powerful than the operations we have in Swift, though Swift does still have some nice tools. You can do things like take a value, pop the value off, and iterate over the list until you get a value. So, if you have a list and you want to perform some operation on it, you don’t actually do it to the list. For example, if you want to do an add over the list, you take one value off and go down the list recursively, adding them up until you get to the end. It sounds crazy until you actually do it, and you realize that is exactly what you are doing.
Map
, Filter
, & Reduce
(3:13)
In Swift, we don’t have things like tail recursion or recursive optimizations. We do have infinite lists, but they’re not efficient, to the same degree they are in Haskell. However, we do have map
, filter
, and reduce
. When I saw the ability to do those in Swift, I took to the language, because I thought, “Ah, I can do this.” These are very nice ways of being able to do what I want with my code. You could just iterate over the list, or write a function that, for example, takes a minimum if greater than forty and stores the value, to be returned later. In this case, you very simply want to filter
and reduce
.
Things like this are quite expressive to write. In Colin’s demonstration of ReactiveCocoa, you see a lot of these functions stacked up. When you come to read and maintain your code, it’s quite easy to do so. And, being able to do this in Haskell means I’m able to do it in Swift without worrying too much about it.
The Type System Is Your Friend (4:17)
You may not believe this, but Swift’s type system is your friend. SourceKit may not be your friend, but the type system is. If you can get your code to compile in Haskell, that takes you, more or less, most of the way there. With a strongly typed system, you don’t have to worry about a whole class of bugs. For example, if I pass a number to something, whether an int
, float
, or a double
, that value won’t be automatically translated into another type. You don’t have to write 1.0f
whenever you want to pass CGFloat
s around. If you give it a float
, it will know that, and when you write your tests, you do not have to worry if you give it other types, like NSNumber
or nil
. So, it becomes a lot easier to write your tests and to reason about your code.
If you can compile your code in Swift, it’s more of a miracle. In Haskell, if you can get your code to compile, you’re fairly well off. Something that we don’t have in Swift but do in Haskell is that Haskell is also very strongly type inferring. Haskell allows you to type a function and tells you the type that it infers from the values. If you remove the type declaration from the function definition, it will give you the most generic value it can infer from it. For example, if you are using plus, it will infer that it is dealing with two values that implement the plus operator. Things like that are really, really useful when trying to debug your code. Without this in Swift, you have to think upfront about the types you are working with.
typealias
(6:28)
typealias
is useful for making your code clearer. You can pass a lot of values around, but using typealias
will make your code more descriptive when reading it over again. For example, you could alias the type String : AnyObject
as JSONDictionary
. Now, when you write a function that writes these things, you say that you’re taking a JSONDictionary
, and as a result, your code is clearer to read and reason about.
func decode(json: [String : AnyObject]) -> MyType { ... }
// or
typealias JSONDictionary = [String : AnyObject]
func decode(json: JSONDictionary) -> myType { ... }
It also makes higher order functions a lot easier to read. For example, I wrote this example at work about Alamofire, which has a JSON response handler. I thought it would be easier if the same handler was used for all responses before the completion handler was called. Instead of duplicating the code, I just wrote a type definition for a higher order function. This gave me the actual completion handler that it would run. It would create common code that would run on every response, then run the handler after it.
// Taken from AlamoFire
typealias JSONResponseHandler = (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> ()
// Define a common completion block with specific handler
func responseHandlerWithCompletion(completion: Result<MyType>) -> JSONResponseHandler { ... }
It also becomes a lot easier to understand code when you are talking to people who are new to the Swift language, and new to iOS. When you have your type defined, reasoning about your problem in terms of the types and their transformations is an easier thing to do before you’ve written any code. Johannes Weiß, who will be giving a Swift Summit talk shortly after me, gave a talk last month for NSLondon, which went into this in more detail.
For example, say you want to write a card game. You can begin by saying that you want to shuffle
a Deck
, deal
, and find out who the winner
is. These are the types that you are passing through your program, and you can think about them straightaway. You still have to write the code - this example code won’t compile in Haskell unless you put undefined
on it - but this is a good way to lay out what you want to do before you actually do it. This prevents you from overthinking little, small functions that later have to be rewritten.
func shuffle(deck: Deck) -> Deck { ... }
func deal(deck: Deck, toPlayers: [Player]) -> [Hand] { ... }
func winner(hands: [Hand]) -> Player { ... }
Keep Code With Side Effects in One Place (8:52)
In Haskell, side effects can only be caused within a certain context. Similarly, try and write your code so that side effects are kept in one place. The following code is written in Haskell, and I’d like to show it to give you an idea of what I’m doing. The block at the bottom is where the I/O is being done.
main :: IO ()
main = do
t <- getLine
forM_ [1..read t :: Int] $ \i -> do
[c, f, x] <- fmap (map read . words) getLine
let result = solve c f x
putStrLn $ concat ["Case #", show i, ": ", Numeric.showFFloat (Just 7) result ""]
There, I take the input from the command line before calling all the other functions. Because I wrote this for a programming competition, I had to write it in a very short amount of time. And so, I wrote this in exactly that way where I defined my types before filling in all the details. I have a function with functions defined inside it, some guards, and some O’Reilly composition. That small chunk at the bottom is where I’m dealing with the outside world. Everything else is pure, where I take values and return them without changing any state.
Functions All the Way Down (9:52)
What we also have, in Haskell, is functions all the way down. There’s no real state; you don’t write a variable but a function that returns a value. You can’t do so much of this in Swift. It’s easier said than done if you have functions, you will still have state. But, you can write your functions so they’re as pure as possible, and change state as little as possible. Try to avoid mixing pure and impure code. Try and write small and, obviously, correct functions.
Below is one example of composition. There is only one way to write this function. You have a value that takes an A
and gives a B
, and a function that gives a B
and returns a C
. That’s the only way to get through the whole function. The same is true for mapping on optionals because in those functions, that is all you can do.
// composition
infix operator >>> { associativity right precedence 170 }
func >>> <A, B, C>(f: B -> C, g: A -> B) -> A -> C {
return { x in f(g(x)) }
}
// map on optionals
infix operator <^> { associativity left }
func <^> <A, B>(f: A -> B, a: A?) -> B? {
if let x = a {
return f(x)
} else {
return nil
}
}
For binding on optionals, I use a different symbol. This isn’t the Haskell symbol that other people use. I don’t want to step on the built-in bit shifting operator because I’m changing the precedence.
infix operator >>== { associativity left precedence 150 }
func >>== <A, B>(a:A?, f: A -> B?) -> B? {
if let x = a {
return f(x)
} else {
return nil
}
}
Unfamiliarity Can Be Mistaken For Complexity (11:49)
What I want to do is write code that is easy to read and understand. The following example may look complex, but it is likely unfamiliar to you. If you read it over a few times, you’ll see that there are really only three higher order functions that work over a simple parsing of JSON. Because you’re not used to seeing it, this looks very difficult to read. Some people might think that you don’t want to write code like this, because it is too difficult to learn. I disagree - I think that you can do this sort of thing.
static func decode(object: JSON) -> DateWithTimeZone? {
return jsonDictionary(object) >>== { dict in
DateWithTimeZone.create <^>
dict[dateWithTimeFormatKey(.Date)] >>== jsonDate <*>
dict[dateWithTimeFormatKey(.TimeZone)] >>== jsonTimeZone
}
}
Apart from talking about Haskell, I talk about Git a whole lot more. People say Git is hard, but I think that the things you do every day are harder. You have to understand frameworks and patterns; Git is just a very small thing. These things that I’ve been doing in Swift are hard, but you can learn how to do them. It’s worth trying.
References (12:42)
To finish off, I’ll go over a few references. The first two books, Learn You a Haskell and Real World Haskell are both available online for free. From them, you’ll learn so much more about the functional programming paradigm. You won’t necessarily get to use it all, but at least, you’ll understand it a lot better. The last book is Functional Programming in Swift, by Chris Eidhof and the objc.io team, which will really help if you want to know how to write this kind of code in Swift.
Thank you very much.
You can read Abizer’s blog here. If you’re ever in London, you can find him at NSCoder Night.
Receive news and updates from Realm straight to your inbox