On the joy, and occasional value, of linear comonoids

Arnaud Spiwack

Comonoid (1/2)

δ:CCC\delta : C \longrightarrow C \otimes C
\delta : C \longrightarrow C \otimes C
ε:C1\varepsilon : C \longrightarrow \mathbf{1}
\varepsilon : C \longrightarrow \mathbf{1}

Comonoid (2/2)

Associativity

Neutral element

Theorem

The reader functor (E)(E\rightarrow) is a monad iff EE is a comonoid

Also theorem

When \otimes is a Cartesian product, then there exists a unique comonoid on each object.

A comonoid

δ:NP(N×N)δ n={(p,q)n=p+q}\begin{array}{lcl} \delta &:& \mathbb{N}\longrightarrow\mathscr{P}(\mathbb{N}\times\mathbb{N})\\ \delta\ n &=& \{ (p,q)\,|\,n = p+q \} \end{array}
\begin{array}{lcl} \delta &:& \mathbb{N}\longrightarrow\mathscr{P}(\mathbb{N}\times\mathbb{N})\\ \delta\ n &=& \{ (p,q)\,|\,n = p+q \} \end{array}
ε:NP({})ε n={n=0}\begin{array}{lcl} \varepsilon &:& \mathbb{N}\longrightarrow\mathscr{P}(\{*\})\\ \varepsilon\ n &=& \{ *\,|\,n=0 \} \end{array}
\begin{array}{lcl} \varepsilon &:& \mathbb{N}\longrightarrow\mathscr{P}(\{*\})\\ \varepsilon\ n &=& \{ *\,|\,n=0 \} \end{array}

Category of sets and relations

Comonoid species #1

Interference

Linear Haskell

a %1-> 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 %1-> 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

Linear types have (non-trivial) comonoids

In linear-base

class Consumable a where
  consume :: a %1 -> ()
  
class Consumable a => Dupable a where
  dup2 :: a %1 -> (a, a)

(in truth: more methods)

Comonoid: reference counting

Comonoid species #2

Hidden shared state

Comonoid: LVars (kind of)

class Lattice a where
  top :: a
  sup :: a -> a -> a


data L
instance Lattice L
data LV ≈ IORef L
δ r ≈ (r, r)
ε r ≈ ()

shout :: LV %1 -> L -> ()
shout r l ≈ modifyIORef r (sup l)

Can't read back 🙁 . Blocking operations impossible(?) in pure code

Fixing LVars

fix :: (LV %1 -> a) -> (L, a)
f :: LV %1 -> a

Computes a monotonic function in a lattice (+ some value)

Iterates f until a fixpoint

(conditions may apply)

Variation: union-find

data UF ≈ <some mutable union-find data structure>
δ r ≈ (r, r)
ε r ≈ ()

union :: UF %1 -> A -> A -> ()

find :: UF %1 -> Ur A

We may still take a fixed point, but  union-find is not necessarily finite-height, so it can diverge.

It'll works if A is a finite type.

Commutative monoids

class Monoid a
  => Commutative a where
  -- a <> b = b <> a

data M
instance Commutative M
data W ≈ IORef M
δ r ≈ (r, r)
ε r ≈ ()

shout :: W %1 -> M -> ()
shout r a ≈ modifyIORef r (<> a)

No fixed-point, no reads. Resembles map-reduce.

Non-commutative monoids

Revisiting reads

We might as well remember all the intermediate values

a₀
a₀ <> a₁
a₀ <> a₁ <> a₂
a₀ <> a₁ <> a₂ <> a₃
read :: W %1 -> Ur M

Needs more. What exactly?

It seems to work in a monad 🤢

M-set

():X×MXx1=xx(m1m2)=(xm1)m2\begin{array}{lcl} (\bullet) &:& X\times M \longrightarrow X\\ x\bullet 1 &=& x\\ x\bullet (m_1 \cdot m_2) &=& (x \bullet m_1) \bullet m_2 \end{array}
\begin{array}{lcl} (\bullet) &:& X\times M \longrightarrow X\\ x\bullet 1 &=& x\\ x\bullet (m_1 \cdot m_2) &=& (x \bullet m_1) \bullet m_2 \end{array}

A structure which can accumulate Ms, can accumulate Xs directly. Optimisation!

State is an M-set

instance MSet (Endo s) s where
  s • (Endo f) = f s

So we could encode the (non-linear) state monad as a comonoid, if we can solve the pesky read issue.

Toward better comonads

newtype Act w = Act (forall r. w r -> r)

instance Comonad w => Semigroup (Act w) where
  (Act a) <> (Act b) = Act $ b . extend a
  
instance Comonad w => Monoid (Act w) where
  mempty = Act extract
  
instance MSet (Act w) w where
  s • (Act a) = extend a s

Question: is

WAct w %1 -> a
\approx
\approx
w a

?

On the joy, and occasional value, of linear comonoids

By Arnaud Spiwack

On the joy, and occasional value, of linear comonoids

Imagine you want to write a data type for an abstract syntax tree with binders. You get started, write a function for substitution. Get lost in the renaming story. Ok. You've heard about this de Bruijn index stuff. You get started. But you get lost again in the shifts and the lifts. Wouldn't it be nice if types could help you get all of this binder story right? After all, you're writing Haskell. Arnaud will present a way to do just that: it's like de Bruijn indices, but with types. It also makes it possible to extend types-with-binders piecewise in the same style as the data type à la carte. Interestingly α-equivalent terms are equal in this representation, which is nice if you want to store the terms in tables, or use memoisation. And it all uses algebra! After all, you're writing Haskell. This story involves functors of functors (not a typo), higher catamorphisms, and Generic1 instances (not a typo either), as well as a brand new feature from GHC 8.6: quantified constraint.

  • 157