Sök…


Introduktion

TypeApplications är ett alternativ till typanteckningar när kompilatorn kämpar för att dra slutsatser för ett visst uttryck.

Denna serie av exempel kommer att förklara syftet med tillägget TypeApplications och hur man använder det

Glöm inte att aktivera tillägget genom att placera {-# LANGUAGE TypeApplications #-} högst upp i din källfil.

Undvika anteckningar av typ

Vi använder typanteckningar för att undvika tvetydighet. Typapplikationer kan användas för samma ändamål. Till exempel

x :: Num a => a
x = 5

main :: IO ()
main = print x

Den här koden har ett tvetydighetsfel. Vi vet att a har en Num instans, och för att skriva ut den vet vi att den behöver en Show instans. Detta kan fungera om a till exempel var en Int , så för att fixa felet kan vi lägga till en typanteckning

main = print (x :: Int)

En annan lösning med användning av typapplikationer skulle se ut så här

main = print @Int x

För att förstå vad detta betyder måste vi titta på typsignaturen på print .

print :: Show a => a -> IO ()

Funktionen tar en parameter av typ a , men ett annat sätt att titta på den är att den faktiskt tar två parametrar. Den första är en parameter typ, är den andra ett värde vars typ är den första parametern.

Den största skillnaden mellan värdeparametrar och typparametrarna är att de senare tillförs implicit till funktioner när vi kallar dem. Vem tillhandahåller dem? Typinferensalgoritmen! Vad TypeApplications låter oss göra är att ge dessa typparametrar uttryckligen. Detta är särskilt användbart när typinferensen inte kan bestämma rätt typ.

Så för att dela upp ovanstående exempel

print :: Show a => a -> IO ()
print @Int :: Int -> IO ()
print @Int x :: IO ()

Skriv applikationer på andra språk

Om du känner till språk som Java, C # eller C ++ och konceptet med generik / mallar kan denna jämförelse vara användbar för dig.

Säg att vi har en generisk funktion i C #

public static T DoNothing<T>(T in) { return in; }

För att kalla denna funktion med en float vi göra DoNothing(5.0f) eller om vi vill vara tydliga kan vi säga DoNothing<float>(5.0f) . Den delen inuti vinkelfästarna är typapplikationen.

I Haskell är det samma, förutom att typparametrarna inte bara är implicita på samtalssidor utan också på definitionssidor.

doNothing :: a -> a
doNothing x = x

Detta kan också göras tydligt med antingen ScopedTypeVariables , Rank2Types eller RankNTypes tillägg som detta.

doNothing :: forall a. a -> a
doNothing x = x

På samtalssidan kan vi återigen antingen skriva doNothing 5.0 eller doNothing @Float 5.0

Parameterordning

Problemet med att typargument implicit blir uppenbart när vi har mer än ett. Vilken ordning kommer de in?

const :: a -> b -> a

Betyder att skriva const @Int betyder a är lika med Int , eller är det b ? Om vi uttryckligen anger typparametrarna med hjälp av en forall som const :: forall a b. a -> b -> a då ordningen är som skriven: a , sedan b .

Om vi inte gör det, är variabelns ordning från vänster till höger. Den första variabeln som ska nämnas är den första typen av parametrar, den andra är den andra typen av parametrar och så vidare.

Vad händer om vi vill ange den andra typvariabeln, men inte den första? Vi kan använda ett jokertecken för den första variabeln som den här

const @_ @Int

Typen av detta uttryck är

const @_ @Int :: a -> Int -> a

Interaktion med tvetydiga typer

Anta att du introducerar en klass av typer som har en storlek i byte.

class SizeOf a where
    sizeOf :: a -> Int

Problemet är att storleken ska vara konstant för varje värde av den typen. Vi vill faktiskt inte att funktionen sizeOf ska bero på a , utan bara av den typen.

Utan typapplikationer var den bästa lösningen vi hade den Proxy typen som definierades så här

data Proxy a = Proxy

Syftet med denna typ är att bära typinformation, men ingen värdeinformation. Då kunde vår klass se ut så här

class SizeOf a where
    sizeOf :: Proxy a -> Int

Nu undrar du kanske, varför inte släppa det första argumentet helt och hållet? Typen av vår funktion skulle då bara vara sizeOf :: Int eller, för att vara mer exakt eftersom det är en metod för en klass, sizeOf :: SizeOf a => Int eller för att vara ännu mer uttrycklig sizeOf :: forall a. SizeOf a => Int .

Problemet är typinferens. Om jag skriver sizeOf någonstans, vet inferensalgoritmen bara att jag förväntar mig en Int . Det har ingen aning om vilken typ jag vill ersätta a . På grund av detta avvisas definitionen av kompilatorn om du inte har {-# LANGUAGE AllowAmbiguousTypes #-} aktiverat. I så fall kan definitionen sammanställas, den kan bara inte användas någonstans utan ett tvetydighetsfel.

Lyckligtvis sparar introduktionen av typapplikationer dagen! Nu kan vi skriva sizeOf @Int , uttryckligen säga att a är Int . Typapplikationer tillåter oss att tillhandahålla en typparameter, även om den inte visas i funktionens faktiska parametrar !



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow