Please introduce yourself
I’m Rebecca Skinner, and I’m the author of Effective Haskell. I’ve been writing Haskell for over ten years now. For the last two and a half years or so I’ve been volunteering on the Haskell.org committee, which is the group that helps manage the Haskell.org website and some of the Haskell infrastructure. I suppose you could say that I’m really enthusiastic about Haskell.
For people who are not familiar with the Haskell language, how would you introduce it to them?
Haskell is a pure functional programming language that puts a strong emphasis on letting programmers build precise abstractions to represent particular problem domains. Haskell has a lightweight and flexible syntax. Combined with lazy evaluation and a strong type system, Haskell is particularly well suited for building APIs and designing systems that need to capture the complexities of different technical or business domains and present them to users in a way that is both easy to use and hard to misuse.
You started off by saying that Haskell is a pure functional language. So could you get into that a little bit and talk about how that affects coding?
Although functional programming has a reputation for being a very high-level abstract way of thinking about programs, in a lot of ways it leads us to a more concrete and direct way of thinking about architecture and how we solve problems. In an object-oriented language, programming can be very indirect. If you want some specific output, you need to call a method on one object that sends a message to another object and so on. Abstraction in an object-oriented language generally involves adding more layers of indirection through objects and interfaces. Functional programming frequently makes the abstraction more direct because there’s a focus on composing smaller pieces of functionality on the fly. You can start to see this when you use functional features in non-functional languages. For example, using higher-order functions like map encourages you to directly write a lot of small functions that do one particular transformation on an element and then pass them to the map function. In a more traditional OO language, where composition is harder, you’re often faced with the choice to predict all of the different operations you might want to do on your data ahead of time, or else you need to add design patterns like factories and adapters that work around the lack of composition. Those design patterns can make it hard to follow what your program is doing.
While most programmers have had some exposure to the idea of functional programming in their languages thanks to the new features that are getting added, people are often a bit less comfortable with the idea of pure functional programming. In a pure functional language, all of our values are immutable and we avoid any arbitrary external side effects, like printing things to the screen or talking to the network. At first this can seem really limiting, because it focuses on all of the things that we can’t do, but in practice it turns out to be extraordinarily useful when we are working in a functional language because it means that we can combine code freely without ever having to worry about accidental or unintended side effects. We’re also not totally prohibited from having side effects when we need them, they just aren’t the default. In Haskell we already have a lot of really nice ways to combine smaller parts of our program into a larger program, and we reuse these same capabilities to let us manage side effects. In Haskell, if you want to do something like open a file or make an HTTP request, you create an IO action that will do that thing for you when the program runs. You can combine all of these little IO actions into one big one, and that big IO action that you created is what does all of the work when you actually run your program.
Could you discuss how Haskell APIs promote safety?
A Haskell API really is just describing and presenting a surface for someone to interact with our code. The difference is that Haskell gives us a lot of flexibility so that we can be very precise about how we describe that interface, and it lets us limit the interactions so that people can’t use our program incorrectly. Pure functional programming is a great tool for this, because we can design an API where we can very precisely control what inputs a user needs to accept and what outputs they need to return. When we know that code the user is passing in won’t have any side effects, it makes it easier to have confidence in how our code will be used. For example, if I’m building an API to interact with a system that requires authentication and has security policies in place, I can ask the user to pass me pure functions, and I know that any private information I pass into those functions won’t leak out, because the function can’t have any side effects.
How can Haskell help you with asynchronous code?
There are a couple of different ways that we deal with asynchronous programming In Haskell. The async library is one way we can do asynchronous programming, and it’s a great example of the point I was making earlier about how Haskell’s purity and type system can help us design APIs that make it easier to do resource management. The async library lets us write asynchronous code without worrying about leaking resources and forgetting to wait on threads, and it does it by treating the threads as the resource that the library is managing. When we use the library, we can say, “Hey, run this code asynchronously while you also run this other code,” and the library will create a new thread and pass it in as an argument to your function. You can do anything you might need to do, like sending a signal to the thread or canceling it, but it’s only in scope while your function is running and it will be cleaned up for you automatically.
The STM library is another example of a really great library that makes it easy for us to deal with asynchronous code in a way that feels familiar to anyone who has done async programming in other languages, but we get some really nice extra properties from STM compared to the usual synchronization primitives. STM stands for “software-transactional memory” and it makes it easy to write small individual parts of a program that each deal with some concurrency, and we can compose those effectively into a larger functions without any individual part of the application needing to know how the other parts of the program are handling their own concurrency.
When I talk about the ways that Haskell’s type system can help us write nicer APIs, this is another great example. Thanks to higher-kinded types, we can define these really broadly useful libraries that work with all different sorts of things that we might want to combine like this. We call them Applicatives and Monads. Haskell itself also has some special syntax called “do notation” that makes it easier to work with Monads. The underlying features that Haskell has for dealing with asynchronous programming are nice, but the fact that we can reuse so many useful libraries when we’re working with asynchronous code is an even bigger benefit.
Is this similar to the notion of generics or protocols in other languages?
Yeah, exactly. If we’re talking about generics in a language like Java, we have generics that let us write a value like "a list of type
T" where we can make T a string or number or another list. We can do that in Haskell too, but we can go a bit further and instead of being specific about having a list, we can make that generic as well. You can kind of get there with interfaces, but the higher-kinded types give you a lot of expressive power that you just don’t have in other languages.
How do you interact from Haskell with, for example, a C library or Java libraries, or just external libraries, so that you can take advantage of that code? Either directly, or by building some interop bridge?
Haskell has very good support for interoperating with the C ABIs. It’s pretty straightforward to use C libraries from Haskell, and a lot of other languages support the C calling convention, so we can generally interoperate with most other natively compiled languages like C++, Rust, and Go either directly or with only some minor modifications or a small shim. We can go the other direction too, and create native libraries from Haskell that can be used in other languages. There are also tools like C2HS that let you automatically generate language bindings for Haskell from larger C applications, and there are a number of projects that let you compile Haskell code into other languages.
How can people keep track of what you’re doing and follow you?
My website is https://rebeccaskinner.net, and I’m often active on Twitter at cercerilla. For anyone who is reading Effective Haskell, I’m also active on the DevTalk forum, where I love getting feedback and answering questions about the book.
Thank you for taking the time to speak with us. This has been great!