Linear Types

Po co?

  • Zarządzanie zasobami:
    • śledzenie alokacji pamięci
    • operacje na plikach
    • tania serializacja
  • Modelowanie domeny:
    • niektóre operacje powinny wykonać się tylko raz
    • np. płatność za fakturę

TL;DR

Argument przed %1-> musi zostać użyty dokładnie raz.

linearId :: a %1-> a
linearId x = x
wontTypecheck :: a %1-> (a, a)
wontTypecheck x = (x, x)

Próba użycia argumentu dwukrotnie spowoduje błąd kompilacji.

wontTypecheckEither :: a %1-> Int
wontTypecheckEither _ = 42

Nieużycie argumentu -- tak samo.

Sidebar

Gdyby zmienić:

Argument przed strzałką musi zostać użyty dokładnie raz

na:

Argument przed strzałką musi zostać użyty co najwyżej raz

dostajemy Affine Types, jak w Ruście.

Implikacje

Funkcja z liniowym argumentem może zrobić dwie rzeczy:

  • zwrócić go
  • przekazać do innej liniowej funkcji
printLinear :: String %1-> Linear.IO ()
printLinear s = print s

Nie zadziała! Zwykłe print nie jest liniowe.

 

Dlaczego tak jest?

Typechecker nie jest w stanie zagwarantować, że print użyje argumentu tylko raz.

Wytrych nr 1

Żeby móc zrobić cokolwiek, musimy używać liniowych wersji funkcji.

Dużo z nich jest w linear-base.

import           Prelude.Linear

import qualified Control.Functor.Linear   as Linear
import qualified System.IO.Linear         as LIO

Teraz możemy już skompilować coś takiego:

linearAdd :: Int %1-> Int %1-> Int
linearAdd = (+)

Wytrych nr 2

To, że argument jest liniowy, nie oznacza, że jego pola muszą zostać użyte tylko raz:

nonLinearDup :: Ur a %1-> (a, a)
nonLinearDup (Ur x) = (x, x)

Liniowe konstruktory

Nie tylko funkcje mogą być liniowe:

{#- LANGUAGE GADTs #-}


data LinearWrapper a where
  LinearWrapper :: a %1-> LinearWrapper a

Problemy

let, where i case konsumują swój argument więcej, niż raz (według typecheckera...)

wontCompile :: a %1-> a
wontCompile x = let y = x in y

Pattern matching wymaga sporej gimnastyki...

  InvoiceDraft  :: Ur String %1-> Ur Int %1-> Invoice Draft

Technikalia

Linear types dostępne są jako rozszerzenie Haskella od GHC 9.

{-# LANGUAGE LinearTypes #-}

Liniowe wersje popularnych funkcji znajdują się w linear-base.

 

Bardzo wygodnie jest używać również jednego z nowych rozszerzeń:

{#- LANGUAGE QualifiedDo #-}

Które pozwala nam używać liniowych wersji operatorów

z do-notation:

import qualified Control.Monad.Linear   as Linear
import qualified System.IO.Linear       as LIO


test :: a %-> a%-> LIO.IO ()
test x y = Linear.do
  process x
  process y

Linear Types

By ambrozyk

Linear Types

  • 209