Szukaj…


Wprowadzenie

TypeApplications to alternatywa dla adnotacji typu, gdy kompilator ma problemy z wnioskowaniem typów dla danego wyrażenia.

Ta seria przykładów wyjaśni cel rozszerzenia TypeApplications i sposób jego użycia

Nie zapomnij włączyć rozszerzenia, umieszczając {-# LANGUAGE TypeApplications #-} u góry pliku źródłowego.

Unikanie adnotacji typu

Używamy adnotacji typu, aby uniknąć dwuznaczności. Aplikacje typu mogą być używane w tym samym celu. Na przykład

x :: Num a => a
x = 5

main :: IO ()
main = print x

W kodzie występuje błąd niejednoznaczności. Wiemy, że a ma instancję Num , i aby ją wydrukować, wiemy, że potrzebuje instancji Show . To może działać, jeśli było, na przykład a Int , tak aby naprawić błąd możemy dodać typ adnotacji

main = print (x :: Int)

Inne rozwiązanie wykorzystujące aplikacje typu wyglądałoby tak

main = print @Int x

Aby zrozumieć, co to oznacza, musimy spojrzeć na podpis typu print .

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

Funkcja przyjmuje jeden parametr typu a , ale można na to spojrzeć inaczej, ponieważ w rzeczywistości zajmuje dwa parametry. Pierwszy to parametr typu , drugi to wartość, której typ jest pierwszym parametrem.

Główną różnicą między parametrami wartości a parametrami typu jest to, że te ostatnie są domyślnie dostarczane do funkcji, gdy je wywołujemy. Kto je zapewnia? Algorytm wnioskowania typu! To, TypeApplications pozwalają nam TypeApplications , to jawne podanie tych parametrów typu. Jest to szczególnie przydatne, gdy wnioskowanie o typie nie może określić poprawnego typu.

Aby rozbić powyższy przykład

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

Wpisz aplikacje w innych językach

Jeśli znasz języki takie jak Java, C # lub C ++ i pojęcie ogólne / szablony, to porównanie może być dla Ciebie przydatne.

Powiedzmy, że mamy ogólną funkcję w C #

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

Aby wywołać tę funkcję za pomocą DoNothing(5.0f) float , możemy zrobić DoNothing(5.0f) lub, jeśli chcemy być DoNothing(5.0f) , możemy powiedzieć DoNothing<float>(5.0f) . Ta część wewnątrz nawiasów kątowych to aplikacja typu.

W Haskell jest tak samo, z tym wyjątkiem, że parametry typu są niejawne nie tylko w witrynach wywołań, ale także w witrynach definicji.

doNothing :: a -> a
doNothing x = x

Można to również jednoznacznie określić za pomocą takich rozszerzeń ScopedTypeVariables , Rank2Types lub RankNTypes .

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

Następnie na stronie połączeń możemy ponownie napisać doNothing 5.0 lub doNothing @Float 5.0

Kolejność parametrów

Problem z niejawnymi argumentami typu staje się oczywisty, gdy mamy więcej niż jeden. W jakiej kolejności są?

const :: a -> b -> a

Czy zapisanie const @Int oznacza, że a jest równe Int , czy jest to b ? W przypadku, gdy jawnie określamy parametry typu za pomocą forall takiego jak const :: forall a b. a -> b -> a a następnie kolejność jest zapisana: a , a następnie b .

Jeśli nie, to kolejność zmiennych jest od lewej do prawej. Pierwsza zmienna, o której należy wspomnieć, to parametr pierwszego typu, druga to parametr drugiego typu i tak dalej.

Co jeśli chcemy określić zmienną drugiego typu, ale nie pierwszą? Możemy użyć symbolu wieloznacznego dla pierwszej takiej zmiennej

const @_ @Int

Typ tego wyrażenia to

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

Interakcja z niejednoznacznymi typami

Załóżmy, że wprowadzasz klasę typów o rozmiarze w bajtach.

class SizeOf a where
    sizeOf :: a -> Int

Problem polega na tym, że rozmiar powinien być stały dla każdej wartości tego typu. Nie faktycznie chcesz sizeOf funkcja zależy od , ale tylko na jego rodzaj. a

Bez aplikacji typu najlepszym rozwiązaniem, jakie mieliśmy, był typ Proxy zdefiniowany w ten sposób

data Proxy a = Proxy

Celem tego typu jest przenoszenie informacji o typie, ale bez informacji o wartości. Wtedy nasza klasa mogłaby wyglądać tak

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

Teraz możesz się zastanawiać, dlaczego nie porzucić pierwszego argumentu? Typem naszej funkcji byłby więc po prostu sizeOf :: Int lub, ściślej mówiąc, ponieważ jest to metoda klasy, sizeOf :: SizeOf a => Int lub jeszcze bardziej sizeOf :: forall a. SizeOf a => Int .

Problemem jest wnioskowanie typu. Jeśli piszę gdzieś sizeOf , algorytm wnioskowania wie tylko, że oczekuję Int . To nie ma pojęcia jaki typ chcę zastąpić . a Z tego powodu kompilator odrzuca definicję, chyba że włączone jest rozszerzenie {-# LANGUAGE AllowAmbiguousTypes #-} . W takim przypadku definicja się kompiluje, po prostu nie można jej użyć nigdzie bez błędu niejednoznaczności.

Na szczęście wprowadzenie aplikacji typu oszczędza dzień! Teraz możemy napisać sizeOf @Int , wyraźnie mówiąc, że a jest Int . Aplikacje typu pozwalają nam podać parametr typu, nawet jeśli nie pojawia się on w rzeczywistych parametrach funkcji !



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow