Szukaj…


Uwagi

Te rozszerzenia językowe są zwykle dostępne podczas korzystania z kompilatora Glasgow Haskell (GHC), ponieważ nie są częścią zatwierdzonego raportu językowego Haskell 2010 . Aby skorzystać z tych rozszerzeń, należy poinformować kompilator za pomocą flagi lub umieścić program LANGUAGE przed słowem kluczowym module w pliku. Oficjalna dokumentacja znajduje się w sekcji 7 przewodnika dla użytkowników GCH.

Format programu LANGUAGE to {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-} . To jest dosłowny {-# po którym następuje LANGUAGE po którym następuje lista oddzielonych przecinkami rozszerzeń, a na końcu #-} . W jednym pliku może znajdować się wiele programów LANGUAGE .

MultiParamTypeClasses

Jest to bardzo popularne rozszerzenie, które umożliwia klasy typów z wieloma parametrami typu. Możesz myśleć o MPTC jako związku między typami.

{-# LANGUAGE MultiParamTypeClasses #-}

class Convertable a b where
    convert :: a -> b

instance Convertable Int Float where
    convert i = fromIntegral i

Kolejność parametrów ma znaczenie.

MPTC można czasem zastąpić rodzinami typów.

Elastyczne substancje

Zwykłe instancje wymagają:

All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.

Oznacza to, że na przykład podczas tworzenia instancji dla [a] nie można utworzyć instancji dla konkretnie [Int] .; FlexibleInstances rozluźnia, że:

class C a where

-- works out of the box
instance C [a] where

-- requires FlexibleInstances
instance C [Int] where

OverloadedStrings

Zwykle literały łańcuchowe w Haskell mają typ String (który jest aliasem typu dla [Char] ). Chociaż nie jest to problemem w przypadku mniejszych programów edukacyjnych, aplikacje w świecie rzeczywistym często wymagają bardziej wydajnego przechowywania, takiego jak Text lub ByteString .

OverloadedStrings po prostu zmienia typ literałów na

"test" :: Data.String.IsString a => a

Umożliwiając bezpośrednie przekazywanie ich do funkcji oczekujących takiego typu. Wiele bibliotek implementuje ten interfejs dla swoich łańcuchowych typów, w tym Data.Text i Data.ByteString, które zapewniają pewne korzyści czasowe i przestrzenne w porównaniu z [Char] .

Istnieją również pewne unikalne zastosowania OverloadedStrings takie jak te z biblioteki Postgresql-simple, która umożliwia pisanie zapytań SQL w podwójnych cudzysłowach, takich jak normalny ciąg, ale zapewnia ochronę przed niewłaściwą konkatenacją, znanym źródłem ataków wstrzykiwania SQL.

Aby utworzyć instancję klasy IsString , musisz fromString funkcję fromString . Przykład :

data Foo = A | B | Other String deriving Show

instance IsString Foo where
  fromString "A" = A
  fromString "B" = B
  fromString xs  = Other xs

tests :: [ Foo ]
tests = [ "A", "B", "Testing" ]

Ten przykład dzięki uprzejmości Lyndon Maydwell ( sordina na GitHub) znaleźć tutaj .

TupleSections

Rozszerzenie składniowe, które pozwala zastosować konstruktor krotki (który jest operatorem) w sposób przekrojowy:

(a,b) == (,) a b

-- With TupleSections
(a,b) == (,) a b == (a,) b == (,b) a

Krotki N.

Działa również dla krotek o arsenie większym niż dwa

(,2,) 1 3 == (1,2,3)

Mapowanie

Może to być przydatne w innych miejscach, w których używane są sekcje:

map (,"tag") [1,2,3] == [(1,"tag"), (2, "tag"), (3, "tag")]

Powyższy przykład bez tego rozszerzenia wyglądałby następująco:

map (\a -> (a, "tag")) [1,2,3]

UnicodeSyntax

Rozszerzenie, które pozwala używać znaków Unicode zamiast niektórych wbudowanych operatorów i nazw.

ASCII Unicode Użycie
:: ma typ
-> typy funkcji, lambdy, gałęzie case itp.
=> ograniczenia klasowe
forall wyraźny polimorfizm
<- do notacja
* rodzaj (lub rodzaj) typów (np. Int :: ★ )
>- notacja proc dla Arrows
-< notacja proc dla Arrows
>>- notacja proc dla Arrows
-<< notacja proc dla Arrows

Na przykład:

runST :: (forall s. ST s a) -> a

stanie się

runST ∷ (∀ s. ST s a) → a

Zauważ, że przykład * vs. jest nieco inny: ponieważ * nie jest zarezerwowany, działa również w ten sam sposób co * dla mnożenia lub jakiejkolwiek innej funkcji o nazwie (*) i odwrotnie. Na przykład:

ghci> 2 ★ 3
6
ghci> let (*) = (+) in 2 ★ 3
5
ghci> let (★) = (-) in 2 * 3
-1

BinaryLiterals

Standardowy Haskell pozwala pisać literały całkowite w systemie dziesiętnym (bez żadnego prefiksu), szesnastkowym (poprzedzonym 0x lub 0X ) i ósemkowym (poprzedzonym 0o lub 0O ). Rozszerzenie BinaryLiterals dodaje opcję binarną (poprzedzoną 0b lub 0B ).

0b1111 == 15     -- evaluates to: True

ExistentialQuantification

Jest to rozszerzenie systemu typów, które pozwala na typy, które są egzystencjalnie skwantyfikowane, lub innymi słowy, mają zmienne typu, które są tworzone tylko w czasie wykonywania .

Wartość typu egzystencjalnego jest podobna do odwołania do klasy abstrakcyjnej w językach OO: nie wiesz, jaki dokładnie zawiera typ, ale możesz ograniczyć klasę typów.

data S = forall a. Show a => S a

lub równoważnie ze składnią GADT:

{-# LANGUAGE GADTs #-}
data S where
   S :: Show a => a -> S

Egzystencjalne typy otwierają drzwi do takich rzeczy, jak prawie heterogeniczne pojemniki: jak powiedziano powyżej, w rzeczywistości mogą istnieć różne typy w wartości S , ale wszystkie z nich mogą być show n, dlatego możesz również zrobić

instance Show S where
    show (S a) = show a   -- we rely on (Show a) from the above

Teraz możemy stworzyć kolekcję takich obiektów:

ss = [S 5, S "test", S 3.0]

Co pozwala nam również wykorzystać zachowanie polimorficzne:

mapM_ print ss

Egzystencje mogą być bardzo potężne, ale zauważ, że w Haskell nie są one często konieczne. W powyższym przykładzie wszystko, co możesz faktycznie zrobić z instancją Show to show (duh!) Wartości, tj. Utworzenie reprezentacji ciągu. Cały typ S zawiera zatem dokładnie tyle informacji, ile ciąg, który otrzymujesz, gdy go wyświetlasz. Dlatego zwykle lepiej jest po prostu od razu zapisać ten ciąg, zwłaszcza że Haskell jest leniwy, a zatem i tak początkowo będzie to tylko nieoceniony zgrzyt.

Z drugiej strony, egzystencjalne powodują pewne unikalne problemy. Na przykład sposób, w jaki informacja o typie jest „ukryta” w egzystencjalnym. Jeśli dopasujesz wzorzec dla wartości S , będziesz mieć zawarty typ w zakresie (a dokładniej jego instancję Show ), ale ta informacja nigdy nie umknie jego zakresowi, który staje się więc trochę „tajnym stowarzyszeniem”: kompilatorem nie pozwala, aby cokolwiek wymknęło się poza zakres, z wyjątkiem wartości, których typ jest już znany z zewnątrz. Może to prowadzić do dziwnych błędów, takich jak Couldn't match type 'a0' with '()' 'a0' is untouchable .


Porównaj to ze zwykłym polimorfizmem parametrycznym, który jest zwykle rozwiązywany w czasie kompilacji (umożliwiając całkowite usunięcie typu).


Typy egzystencjalne różnią się od typów Rank-N - z grubsza mówiąc, rozszerzenia te są podwójnie względem siebie: aby faktycznie użyć wartości typu egzystencjalnego, potrzebujesz (prawdopodobnie ograniczonej) funkcji polimorficznej, takiej jak show w przykładzie. Funkcja polimorficzna jest uniwersalnie skwantyfikowana, tzn. Działa dla dowolnego typu w danej klasie, podczas gdy kwantyfikacja egzystencjalna oznacza, że działa dla określonego typu, który jest z góry nieznany. Jeśli masz funkcję polimorficzną, to wystarczy, jednak aby przekazać funkcje polimorficzne jako argumenty, potrzebujesz {-# LANGUAGE Rank2Types #-} :

genShowSs :: (∀ x . Show x => x -> String) -> [S] -> [String]
genShowSs f = map (\(S a) -> f a)

LambdaCase

Rozszerzenie składniowe, które pozwala na zapisanie \case zamiast \arg -> case arg of .

Rozważ następującą definicję funkcji:

dayOfTheWeek :: Int -> String
dayOfTheWeek 0 = "Sunday"
dayOfTheWeek 1 = "Monday"
dayOfTheWeek 2 = "Tuesday"
dayOfTheWeek 3 = "Wednesday"
dayOfTheWeek 4 = "Thursday"
dayOfTheWeek 5 = "Friday"
dayOfTheWeek 6 = "Saturday"

Jeśli chcesz uniknąć powtarzania nazwy funkcji, możesz napisać coś takiego:

dayOfTheWeek :: Int -> String
dayOfTheWeek i = case i of
    0 -> "Sunday"
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6 -> "Saturday"

Korzystając z rozszerzenia LambdaCase, możesz napisać to jako wyrażenie funkcyjne, bez konieczności nazywania argumentu:

{-# LANGUAGE LambdaCase #-}

dayOfTheWeek :: Int -> String
dayOfTheWeek = \case
    0 -> "Sunday"
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6 -> "Saturday"

RankNTypes

Wyobraź sobie następującą sytuację:

foo :: Show a => (a -> String) -> String -> Int -> IO ()
foo show' string int = do
   putStrLn (show' string)
   putStrLn (show' int)

Tutaj chcemy przekazać funkcję, która konwertuje wartość na String, zastosować tę funkcję zarówno do parametru string, jak i parametru int i wydrukować je oba. Moim zdaniem nie ma powodu, by to się nie udawało! Mamy funkcję, która działa na oba typy przekazywanych parametrów.

Niestety nie będzie to sprawdzać typu! GHC wywodzi się a typem w oparciu off z jego pierwszego wystąpienia w ciele funkcji. To znaczy, jak tylko trafimy:

putStrLn (show' string)

GHC wywnioskuje, że show' :: String -> String , ponieważ string jest String . Zacznie wysadzać w powietrze podczas próby show' int .

RankNTypes pozwala zamiast tego napisać podpis typu w następujący sposób, kwantyfikując wszystkie funkcje spełniające typ show' :

foo :: (forall a. Show a => (a -> String)) -> String -> Int -> IO ()

To jest polimorfizm Pozycja 2: jesteśmy twierdząc, że show' funkcja musi działać dla wszystkich a s w naszej funkcji, a poprzednia realizacja działa teraz.

Rozszerzenie RankNTypes pozwala na dowolne zagnieżdżanie wszystkich forall ... bloków w podpisach typów. Innymi słowy, pozwala na polimorfizm rangi N.

OverloadedLists

dodano w GHC 7.8 .

OverloadedLists, podobnie jak OverloadedStrings , pozwala na projektowanie literałów listy w następujący sposób:

[]          -- fromListN 0 []
[x]         -- fromListN 1 (x : [])
[x .. ]     -- fromList (enumFrom x)

Jest to przydatne, gdy mamy do czynienia z typami takimi jak Set , Vector i Map .

['0' .. '9']             :: Set Char
[1 .. 10]                :: Vector Int
[("default",0), (k1,v1)] :: Map String Int
['a' .. 'z']             :: Text

Klasa IsList w GHC.Exts jest przeznaczona do użytku z tym rozszerzeniem.

IsList jest wyposażony w jedną funkcję typu, Item i trzy funkcje, fromList :: [Item l] -> l , toList :: l -> [Item l] oraz fromListN :: Int -> [Item l] -> l gdzie fromListN jest opcjonalny. Typowe wdrożenia to:

instance IsList [a] where
  type Item [a] = a
  fromList = id
  toList   = id

instance (Ord a) => IsList (Set a) where
  type Item (Set a) = a
  fromList = Set.fromList
  toList   = Set.toList

Przykłady zaczerpnięte z OverloadedLists - GHC .

FunctionalDependencies

Jeśli masz wieloparametrową klasę typu z argumentami a, b, c i x, to rozszerzenie pozwala wyrazić, że typ x można jednoznacznie zidentyfikować na podstawie a, b i c:

class SomeClass a b c x | a b c -> x where ...

Podczas deklarowania instancji takiej klasy zostanie ona sprawdzona względem wszystkich innych instancji, aby upewnić się, że funkcjonalna zależność zachowuje, to znaczy, że nie istnieje żadna inna instancja z tym samym abc ale innym x .

Możesz określić wiele zależności na liście oddzielonej przecinkami:

class OtherClass a b c d | a b -> c d, a d -> b where ...

Na przykład w MTL możemy zobaczyć:

class MonadReader r m| m -> r where ...
instance MonadReader r ((->) r) where ...

Teraz, jeśli masz wartość typu MonadReader a ((->) Foo) => a , kompilator może wywnioskować, że a ~ Foo , ponieważ drugi argument całkowicie określa pierwszy i odpowiednio uprości ten typ.

SomeClass można traktować jako funkcję argumentów abc które dają x . Takie klasy mogą być używane do wykonywania obliczeń w systemie typów.

GADT

Konwencjonalne algebraiczne typy danych mają charakter parametryczny. Na przykład, jeśli zdefiniujemy ADT jak

data Expr a = IntLit Int 
            | BoolLit Bool 
            | If (Expr Bool) (Expr a) (Expr a)

z nadzieją, że statycznie wykluczy to IntLit :: Int -> Expr a warunkowe, nie będzie to IntLit :: Int -> Expr a zgodnie z oczekiwaniami, ponieważ typ IntLit :: Int -> Expr a jest IntLit :: Int -> Expr a kwantyfikowany: dla każdego wyboru a , daje wartość typu Expr a . W szczególności dla a ~ Bool mamy IntLit :: Int -> Expr Bool , co pozwala nam zbudować coś takiego jak If (IntLit 1) e1 e2 co jest typem konstruktora If który próbował wykluczyć.

Uogólnione algebraiczne typy danych pozwalają nam kontrolować wynikowy typ konstruktora danych, dzięki czemu nie są one jedynie parametryczne. Możemy przepisać nasz typ Expr jako GADT w następujący sposób:

data Expr a where
  IntLit :: Int -> Expr Int
  BoolLit :: Bool -> Expr Bool
  If :: Expr Bool -> Expr a -> Expr a -> Expr a

W tym przypadku typ konstruktora IntLit to Int -> Expr Int , a więc IntLit 1 :: Expr Bool nie będzie sprawdzał typu.

Dopasowanie wzorca do wartości GADT powoduje uściślenie typu zwracanego terminu. Na przykład, możliwe jest napisanie ewaluatora dla Expr a podobnego do tego:

crazyEval :: Expr a -> a
crazyEval (IntLit x) = 
   -- Here we can use `(+)` because x :: Int
   x + 1 
crazyEval (BoolLit b) = 
   -- Here we can use `not` because b :: Bool
   not b
crazyEval (If b thn els) = 
  -- Because b :: Expr Bool, we can use `crazyEval b :: Bool`.
  -- Also, because thn :: Expr a and els :: Expr a, we can pass either to 
  -- the recursive call to `crazyEval` and get an a back
  crazyEval $ if crazyEval b then thn else els 

Zauważ, że jesteśmy w stanie używać (+) w powyższych definicjach, ponieważ gdy np. IntLit x jest zgodny ze wzorcem, uczymy się również, że a ~ Int (i podobnie dla not i if_then_else_ kiedy a ~ Bool ).

ScopedTypeVariables

ScopedTypeVariables pozwala odwoływać się do uniwersalnie skwantyfikowanych typów w deklaracji. Mówiąc dokładniej:

import Data.Monoid

foo :: forall a b c. (Monoid b, Monoid c) => (a, b, c) -> (b, c) -> (a, b, c)
foo (a, b, c) (b', c') = (a :: a, b'', c'')
    where (b'', c'') = (b <> b', c <> c') :: (b, c)

Ważne jest to, że możemy użyć a , b i c aby poinstruować kompilator o podwyrażeniach deklaracji (krotka w klauzuli where i pierwsza a w wyniku końcowym). W praktyce ScopedTypeVariables pomaga w pisaniu złożonych funkcji jako sumy części, pozwalając programiście dodawać podpisy typów do wartości pośrednich, które nie mają konkretnych typów.

PatternSynonimy

Synonimy wzorców to abstrakcje wzorców podobne do tego, jak funkcje są abstrakcjami wyrażeń.

W tym przykładzie przyjrzyjmy się interfejsowi Data.Sequence i zobaczmy, jak można go poprawić za pomocą synonimów wzorców. Typ Seq jest typem danych, który wewnętrznie wykorzystuje skomplikowaną reprezentację, aby osiągnąć dobrą asymptotyczną złożoność dla różnych operacji, w szczególności zarówno O (1) (nie) konsumującą i (nie) snocing.

Ale ta reprezentacja jest nieporęczna i niektórych jej niezmienników nie można wyrazić w systemie typów Haskella. Z tego powodu typ Seq jest udostępniany użytkownikom jako typ abstrakcyjny, wraz z niezmienniczymi funkcjami akcesora i konstruktora, między innymi:

empty :: Seq a

(<|) :: a -> Seq a -> Seq a
data ViewL a = EmptyL | a :< (Seq a)
viewl :: Seq a -> ViewL a

(|>) :: Seq a -> a -> Seq a 
data ViewR a = EmptyR | (Seq a) :> a 
viewr :: Seq a -> ViewR a

Ale korzystanie z tego interfejsu może być nieco kłopotliwe:

uncons :: Seq a -> Maybe (a, Seq a)
uncons xs = case viewl xs of
    x :< xs' -> Just (x, xs')
    EmptyL -> Nothing

Możemy użyć wzorców widoku, aby go trochę oczyścić:

{-# LANGUAGE ViewPatterns #-}

uncons :: Seq a -> Maybe (a, Seq a)
uncons (viewl -> x :< xs) = Just (x, xs)
uncons _ = Nothing

Korzystając z rozszerzenia języka PatternSynonyms , możemy zapewnić jeszcze lepszy interfejs, umożliwiając dopasowywanie wzorców, aby udawać, że mamy listę wad lub list snoc:

{-# LANGUAGE PatternSynonyms #-}
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq

pattern Empty :: Seq a
pattern Empty <- (Seq.viewl -> Seq.EmptyL)

pattern (:<) :: a -> Seq a -> Seq a
pattern x :< xs <- (Seq.viewl -> x Seq.:< xs)

pattern (:>) :: Seq a -> a -> Seq a
pattern xs :> x <- (Seq.viewr -> xs Seq.:> x)

To pozwala nam pisać uncons w bardzo naturalnym stylu:

uncons :: Seq a -> Maybe (a, Seq a)
uncons (x :< xs) = Just (x, xs)
uncons _ = Nothing

RecordWildCards

Zobacz RecordWildCards



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