-
PartialTypeSignatures -
- Начиная с:
-
7.10.1
Проверка типов разрешает вывод типов для пропусков.
Частичная сигнатура типа — это сигнатура типа, содержащая специальные заполнители, называемые подстановочными знаками. Подстановочный знак записывается как подчёркивание (например, «_») или, если включено NamedWildCards, как любой идентификатор, начинающийся с подчёркивания (например, «_foo», «_bar»). Частичные сигнатуры типов относятся к сигнатурам типов так же, как типизированные пропуски относятся к выражениям. Во время компиляции такие подстановочные знаки или пропуски вызовут сообщение об ошибке, в котором будет указан тип, выведенный для места пропуска, и информация о происхождении свободных переменных типов. По умолчанию GHC выводит такие сообщения об ошибках.
В отличие от типизированных пропусков, которые делают программу неполной и приводят к ошибкам при вычислении, для пропусков в сигнатурах типов это необязательно. Проверка типов (в большинстве случаев) может проверить привязку как с сигнатурой типа, так и без неё. Частичная сигнатура типа позволяет заполнить промежуток между этими двумя крайностями: программист может выбрать, какие части типа аннотировать, а какие оставить для вывода проверяющей системой типов.
По умолчанию проверяющая система типов выводит сообщение об ошибке для каждого пропуска в частичной сигнатуре типа, сообщая программисту выведенный тип. Если включено расширение PartialTypeSignatures, проверяющая система типов принимает выведенный тип для каждого пропуска, выдавая предупреждения вместо ошибок. Кроме того, эти предупреждения можно отключить флагом -Wno-partial-type-signatures.
Однако, поскольку GHC должен вывести тип, когда часть типа опущена, он не может использовать полиморфную рекурсию. То же ограничение действует и в случае, когда сигнатура типа полностью опущена.
Частичная сигнатура типа также заставляет GHC обобщать привязку, даже если включён MonoLocalBinds; см. Обобщение в let-выражениях.
6.11.7.1. Синтаксис
(Частичная) сигнатура типа имеет следующий вид: forall a b .. . (C1, C2, ..) => tau. Она состоит из трёх частей:
- Переменные типов:
a b .. - Ограничения:
(C1, C2, ..) - (Моно)тип:
tau
Различают три вида подстановочных знаков.
6.11.7.1.1. Подстановочные знаки типов
Подстановочные знаки, встречающиеся в части монотипа (тау) сигнатуры типа, называются подстановочными знаками типов (слово «тип» часто опускают, поскольку это вид подстановочного знака по умолчанию). Подстановочные знаки типов могут быть инстанцированы любым монотипом, например Bool или Maybe [Bool], в том числе функциями и типами высших порядков, такими как (Int -> Bool) или Maybe.
not' :: Bool -> _ not' x = not x -- Inferred: Bool -> Bool maybools :: _ maybools = Just [True] -- Inferred: Maybe [Bool] just1 :: _ Int just1 = Just 1 -- Inferred: Maybe Int filterInt :: _ -> _ -> [Int] filterInt = filter -- has type forall a. (a -> Bool) -> [a] -> [a] -- Inferred: (Int -> Bool) -> [Int] -> [Int]
Например, первый подстановочный знак в сигнатуре типа not' приведёт к следующему сообщению об ошибке:
Test.hs:4:17: error:
• Found type wildcard ‘_’ standing for ‘Bool’
To use the inferred type, enable PartialTypeSignatures
• In the type signature:
not' :: Bool -> _
• Relevant bindings include
not' :: Bool -> Bool (bound at Test.hs:5:1)
Если подстановочный знак не инстанцирован монотипом, он обобщается, то есть заменяется новой переменной типа. Например:
foo :: _ -> _ foo x = x -- Inferred: forall t. t -> t filter' :: _ filter' = filter -- has type forall a. (a -> Bool) -> [a] -> [a] -- Inferred: (a -> Bool) -> [a] -> [a]
6.11.7.1.2. Именованные подстановочные знаки
-
NamedWildCards -
Разрешает давать имена подстановочным знакам (например,
_x) в сигнатурах типов.
Подстановочным знакам типов также можно дать имя, добавив к подчёркиванию идентификатор, то есть _a. Они называются именованными подстановочными знаками. Все вхождения одного и того же именованного подстановочного знака в пределах одной сигнатуры типа унифицируются с одним и тем же типом. Например:
f :: _x -> _x
f ('c', y) = ('d', error "Urk")
-- Inferred: forall t. (Char, t) -> (Char, t)
Именованный подстановочный знак заставляет типы аргумента и результата совпадать. Без сигнатуры GHC вывел бы forall a b. (Char, a) -> (Char, b). Именованный подстановочный знак можно упоминать в ограничениях, если он также встречается в части монотипа сигнатуры типа, чтобы гарантировать его унификацию с каким-либо типом:
somethingShowable :: Show _x => _x -> _ somethingShowable x = show x -- Inferred type: Show a => a -> String somethingShowable' :: Show _x => _x -> _ somethingShowable' x = show (not x) -- Inferred type: Bool -> String
Кроме подстановочного знака дополнительных ограничений (см. Подстановочный знак дополнительных ограничений), в ограничениях могут встречаться только именованные подстановочные знаки, например _x в Show _x.
Если включён ScopedTypeVariables, область видимости именованных подстановочных знаков сигнатуры функции распространяется на тело функции так же, как у явно объявленных через forall переменных типов (Переменные типов с лексической областью видимости), даже если явного forall нет. Например:
f :: _a -> _a
f x = let g :: _a -> _a
g = ...
in ...
Здесь именованный подстановочный знак _a имеет область видимости в теле f и тем самым связывает вхождения _a в сигнатуре g. Все четыре вхождения обозначают один и тот же тип.
Именованные подстановочные знаки не следует путать с переменными типов. Несмотря на синтаксическое сходство, именованные подстановочные знаки могут унифицироваться с монотипами, а также обобщаться (и вести себя как переменные типов).
В первом примере выше _x обобщается (и фактически заменяется новой переменной типа a). Во втором примере _x унифицируется с типом Bool, а поскольку Bool реализует класс типов Show, ограничение Show Bool можно упростить и удалить.
По умолчанию GHC (как предписывает стандарт Haskell 2010) разбирает идентификаторы, начинающиеся с подчёркивания в типе, как переменные типов. Чтобы считать их именованными подстановочными знаками, необходимо включить расширение NamedWildCards. В приведённом ниже примере показан эффект.
foo :: _a -> _a foo _ = False
Компиляция этой программы без включения NamedWildCards приводит к следующему сообщению об ошибке: переменная типа _a не соответствует фактическому типу Bool.
Test.hs:5:9: error:
• Couldn't match expected type ‘_a’ with actual type ‘Bool’
‘_a’ is a rigid type variable bound by
the type signature for:
foo :: forall _a. _a -> _a
at Test.hs:4:8
• In the expression: False
In an equation for ‘foo’: foo _ = False
• Relevant bindings include foo :: _a -> _a (bound at Test.hs:5:1)
Компиляция этой программы с включёнными NamedWildCards и PartialTypeSignatures приводит к следующему сообщению об ошибке, в котором указан выведенный тип именованного подстановочного знака _a.
Test.hs:4:8: warning: [-Wpartial-type-signatures]
• Found type wildcard ‘_a’ standing for ‘Bool’
• In the type signature:
foo :: _a -> _a
• Relevant bindings include
foo :: Bool -> Bool (bound at Test.hs:5:1)
6.11.7.1.3. Подстановочный знак дополнительных ограничений
Третий вид подстановочного знака — это подстановочный знак дополнительных ограничений. Его наличие указывает, что во время проверки типов может быть выведено произвольное количество дополнительных ограничений, которые будут добавлены в сигнатуру типа. В приведённом ниже примере подстановочный знак дополнительных ограничений используется для вывода трёх дополнительных ограничений.
arbitCs :: _ => a -> String
arbitCs x = show (succ x) ++ show (x == x)
-- Inferred:
-- forall a. (Enum a, Eq a, Show a) => a -> String
-- Error:
Test.hs:5:12: error:
Found constraint wildcard ‘_’ standing for ‘(Show a, Eq a, Enum a)’
To use the inferred type, enable PartialTypeSignatures
In the type signature:
arbitCs :: _ => a -> String
Подстановочный знак дополнительных ограничений не должен мешать программисту заранее перечислить ограничения, которые ему известны или которые он хочет указать, например:
-- Also a correct partial type signature:
arbitCs' :: (Enum a, _) => a -> String
arbitCs' x = arbitCs x
-- Inferred:
-- forall a. (Enum a, Show a, Eq a) => a -> String
-- Error:
Test.hs:9:22: error:
Found constraint wildcard ‘_’ standing for ‘()’
To use the inferred type, enable PartialTypeSignatures
In the type signature:
arbitCs' :: (Enum a, _) => a -> String
Подстановочный знак дополнительных ограничений также может привести к тому, что дополнительные ограничения не будут выведены, например:
noCs :: _ => String
noCs = "noCs"
-- Inferred: String
-- Error:
Test.hs:13:9: error:
Found constraint wildcard ‘_’ standing for ‘()’
To use the inferred type, enable PartialTypeSignatures
In the type signature:
noCs :: _ => String
Поскольку одного подстановочного знака дополнительных ограничений достаточно для вывода любого количества ограничений, в сигнатуре типа допускается только один такой знак, и он должен стоять последним в списке ограничений.
Подстановочным знакам дополнительных ограничений нельзя давать имена.
6.11.7.2. Где они могут встречаться?
Частичные сигнатуры типов разрешены для привязок, сигнатур образцов и выражений, за исключением того, что подстановочные знаки дополнительных ограничений не поддерживаются в сигнатурах образцов и выражений. В следующем примере подстановочный знак используется в каждом из трёх возможных контекстов.
{-# LANGUAGE ScopedTypeVariables #-}
foo :: _
foo (x :: _) = (x :: _)
-- Inferred: forall w_. w_ -> w_
Анонимные и именованные подстановочные знаки могут встречаться в левой части объявления экземпляра типа или данных; см. Подстановочные знаки в левой части экземпляров семейств данных и типов.
Анонимные подстановочные знаки также разрешены в видимых применениях типов/видимых применениях видов (Видимое применение типа). Если нужно указать только второй аргумент типа для wurble, можно написать wurble @_ @Int, где первый аргумент — подстановочный знак.
Отдельные объявления deriving допускают использование одного подстановочного знака дополнительных ограничений, например:
deriving instance _ => Eq (Foo a)
Это обозначает производный экземпляр Eq (Foo a), контекст которого выводится примерно так же, как в обычных предложениях deriving. Любое другое использование подстановочных знаков в отдельном объявлении deriving запрещено.
Во всех остальных контекстах подстановочные знаки типов запрещены, а именованный подстановочный знак рассматривается как обычная переменная типа. Например:
class C _ where ... -- Illegal instance Eq (T _) -- Illegal (currently; would actually make sense) instance Eq _a => Eq (T _a) -- Perfectly fine, same as Eq a => Eq (T a)
Частичные сигнатуры типов также можно использовать во вставках Template Haskell.
-
Вставки объявлений: частичные сигнатуры типов полностью поддерживаются.
{-# LANGUAGE TemplateHaskell, NamedWildCards #-} $( [d| foo :: _ => _a -> _a -> _ foo x y = x == y|] ) -
Вставки выражений: анонимные и именованные подстановочные знаки можно использовать в сигнатурах выражений. Подстановочные знаки дополнительных ограничений не поддерживаются, как и в обычных сигнатурах выражений.
{-# LANGUAGE TemplateHaskell, NamedWildCards #-} $( [e| foo = (Just True :: _m _) |] ) - Типизированные вставки выражений: поддерживаются те же подстановочные знаки, что и во вставках выражений (без проверки типов).
-
Вставки образцов: анонимные и именованные подстановочные знаки можно использовать в сигнатурах образцов. Обратите внимание, что для разрешения сигнатур образцов необходимо включить
ScopedTypeVariables. Подстановочные знаки дополнительных ограничений не поддерживаются, как и в обычных сигнатурах образцов.{-# LANGUAGE TemplateHaskell, ScopedTypeVariables #-} foo $( [p| (x :: _) |] ) = x -
Вставки типов: во вставках типов поддерживаются только анонимные подстановочные знаки. Именованные подстановочные знаки и подстановочные знаки дополнительных ограничений не поддерживаются.
{-# LANGUAGE TemplateHaskell #-} foo :: $( [t| _ |] ) -> a foo x = x