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

Made with Slides.com