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?
I think most programmers these days have exposure to functional programming. Newer languages like Rust, Swift, and Kotlin have taken a lot of inspiration from functional languages, including Haskell. Thereās also a pretty big contingent of people in the JavaScript and TypeScript ecosystem who are enthusiastic about functional programming. Even C++ and Java have started to get more features inspired by functional programming. I think the most surprising thing when you move from an object-oriented or multi-paradigm language to a functional programming language is how much working in a functional language affects the way that you think about your program at the architectural level.
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.
Really, being able to compose functions is where I think Haskell shines whether those functions are directly needing to think about concurrency or not. If we think about a language like JavaScript that handles asynchronous code with promises, you see a pattern that comes up repeatedly where you need to combine promises into a bigger promise so that you can run more of the code asynchronously. In practice you can end up with a pretty large asynchronous program that gets run in the end and itās just going to, for example, call one API and then wait for the response and pass that response to the next API, wait for that response, and so on. It turns out that that is almost exactly the same problem you have when you think, āOkay, well, Iām reading a file from disk, and then Iām writing some of the file to the screen, and then Iām going to wait for user input.ā There are other examples that arenāt quite as obviously the same until you think about it: I have this function that might fail, but if it doesnāt, I want to take its output and pass it into this next function that might also fail, or I have this list of values and I want to take each one and pass it to a function that will return its own list.
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!