At the time of our first post on linear types, we were fresh out of the design phase to extend GHC with linear types. We had a prototype implementation, but it was just a proof of concept: there was precious little you could do with it.
A few months down the line, we are now in a very different place. We have pushed out a new paper, rewritten top to bottom. This one is chock-full of example applications of linear types. Today and in my next post in this series on linear types, I’d like to touch on two nifty uses of linear types from the paper.
But before I get started - I want to point out some recent progress on
the linear-types
branch in GHC. This branch is now
a usable playground to experiment with linear types. Most of the basic
features are there, but do expect some very rough edges and keep in
mind that error messages still need to be improved. We provide
a ready-made Docker image, so you don’t have to compile GHC yourself
just to play with linear types. It’s all in the README
: check it
out!
I/O states, in your types
Say you want to communicate across a network. You use
the socket library. You open the documentation and find that to
use a TCP socket, on a server, you first need to bind
the socket to an
address. At this point the socket isn’t doing anything: you need to
listen
for incoming traffic. Now the socket receives messages from
the network, specifically connection requests, which you must
accept
. An accept
call returns a new socket which can receive
a
TCP stream.
That’s a bit of a headache! And you’ve got no safety net: the
(simplified) type of bind
is:
bind :: Socket -> SocketAddress -> IO ()
The type of listen
is:
listen :: Socket -> IO ()
These types are really not that helpful. In Haskell, we like our types to
tell us what we can do with a value. But when I have a Socket
, I can
maybe bind
it or listen
to it, but certainly not both.
What we really need is that the type of a socket be indexed by the kind of things we can do with it. Something along the lines of:
data State = Unbound | Bound | Listening | …
data Socket (s :: State)
bind :: Socket Unbound -> IO ()
listen :: Socket Bound -> IO ()
…
Good! Now, what is the type of s
in bind s
? It must be Socket Unbound
so that I can apply bind
. Wait! When the bind
call
returns, s
must not be Unbound
anymore. It must be Bound
. So…
the type of s
seems to change over time. This is the idea
of typestates.
To implement a typestate for socket, maybe I could simply return a socket with its type changed like so:
bind :: Socket Unbound -> IO (Socket Bound)
do { s' <- bind s; … }
I can then use s'
as evidence that the socket has been
bound. But I also have the old s
still hanging about. And s
claims to
be unbound: we can go back in time. One has to be careful not to use
s
ever again to avoid ill effects: types have failed us once again.
What we need is the ability to consume the old s
, to make it
inaccessible. Which, coincidentally, is exactly what linear types
enable. We just need to make IO
a little bit more general, so that
it can return both linear and non-linear values:
data IOL p a
-- An old friend, redefined.
type IO = IOL ω
-- `a ->_1 b = a ⊸ b` and `a ->_ω b = a -> b`
return :: a ->_p IOL p a
(>>=) :: IO p a ⊸ (a ->_p IO q b) ⊸ IO q b
and we can now have sockets with typestates:
socket :: IOL 1 (Socket Unbound)
bind :: Socket Unbound ⊸ SocketAddress -> IOL 1 (Socket Bound)
listen :: Socket Bound ⊸ IOL 1 (Socket Listening)
…
What we’ve done here is precisely and safely captured the state of the socket at the type-level. The type tells you exactly what to do next with a socket. And GHC won’t let you reuse old states by accident. This is a prototypical example, demonstrating how linear types help us to be more precise about the use of resources, so that the type checker assists us in avoiding faulty resource use.
In the next post in the series, we’ll take a look at how linear types can pave the way towards safe zero-copy packed data exchange across clusters.
About the author
Arnaud is Tweag's head of R&D. He described himself as a multi-classed Software Engineer/Constructive Mathematician. He can regularly be seen in the Paris office, but he doesn't live in Paris as he much prefers the calm and fresh air of his suburban town.
If you enjoyed this article, you might be interested in joining the Tweag team.