Linear Haskell

Arnaud Spiwack

Joint with: Jean-Philippe Bernardy, Richard Eisenberg, Csongor Kiss, Ryan Newton, Simon Peyton Jones, Nicolas Wu

Prolific literature…

… but not much delivery

That's about it

… until GHC 9.0

Linear Haskell

 a ⊸ b

Since GHC 9.0

{-# LANGUAGE LinearTypes #-}

Completely normal Haskell + an extra type

(+ stuff for polymorphism)

(but we won't talk about it today)

Consume exactly once

f :: A ⊸ B
 f u
 u

If            is consumed exactly once
then      is consumed exactly once

What does “consume exactly once” mean?

evaluate x

apply x and consume the result exactly once

decompose x and consume both components exactly once

Base type

Function

Pair

Linear types, by examples (1/3)

id x = x

linear

dup x = (x,x)

not linear

swap (x,y) = (y,x)

linear

forget x = ()

not linear

Linear types, by examples (2/3)

f (Left x) = x
f (Right y) = y

linear

linear

not linear

h x b = case b of
  True -> x
  False -> x
g z = case z of
  Left x -> x
  Right y -> y
k x b = case b of
  True -> x
  False -> ()

linear

Linear types, by examples (3/3)

f x = dup x

linear

not linear

h u = u 0
g x = id (id x)
k u = u (u 0)

linear

not linear

Application class 1

Making more things pure

Example: safe mutable arrays

Mutable arrays: the ST way

array :: Int -> [(Int,a)] -> Array a
array size pairs = runST $ do
  fma <- newMArray size
  forM pairs (write ma)
  return (unsafeFreeze ma)
newMArray    :: Int -> ST s (MArray s a)
read         :: MArray s a -> Int -> ST s a
write        :: MArray s a -> (Int, a) -> ST s ()
unsafeFreeze :: MArray s a -> ST s (Array a)
forM         :: Monad m => [a] -> (a -> m ()) -> m ()
runST        :: (∀s. ST s a) -> a

Allocate

Fill

Freeze

 unsafeFreeze

                      is unsafe!

The same, in Linear Haskell

array :: Int -> [(Int,a)] -> Array a
array size pairs = newMArray size $ \ma ->
  freeze (foldl write ma pairs)
newMArray :: Int -> (MArray a ⊸ Ur b) ⊸ Ur b
write     :: MArray a ⊸ (Int,a) -> MArray a
read      :: MArray a ⊸ Int -> (MArray a, Ur a)
freeze    :: MArray a ⊸ Ur (Array a)
foldl     :: (a ⊸ b ⊸ a) -> a ⊸ [b] ⊸ a

Allocate

Fill

Freeze (safe!)

Scope passing style

newMArray :: Int -> (MArray a ⊸ Ur b) ⊸ Ur b

This is what ensures that references to arrays are unique

Unrestricted

data Ur a where
  Ur :: a -> Ur a

compare with

data Id a where
  Id :: a ⊸ Id a

Data types are linear by default

Scope passing style (continued)

newMArray :: Int -> (MArray a ⊸ Ur b) ⊸ Ur b

Don't work:

newMArrayDirect :: Int ⊸ MArray a
newMArrayLeaky :: Int -> (MArray a ⊸ b) ⊸ b

If the result is consumed exactly once
then the argument is consumed exactly once

Remember

Application class 2

Protocols in types

Example: files

I/O protocols

Files

  • ensure you close a file
  • ensure no read after close

Malloc

  • ensure you free a block
  • ensure no read after close

Sockets

  • ensure bind a socket before reading from it
  • ensure you close it
  • ensure you don’t read or bind after close

Files

openFile  :: FilePath -> IOL Handle
readLine  :: Handle ⊸ IOL (Handle, Ur String)
closeFile :: Handle ⊸ IOL ()
firstLine :: FilePath -> IOL (Ur String)
firstLine fp = do
  h <- openFile fp
  (h, Ur xs) <- readLine h
  closeFile h
  return $ Ur xs

Monads already have scope

do { x <- u ; v} = u >>= \x -> v
(>>=) :: IOL a ⊸ (a ⊸ IOL b) ⊸ IOL b

Application (class) 3

Circuits and such

Drawing circuits with linear types

let f (a,b) =
  let (x,y,z) = 𝜙 a in
  let (t,w) = 𝜔 (y,b) in
  (𝜉 (x,t), 𝜁 (z,w))
in
decode f

https://slides.com/aspiwack/loria202205

https://www.tweag.io/blog/tags/linear-types/

\chi

https://arxiv.org/abs/1710.09756

\chi

https://arxiv.org/abs/2103.06195

\chi

https://arxiv.org/abs/2103.06127

Linear Haskell

By Arnaud Spiwack

Linear Haskell

Since 2016, I’ve been leading the effort to supplement the functional programming language Haskell with linear typing (in the sense of linear logic). That is you can write a type of functions which are allowed to use their arguments just once. The first iteration of this was released as part of GHC 9.0. This may seem like a curious property to require of a function. Originally, linear logic was motivated by proof-theoretic consideration. At first it appeared as a natural decomposition of the coherence-space models of classical logic, but it does have far reaching proof-theoretical considerations. I’m one to take the connection between proof theory and programming languages (the Curry-Howard correspondence) quite seriously. Linear logic has almost immediately been seen, from a programming language standpoint, as giving a way to model resources in types. But what this concretely means is not super clear. In this talk I will describe the sort of practical benefits that we expect from linear types in Haskell today. They are, in particular, related to Rust’s ownership typing, though I don’t know whether I will have time to explain this in detail. At any rate, I am not going to spoil the entire talk here. I’d do a bad job of it in these handful of lines, anyway.

  • 568