Argument przed %1-> musi zostać użyty dokładnie raz.
linearId :: a %1-> a
linearId x = xwontTypecheck :: a %1-> (a, a)
wontTypecheck x = (x, x)Próba użycia argumentu dwukrotnie spowoduje błąd kompilacji.
wontTypecheckEither :: a %1-> Int
wontTypecheckEither _ = 42Nieużycie argumentu -- tak samo.
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.
Funkcja z liniowym argumentem może zrobić dwie rzeczy:
printLinear :: String %1-> Linear.IO ()
printLinear s = print sNie zadziała! Zwykłe print nie jest liniowe.
Dlaczego tak jest?
Typechecker nie jest w stanie zagwarantować, że print użyje argumentu tylko raz.
Ż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 LIOTeraz możemy już skompilować coś takiego:
linearAdd :: Int %1-> Int %1-> Int
linearAdd = (+)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)Nie tylko funkcje mogą być liniowe:
{#- LANGUAGE GADTs #-}
data LinearWrapper a where
LinearWrapper :: a %1-> LinearWrapper alet, where i case konsumują swój argument więcej, niż raz (według typecheckera...)
wontCompile :: a %1-> a
wontCompile x = let y = x in yPattern matching wymaga sporej gimnastyki...
InvoiceDraft :: Ur String %1-> Ur Int %1-> Invoice DraftLinear 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