Поиск…


замечания

Эти языковые расширения обычно доступны при использовании компилятора Glasgow Haskell (GHC), поскольку они не являются частью утвержденного отчета о языке Haskell 2010 . Чтобы использовать эти расширения, нужно либо проинформировать компилятор, используя флаг, либо поместить программу LANGUAGE перед ключевым словом module в файле. Официальную документацию можно найти в разделе 7 руководства пользователя GCH.

Формат программы LANGUAGE - {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-} . Это буквальный {-# за которым следует LANGUAGE за которым следует список расширений, разделенных запятыми, и, наконец, закрытие #-} . Несколько программ LANGUAGE могут быть помещены в один файл.

MultiParamTypeClasses

Это очень распространенное расширение, которое позволяет использовать классы типов с несколькими параметрами типа. Вы можете думать о MPTC как о связи между типами.

{-# LANGUAGE MultiParamTypeClasses #-}

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

instance Convertable Int Float where
    convert i = fromIntegral i

Порядок параметров имеет значение.

Иногда MPTC можно заменить семействами типов.

FlexibleInstances

Регулярные экземпляры требуют:

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.

Это означает, что, например, пока вы можете создать экземпляр для [a] вы не можете создать экземпляр специально для [Int] .; FlexibleInstances расслабляет:

class C a where

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

-- requires FlexibleInstances
instance C [Int] where

OverloadedStrings

Обычно строковые литералы в Haskell имеют тип String (который является псевдонимом типа для [Char] ). Хотя это не проблема для небольших образовательных программ, в реальных приложениях часто требуется более эффективное хранилище, например Text или ByteString .

OverloadedStrings просто меняет тип литералов на

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

Предоставление им прямого доступа к функциям, ожидающим такого типа. Многие библиотеки реализуют этот интерфейс для своих строковых типов, включая Data.Text и Data.ByteString, которые обеспечивают определенные преимущества по времени и пространству по сравнению с [Char] .

Есть также некоторые уникальные применения OverloadedStrings подобные тем, что из простой библиотеки Postgresql, которая позволяет SQL-запросам записываться в двойных кавычках, таких как обычная строка, но обеспечивает защиту от неправильной конкатенации, пресловутого источника атак SQL-инъекций.

Чтобы создать экземпляр класса IsString вам необходимо внедрить функцию fromString . Пример †:

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" ]

Этот пример любезно Линдона Maydwell ( sordina на GitHub) нашел здесь .

TupleSections

Синтаксическое расширение, которое позволяет применять конструктор кортежа (который является оператором) в виде раздела:

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

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

N-кортежи

Он также работает для кортежей с арностью более двух

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

картографирование

Это может быть полезно в других местах, где используются разделы:

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

Вышеприведенный пример без этого расширения будет выглядеть так:

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

UnicodeSyntax

Расширение, позволяющее использовать символы Unicode вместо определенных встроенных операторов и имен.

ASCII Unicode Используйте (s)
:: имеет тип
-> типы функций, лямбда, ветви case и т. д.
=> ограничения класса
forall явный полиморфизм
<- do запись
* тип (или вид) типов (например, Int :: ★ )
>- proc для Arrows
-< proc для Arrows
>>- proc для Arrows
-<< proc для Arrows

Например:

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

станет

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

Обратите внимание, что пример * vs. немного отличается: поскольку * не зарезервирован, также работает так же, как * для умножения или любой другой функции с именем (*) и наоборот. Например:

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

BinaryLiterals

Стандартный Haskell позволяет записывать целочисленные литералы в десятичных (без префикса), шестнадцатеричные (с предшествующими 0x или 0X ) и восьмеричные (с предшествующими 0o или 0O ). Расширение BinaryLiterals добавляет возможность двоичного кода (которому предшествуют 0b или 0B ).

0b1111 == 15     -- evaluates to: True

ExistentialQuantification

Это системное расширение типа, которое позволяет типы, которые экзистенциально квантуются, или, другими словами, иметь переменные типа, которые получают только экземпляр во время выполнения .

Значение экзистенциального типа похоже на ссылку на абстрактный базовый класс в языках OO: вы не знаете точный тип в составе, но можете ограничить класс типов.

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

или эквивалентно, с синтаксисом GADT:

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

Экзистенциальные типы открывают дверь для вещей, таких как почти гетерогенные контейнеры: как сказано выше, на самом деле могут быть разные типы в значении S , но все они могут быть show n, следовательно, вы также можете сделать

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

Теперь мы можем создать коллекцию таких объектов:

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

Что также позволяет использовать полиморфное поведение:

mapM_ print ss

Экзистенциалы могут быть очень мощными, но обратите внимание, что они на самом деле не нужны очень часто в Haskell. В приведенном выше примере все, что вы действительно можете сделать с экземпляром Show - это показать (duh!) Значения, т. Е. Создать строковое представление. Таким образом, весь S тип содержит точно столько же информации, сколько строка, которую вы получаете при ее показе. Поэтому обычно лучше просто сохранить эту строку сразу, тем более, что Haskell ленив, и поэтому строка вначале будет только неоценимым thunk.

С другой стороны, экзистенции вызывают некоторые уникальные проблемы. Например, способ, которым информация типа «скрыта» в экзистенциальном. Если вы сопоставляете шаблон по значению S , у вас будет скрытый тип в области видимости (точнее, его экземпляр Show ), но эта информация никогда не сможет выйти из сферы действия, поэтому становится немного «секретным обществом»: компилятор не позволяет чему-либо избежать рамки, кроме значений, тип которых уже известен извне. Это может привести к появлению странных ошибок, таких как Couldn't match type 'a0' with '()' 'a0' is untouchable .


Контрастируйте это с обычным параметрическим полиморфизмом, который обычно разрешается во время компиляции (разрешая полное стирание типа).


Экзистенциальные типы отличаются от типов Rank-N - эти расширения, грубо говоря, двойственны друг к другу: чтобы фактически использовать значения экзистенциального типа, вам нужна (возможно, ограниченная) полиморфная функция, как show в примере. Полиморфная функция универсально квантифицирована, т. Е. Работает для любого типа в данном классе, тогда как экзистенциальная квантификация означает, что она работает для определенного типа, который является априорным неизвестным. Если у вас есть полиморфная функция, этого достаточно, однако, чтобы передавать полиморфные функции, такие как аргументы, вам нужно {-# LANGUAGE Rank2Types #-} :

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

LambdaCase

Синтаксическое расширение, которое позволяет вам писать \case вместо \arg -> case arg of .

Рассмотрим следующее определение функции:

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

Если вы хотите избежать повторения имени функции, вы можете написать что-то вроде:

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

Используя расширение LambdaCase, вы можете написать это как выражение функции, не указывая аргумент:

{-# LANGUAGE LambdaCase #-}

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

RankNTypes

Представьте себе следующую ситуацию:

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

Здесь мы хотим передать функцию, которая преобразует значение в строку, применить эту функцию как к параметру строки, так и к параметру int и распечатать их оба. На мой взгляд, нет причин, по которым это должно потерпеть неудачу! У нас есть функция, которая работает с обоими типами параметров, которые мы передаем.

К сожалению, это не будет проверять! GHC выводит a тип , основанный от его первого появления в теле функции. То есть, как только мы ударим:

putStrLn (show' string)

GHC выведет, что show' :: String -> String , так как string является String . Он будет продолжать взрываться, пытаясь show' int .

RankNTypes позволяет вместо этого писать сигнатуру типа следующим образом, количественно определяя все функции, которые удовлетворяют типу show' :

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

Это ранг 2 полиморфизм: Мы утверждаем , что show' функция должна работать для всех a е в нашей функции, а предыдущая реализация в настоящее время работает.

Расширение RankNTypes позволяет произвольно RankNTypes блоки forall ... в сигнатуры типов. Другими словами, он допускает полиморфизм ранга N.

OverloadedLists

добавлено в GHC 7.8 .

OverloadedLists, аналогичный OverloadedStrings , позволяет выводить литералы списков следующим образом:

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

Это удобно при работе с такими типами, как Set , Vector и Map s.

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

Класс IsList в GHC.Exts предназначен для использования с этим расширением.

IsList оснащен одной функцией типа, Item и тремя функциями: fromList :: [Item l] -> l , toList :: l -> [Item l] и fromListN :: Int -> [Item l] -> l где fromListN является обязательным. Типичными реализациями являются:

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

Примеры, взятые из OverloadedLists - GHC .

FunctionalDependencies

Если у вас есть многопараметрический тип-класс с аргументами a, b, c и x, это расширение позволяет вам выразить, что тип x может быть однозначно идентифицирован из a, b и c:

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

При объявлении экземпляра такого класса он будет проверяться на всех других экземплярах, чтобы убедиться, что функциональная зависимость выполняется, то есть не существует другого экземпляра с тем же abc но отличается x .

Вы можете указать несколько зависимостей в списке, разделенном запятыми:

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

Например, в MTL мы видим:

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

Теперь, если у вас есть значение типа MonadReader a ((->) Foo) => a , компилятор может сделать вывод, что a ~ Foo , так как второй аргумент полностью определяет первый и упростит тип соответственно.

Класс SomeClass можно рассматривать как функцию аргументов abc что приводит к x . Такие классы могут использоваться для выполнения вычислений в системе типов.

GADTs

Обычные алгебраические типы данных являются параметрическими в своих переменных типа. Например, если мы определим ADT как

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

с надеждой, что это будет статически исключать не-типизированные условные IntLit :: Int -> Expr a , это не будет вести себя так, как ожидалось, так как тип IntLit :: Int -> Expr a : для любого выбора a он производит значение типа Expr a . В частности, для a ~ Bool у нас есть IntLit :: Int -> Expr Bool , что позволяет нам построить что-то вроде If (IntLit 1) e1 e2 что и пыталось исключить тип конструктора If .

Обобщенные алгебраические типы данных позволяют нам управлять результирующим типом конструктора данных, чтобы они были не просто параметрическими. Мы можем переписать наш тип Expr как GADT следующим образом:

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

Здесь тип конструктора IntLit - Int -> Expr Int , и поэтому IntLit 1 :: Expr Bool не будет проверяться typecheck.

Согласование шаблонов по значению GADT вызывает уточнение типа возвращаемого термина. Например, можно написать оценщик для Expr a следующим образом:

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 

Обратите внимание, что мы можем использовать (+) в приведенных выше определениях, потому что, когда, например, IntLit x соответствует шаблону, мы также узнаем, что a ~ Int (а также для not и if_then_else_ когда a ~ Bool ).

ScopedTypeVariables

ScopedTypeVariables позволяет ссылаться на универсально квантифицированные типы внутри декларации. Чтобы быть более явным:

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)

Важно то, что мы можем использовать a , b и c чтобы проинструктировать компилятор в подвыражениях объявления (кортеж в предложении where и первый a в конечном результате). На практике ScopedTypeVariables помогает записывать сложные функции в виде суммы частей, позволяя программисту добавлять сигнатуры типов к промежуточным значениям, которые не имеют конкретных типов.

PatternSynonyms

Синонимы шаблонов - это абстракции шаблонов, аналогичные тем, как функции являются абстракциями выражений.

В этом примере давайте посмотрим на интерфейс Data.Sequence , и давайте посмотрим, как его можно улучшить с помощью синонимов шаблонов. Тип Seq - это тип данных, который внутренне использует сложное представление для достижения хорошей асимптотической сложности для различных операций, в первую очередь как O (1) (un) consing, так и (un) snocing.

Но это представление громоздко и некоторые его инварианты не могут быть выражены в системе типов Хаскелла. Из-за этого тип Seq подвергается воздействию пользователей как абстрактного типа, наряду с сохраняющими инварианты функциями доступа и конструкторами, среди которых:

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

Но использование этого интерфейса может быть немного громоздким:

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

Мы можем использовать шаблоны просмотра, чтобы немного почистить его:

{-# LANGUAGE ViewPatterns #-}

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

Используя PatternSynonyms языка PatternSynonyms , мы можем дать еще более приятный интерфейс, позволяя сопоставлять шаблоны, чтобы притворяться, что у нас есть cons- или snoc-list:

{-# 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)

Это позволяет нам писать uncons в очень естественном стиле:

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

RecordWildCards

См. RecordWildCards



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow