Zoeken…


Invoering

TypeApplications zijn een alternatief voor aantekeningen typen wanneer de compiler strijd te typen afleiden voor een bepaalde expressie.

Deze reeks voorbeelden legt het doel van de TypeApplications extensie uit en hoe deze te gebruiken

Vergeet niet om de extensie in te schakelen door {-# LANGUAGE TypeApplications #-} bovenaan uw bronbestand te plaatsen.

Type-annotaties vermijden

We gebruiken type-annotaties om dubbelzinnigheid te voorkomen. Type-applicaties kunnen voor hetzelfde doel worden gebruikt. Bijvoorbeeld

x :: Num a => a
x = 5

main :: IO ()
main = print x

Deze code bevat een dubbelzinnigheidsfout. We weten dat a een Num exemplaar heeft en om het af te drukken weten we dat het een Show exemplaar nodig heeft. Dit zou kunnen werken als a bijvoorbeeld een Int , dus om de fout te verhelpen, kunnen we een type-annotatie toevoegen

main = print (x :: Int)

Een andere oplossing met typetoepassingen zou er zo uitzien

main = print @Int x

Om te begrijpen wat dit betekent, moeten we kijken naar de typeaanduiding van print .

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

De functie heeft één parameter van type a , maar een andere manier om ernaar te kijken is dat er eigenlijk twee parameters nodig zijn. De eerste is een parameter type , de tweede is een waarde waarvan het type de eerste parameter is.

Het belangrijkste verschil tussen waardeparameters en de typeparameters is dat de laatste impliciet worden verstrekt aan functies wanneer we ze aanroepen. Wie biedt hen? Het type inferentie-algoritme! Wat TypeApplications ons laten doen, is die TypeApplications expliciet geven. Dit is vooral handig als de type-inferentie niet het juiste type kan bepalen.

Dus om het bovenstaande voorbeeld op te splitsen

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

Typ applicaties in andere talen

Als u bekend bent met talen zoals Java, C # of C ++ en het concept van generieke / sjablonen, dan is deze vergelijking misschien nuttig voor u.

Stel dat we een generieke functie hebben in C #

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

Om deze functie met een float te roepen, kunnen we DoNothing(5.0f) of als we expliciet willen zijn, kunnen we DoNothing<float>(5.0f) . Dat deel binnen de punthaken is de typetoepassing.

In Haskell is het hetzelfde, behalve dat de typeparameters niet alleen impliciet zijn op aanroepsites, maar ook op definitiesites.

doNothing :: a -> a
doNothing x = x

Dit kan ook expliciet worden gemaakt met behulp van deze ScopedTypeVariables , Rank2Types , Rank2Types of RankNTypes .

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

Vervolgens kunnen we op de oproepsite opnieuw doNothing 5.0 of doNothing @Float 5.0

Volgorde van parameters

Het probleem met impliciete typeargumenten wordt duidelijk als we er meer dan één hebben. In welke volgorde komen ze binnen?

const :: a -> b -> a

Betekent het schrijven van const @Int dat a gelijk is aan Int of is het b ? In het geval dat we expliciet de typeparameters vermelden met een forall like const :: forall a b. a -> b -> a dan is de volgorde zoals geschreven: a , dan b .

Als we dat niet doen, is de volgorde van variabelen van links naar rechts. De eerste variabele die moet worden genoemd, is de parameter van het eerste type, de tweede is de parameter van het tweede type, enzovoort.

Wat als we de tweede type variabele willen specificeren, maar niet de eerste? We kunnen een jokerteken gebruiken voor de eerste variabele zoals deze

const @_ @Int

Het type van deze uitdrukking is

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

Interactie met dubbelzinnige types

Stel dat u een klasse typen introduceert met een grootte in bytes.

class SizeOf a where
    sizeOf :: a -> Int

Het probleem is dat de grootte voor elke waarde van dat type constant moet zijn. We willen eigenlijk niet dat de functie sizeOf afhankelijk is van a , maar alleen van het type.

Zonder typetoepassingen was de beste oplossing die we hadden het Proxy type dat als volgt is gedefinieerd

data Proxy a = Proxy

Het doel van dit type is om type-informatie te dragen, maar geen waarde-informatie. Dan zou onze klas er zo uit kunnen zien

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

Nu vraag je je misschien af, waarom zou je het eerste argument niet helemaal laten vallen? Het type van onze functie zou dan gewoon sizeOf :: Int of, om precies te zijn omdat het een methode van een klasse is, sizeOf :: SizeOf a => Int of nog explicieter sizeOf :: forall a. SizeOf a => Int .

Het probleem is type-gevolgtrekking. Als ik ergens ' sizeOf schrijf, weet het inferentie-algoritme alleen dat ik een Int verwacht. Het heeft geen idee welk type ik wil vervangen door a . Hierdoor wordt de definitie door de compiler afgewezen, tenzij u de extensie {-# LANGUAGE AllowAmbiguousTypes #-} ingeschakeld. In dat geval wordt de definitie gecompileerd, deze kan gewoon nergens worden gebruikt zonder een dubbelzinnigheidsfout.

Gelukkig bespaart de introductie van typetoepassingen de dag! Nu kunnen we sizeOf @Int schrijven en expliciet zeggen dat a Int . Met typetoepassingen kunnen we een typeparameter opgeven, zelfs als deze niet voorkomt in de werkelijke parameters van de functie !



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow