Haskell Language
Typade hål
Sök…
Anmärkningar
En av styrkorna med Haskell är förmågan att utnyttja typsystemet för att modellera delar av ditt problemdomän i typsystemet. Då möter man ofta mycket komplexa typer. När man skriver program med dessa typer (dvs med värden som har dessa typer) blir det ibland nästan obehagligt att ”jonglera” alla typerna. Från GHC 7.8 finns det en ny syntaktisk funktion som kallas typhål. Typade hål förändrar inte semantiken i kärnspråket; de är avsedda enbart som ett hjälpmedel för att skriva program.
För en fördjupad förklaring av skrivade hål, samt en diskussion om utformningen av typade hål, se Haskell wiki .
Avsnitt i GHC användarhandbok om typade hål .
Syntax för typade hål
Ett typat hål är ett enda understreck ( _
) eller en giltig Haskell-identifierare som inte är inom räckvidd, i ett uttryckssammanhang. Innan typ av hål existerar skulle båda dessa saker utlösa ett fel, så att den nya syntaxen inte stör någon gammal syntax.
Kontrollera beteende hos typade hål
Standardbeteendet för typade hål är att producera ett kompileringstidsfel när du stöter på ett typat hål. Det finns dock flera flaggor för att finjustera deras beteende. Dessa flaggor sammanfattas enligt följande ( GHC trac ):
Som standard har GHC skrivhål aktiverat och producerar ett kompileringsfel när det möter ett typat hål.
När
-fdefer-type-errors
eller-fdefer-typed-holes
-fdefer-type-errors
-fdefer-typed-holes
är aktiverat konverteras hålfel till varningar och resulterar i körtidsfel vid utvärdering.Varningsflaggan
-fwarn-typed-holes
är som standard på. Utan-fdefer-type-errors
eller---fdefer-typed-holes
denna flagga en no-op, eftersom skrivade hål är ett fel under dessa förhållanden. Om någon av uppskjutningsflaggorna är aktiverade (omvandla fel i-fno-warn-typed-holes
till varningar)-fno-warn-typed-holes
typhål varningarna. Detta innebär att sammanställningen lyckas tyst och att utvärdering av ett hål ger ett körtidfel.
Semantik av typhål
Värdet på ett typhål kan helt enkelt sägas vara undefined
, även om ett typat hål utlöser ett kompileringstidsfel, så det är inte strikt nödvändigt att tilldela det ett värde. Emellertid producerar ett typat hål (när de är aktiverade) ett kompileringstidsfel (eller varning med uppskjutna typfel) som anger namnet på det typade hålet, dess utdragna mest allmänna typ och typerna av lokala bindningar. Till exempel:
Prelude> \x -> _var + length (drop 1 x)
<interactive>:19:7: Warning:
Found hole `_var' with type: Int
Relevant bindings include
x :: [a] (bound at <interactive>:19:2)
it :: [a] -> Int (bound at <interactive>:19:1)
In the first argument of `(+)', namely `_var'
In the expression: _var + length (drop 1 x)
In the expression: \ x -> _var + length (drop 1 x)
Observera att när det gäller typade hål i uttryck som matats in i GHCi-svaret (som ovan) rapporterades också typen av uttryckt in som it
(här av typen [a] -> Int
).
Använd typ av hål för att definiera en klassinstans
Typade hål kan göra det enklare att definiera funktioner genom en interaktiv process.
Säg att du vill definiera en klassinstans Foo Bar
(för din anpassade Bar
typ för att använda den med någon polymorfisk biblioteksfunktion som kräver en Foo
instans). Du skulle nu traditionellt leta upp Foo
dokumentationen, ta reda på vilka metoder du behöver definiera, granska deras typer etc. - men med skrivna hål kan du faktiskt hoppa över det!
Definiera först en dummy-instans:
instance Foo Bar where
Kompilatorn kommer nu att klaga
Bar.hs:13:10: Warning:
No explicit implementation for
‘foom’ and ‘quun’
In the instance declaration for ‘Foo Bar’
Okej, så vi måste definiera foom
för Bar
. Men vad är det även tänkt att vara? Återigen är vi för lata för att titta i dokumentationen och fråga bara kompilatorn:
instance Foo Bar where
foom = _
Här har vi använt ett typat hål som en enkel "dokumentationsfråga". Kompilatorns utgångar
Bar.hs:14:10:
Found hole ‘_’ with type: Bar -> Gronk Bar
Relevant bindings include
foom :: Bar -> Gronk Bar (bound at Foo.hs:4:28)
In the expression: _
In an equation for ‘foom’: foom = _
In the instance declaration for ‘Foo Bar’
Lägg märke till hur kompilatorn redan har fyllt klassvariabeln med betongtypen Bar
som vi vill instansera den för. Detta kan göra signaturen mycket lättare att förstå än den polymorfa som finns i klassdokumentationen, särskilt om du har att göra med en mer komplicerad metod för t.ex. en klass med flera parametrar.
Men vad fan är Gronk
? Just nu är det förmodligen en bra idé att fråga Hayoo . Men vi kan fortfarande komma undan utan detta: som en blind gissning antar vi att detta inte bara är en typkonstruktör utan också konstruktören med ett enda värde, dvs att den kan användas som en funktion som på något sätt kommer att ge ett Gronk a
värde. Så vi försöker
instance Foo Bar where
foom bar = _ Gronk
Om vi har tur är Gronk
faktiskt ett värde, och kompilatorn kommer nu att säga
Found hole ‘_’
with type: (Int -> [(Int, b0)] -> Gronk b0) -> Gronk Bar
Where: ‘b0’ is an ambiguous type variable
Okej, det är fult - först Gronk
bara notera att Gronk
har två argument, så vi kan förfina vårt försök:
instance Foo Bar where
foom bar = Gronk _ _
Och det här är ganska tydligt:
Found hole ‘_’ with type: [(Int, Bar)]
Relevant bindings include
bar :: Bar (bound at Bar.hs:14:29)
foom :: Bar -> Gronk Bar (bound at Foo.hs:15:24)
In the second argument of ‘Gronk’, namely ‘_’
In the expression: Gronk _ _
In an equation for ‘foom’: foom bar = Gronk _ _
Du kan nu göra ytterligare framsteg genom att t.ex. dekonstruera bar
(komponenterna kommer då att visas, med typer, i avsnittet Relevant bindings
). Ofta är det vid någon tidpunkt helt uppenbart vad den rätta definitionen kommer att vara, för du ser alla tillgängliga argument och typerna passar ihop som ett pussel. Eller alternativt kan du se att definitionen är omöjlig och varför.
Allt detta fungerar bäst i en redaktör med interaktiv sammanställning, t.ex. Emacs med haskell-läge. Du kan sedan använda typade hål ungefär som mus-över-värde-frågor i en IDE för ett tolkat dynamiskt imperativspråk, men utan alla begränsningar.