Spec-Zone.ru › Haskell 9

6.7.4. Синонимы шаблонов

PatternSynonyms
Since:

7.8.1

Разрешают определение синонимов шаблонов.

Синонимы шаблонов активируются языковым расширением PatternSynonyms, которое необходимо для их определения, но не для их использования. Более подробную информацию и примеры синонимов шаблонов можно найти на странице Wiki.

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

data Type = App String [Type]

Вот несколько примеров использования данного представления. Рассмотрим несколько типов из Type вселенной, закодированных таким образом:

App "->" [t1, t2]          -- t1 -> t2
App "Int" []               -- Int
App "Maybe" [App "Int" []] -- Maybe Int

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

collectArgs :: Type -> [Type]
collectArgs (App "->" [t1, t2]) = t1 : collectArgs t2
collectArgs _                   = []

isInt :: Type -> Bool
isInt (App "Int" []) = True
isInt _              = False

Сопоставление с App напрямую сложно читать и склонно к ошибкам в написании. И ситуация становится еще хуже, когда сопоставление вложено:

isIntEndo :: Type -> Bool
isIntEndo (App "->" [App "Int" [], App "Int" []]) = True
isIntEndo _                                       = False

Синонимы шаблонов позволяют абстрагироваться от представления, чтобы раскрыть сопоставители, которые ведут себя как конструкторы в отношении сопоставления с шаблоном. Мы можем создать синонимы шаблонов для известных типов, которые нас интересуют, не привязывая к ним представление (обратите внимание, что их не обязательно определять в том же модуле, что и тип Type):

pattern Arrow t1 t2 = App "->"    [t1, t2]
pattern Int         = App "Int"   []
pattern Maybe t     = App "Maybe" [t]

Что позволяет нам переписать наши функции в гораздо более чистом стиле:

collectArgs :: Type -> [Type]
collectArgs (Arrow t1 t2) = t1 : collectArgs t2
collectArgs _             = []

isInt :: Type -> Bool
isInt Int = True
isInt _   = False

isIntEndo :: Type -> Bool
isIntEndo (Arrow Int Int) = True
isIntEndo _               = False

В целом, существует три вида синонимов шаблонов. Однонаправленные, двунаправленные и явно двунаправленные. Приведенные выше примеры являются примерами двунаправленных синонимов шаблонов. Двунаправленный синоним ведет себя так же, как обычный конструктор данных. Мы можем использовать его в контексте шаблона для деконструкции значений и в контексте выражения для построения значений. Например, мы можем построить значение intEndo с помощью синонимов шаблонов Arrow и Int , определённых ранее.

intEndo :: Type
intEndo = Arrow Int Int

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

intEndo :: Type
intEndo = App "->" [App "Int" [], App "Int" []]

Однонаправленные синонимы могут использоваться только в контексте шаблона и определяются следующим образом:

pattern Head x <- x:xs

В этом случае, Head ⟨x⟩ нельзя использовать в выражениях, только в шаблонах, поскольку это не определит значение для ⟨xs⟩ в правой части. Однако мы можем определить явно двунаправленный синоним шаблона, отдельно указав, как построить и деконструировать тип. Синтаксис для этого следующий:

pattern HeadC x <- x:xs where
  HeadC x = [x]

Тогда мы можем использовать HeadC как в контексте выражения, так и в контексте шаблона. В контексте шаблона он будет сопоставляться с заголовком любого списка длиной не менее одного. В контексте выражения он будет строить список-одиночку.

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

pattern StrictJust a = Just !a

Это недопустимо, поскольку использование BangPatterns в правой части предотвращает формирование корректного выражения. Однако создание строгого синонима шаблона вполне возможно с помощью явно двунаправленного синонима шаблона:

pattern StrictJust a <- Just !a where
  StrictJust !a = Just a

Создание явно двунаправленного синонима шаблона также:

  • может создавать разные конструкторы данных из базового типа данных, а не только тот, который появляется в сопоставлении с шаблоном;
  • может вызывать любые функции или условную логику, особенно валидацию, при условии, что она создает результат нужного типа;
  • может использовать условия в левой части =;
  • может иметь несколько уравнений.

Например:

data PosNeg = Pos Int | Neg Int
pattern Smarter{ nonneg } <- Pos nonneg  where
  Smarter x = if x >= 0 then (Pos x) else (Neg x)

Или с использованием условий:

pattern Smarter{ nonneg } <- Pos nonneg  where
  Smarter x | x >= 0    = (Pos x)
            | otherwise = (Neg x)

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

В таблице ниже обобщается, где можно использовать каждый вид синонима шаблона.

Контекст

Однонаправленный

Двунаправленный

Явно двунаправленный

Шаблон

Да

Да

Да

Выражение

Нет

Да (Вывод)

Да (Явно)

6.7.4.1. Синонимы шаблонов записей

Также возможно определить синонимы шаблонов, которые ведут себя как конструкторы записей. Синтаксис для этого следующий:

pattern Point :: Int -> Int -> (Int, Int)
pattern Point{x, y} = (x, y)

Идея состоит в том, что мы можем использовать Point так, как будто мы определили новый тип данных MyPoint с двумя полями x и y.

data MyPoint = Point { x :: Int, y :: Int }

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

Использование

Пример

В качестве конструктора

zero = Point 0 0

В качестве конструктора с синтаксисом записей

zero = Point { x = 0, y = 0 }

В контексте шаблона

isZero (Point 0 0) = True

В контексте шаблона с синтаксисом записей

isZero (Point { x = 0, y = 0 }) = True

В контексте шаблона с именованием полей

getX (Point {x}) = x

В обновлении записи

(0, 0) { x = 1 } == (1,0)

Использование селекторов записей

x (0,0) == 0

Для однонаправленного синонима шаблона записи мы определяем селекторы записей, но не разрешаем обновления записей или построение.

Синтаксис и семантика синонимов шаблонов подробно описаны в следующих подразделах. Более подробная информация также представлена на странице статьи.

Дополнительные сведения см. на странице Wiki.

6.7.4.2. Синтаксис и область действия синонимов шаблонов

Декларация синонима шаблона может быть однонаправленной, двунаправленной или явно двунаправленной. Синтаксис однонаправленных синонимов шаблонов:

pattern pat_lhs <- pat

синтаксис двунаправленных синонимов шаблонов:

pattern pat_lhs = pat

и синтаксис явно двунаправленных синонимов шаблонов:

pattern pat_lhs <- pat where
  pat_lhs = expr                      -- lhs restricted, see below

Мы можем определить префиксные, инфиксные или синонимы шаблонов записей, изменив форму pat_lhs. Синтаксис для них следующий:

Префиксный

Name args

Инфиксный

arg1 `Name` arg2 или arg1 op arg2

Записи

Name{arg1,arg2,...,argn}

pat_lhs для явно двунаправленного построения не может использовать синтаксис записей. (Потому что правая часть expr может строить различные конструкторы данных.) Она может использовать условия с несколькими уравнениями.

Декларации синонимов шаблонов могут появляться только на верхнем уровне модуля. В частности, они не допускаются в качестве локальных определений.

Переменные в левой части определения связываются шаблоном в правой части. Для двунаправленных синонимов шаблонов все переменные в правой части также должны присутствовать в левой части; также не допускаются шаблоны с подстановкой и шаблоны представлений. Для однонаправленных и явно двунаправленных синонимов шаблонов нет ограничений на шаблон в правой части.

Синонимы шаблонов не могут быть определены рекурсивно.

ПРАГМЫ COMPLETE могут быть указаны, чтобы указать проверяющему исчерпываемость сопоставления с шаблоном, что набор синонимов шаблонов является полным.

6.7.4.3. Импорт и экспорт псевдонимов шаблонов

Имя псевдонима шаблона находится в том же пространстве имён, что и правильные конструкторы данных. Как и обычные конструкторы данных, псевдонимы шаблонов могут импортироваться и экспортироваться посредством ассоциации с конструктором типа или независимо.

Чтобы экспортировать их самостоятельно, в спецификации экспорта или импорта, необходимо префиксровать имена шаблонов ключевым словом pattern, например:

module Example (pattern Zero) where

data MyNum = MkNum Int

pattern Zero :: MyNum
pattern Zero = MkNum 0

Без префикса pattern, Zero будет интерпретировано как конструктор типа в списке экспорта.

Вы также можете использовать ключевое слово pattern в спецификации импорта/экспорта, чтобы импортировать или экспортировать обычный конструктор данных. Например:

import Data.Maybe( pattern Just )

Это позволит использовать конструктор данных Just из типа Maybe, не вводя в область видимости также конструктор типа Maybe.

Начиная с GHC 8.0.1, вы можете также «связывать» псевдонимы шаблонов с экспортируемым конструктором типа, делая этот шаблон похожим на конструктор данных этого типа. Чтобы связать псевдоним шаблона, мы перечисляем его в списке экспорта модуля, который экспортирует конструктор типа. Например, чтобы связать Zero с MyNum, мы можем написать следующее:

module Example ( MyNum(Zero) ) where

Если модуль затем импортирует MyNum из Example, он также импортирует псевдоним шаблона Zero.

Также можно использовать специальный токен .. в списке экспорта, чтобы указать все в настоящее время связанные конструкторы. Например, мы можем написать:

module Example ( MyNum(.., Zero) ) where

В этом случае Example экспортирует конструктор типа MyNum с конструктором данных MkNum и также псевдоним шаблона Zero.

Связанные псевдонимы шаблонов проверяются на соответствие типу конструктора, с которым они связаны. Псевдоним шаблона P не может быть связан с конструктором типа T, если тип P явно несовместим с типом T.

Модуль, который импортирует MyNum(..) из Example и затем повторно экспортирует MyNum(..), также экспортирует любые псевдонимы шаблонов, связанные с MyNum в Example. Более полное описание можно найти на сайте вики.

6.7.4.4. Типизация псевдонимов шаблонов

Для определения псевдонима шаблона в форме

pattern P var1 var2 ... varN <- pat

ему присваивается тип шаблона в форме

pattern P :: CReq => CProv => t1 -> t2 -> ... -> tN -> t

где ⟨CReq⟩ и ⟨CProv⟩ — контексты типов, а ⟨t1⟩, ⟨t2⟩, …, ⟨tN⟩ и ⟨t⟩ — типы. Обратите внимание на необычную форму типа с двумя контекстами ⟨CReq⟩ и ⟨CProv⟩:

  • ⟨CReq⟩ — это ограничения, необходимые для соответствия шаблону.
  • ⟨CProv⟩ — это ограничения, предоставляемые (provided) успешным соответствием шаблону.

Например, рассмотрим

data T a where
  MkT :: (Show b) => a -> b -> T a

f1 :: (Num a, Eq a) => T a -> String
f1 (MkT 42 x) = show x

pattern ExNumPat :: (Num a, Eq a) => (Show b) => b -> T a
pattern ExNumPat x = MkT 42 x

f2 :: (Eq a, Num a) => T a -> String
f2 (ExNumPat x) = show x

Здесь f1 не использует псевдонимы шаблонов. Чтобы соответствовать числовому шаблону 42 , необходимо, чтобы вызывающий код удовлетворил ограничения (Num a, Eq a), поэтому они появляются в типе f1. Вызов show генерирует ограничение (Show b), где b — это переменная типа с экзистенциальной областью действия, связанная с соответствием шаблона по MkT . Но то же соответствие шаблону также предоставляет ограничение (Show b) (см. тип MkT ), и все в порядке.

Точно такая же логика применяется к ExNumPat: соответствие шаблону ExNumPat требует ограничений (Num a, Eq a), и предоставляет ограничение (Show b).

Обратите также внимание на следующие моменты

  • В общем случае, когда CProv пусто (то есть ()), его можно вообще опустить в приведенной выше подписи типа шаблона для P.
  • Однако, если CProv не пусто, а CReq пусто, то приведенная выше подпись типа шаблона для P должна быть указана как

    P :: () => CProv => t1 -> t2 -> .. -> tN -> t
    
  • Команда GHCi :info отображает типы шаблонов в этом формате.
  • Вы можете указать явную подпись шаблона, как мы это сделали для ExNumPat выше, чтобы указать тип шаблона, как вы можете сделать для функции. Как обычно, подпись типа может быть менее полиморфной, чем выведенный тип. Например

    -- Inferred type would be 'a -> [a]'
    pattern SinglePair :: (a, a) -> [(a, a)]
    pattern SinglePair x = [x]
    

    Как и подписи для связываний на уровне значений, подписи псевдонимов шаблонов могут применяться к нескольким шаблонам. Например,

    pattern Left', Right' :: a -> Either a a
    pattern Left' x  = Left x
    pattern Right' x = Right x
    
  • Правила для лексически-ограниченных переменных типов (см. Лексически-ограниченные переменные типов) применяются к подписям псевдонимов шаблонов. Как определяют эти правила, только переменные типа из явно видимого внешнего forall (универсалы) охватывают определение псевдонима шаблона; экзистенциали, связанные внутренним forall, нет. Например

    data T a where
       MkT :: Bool -> b -> (b->Int) -> a -> T a
    
    pattern P :: forall a. forall b. b -> (b->Int) -> a -> T a
    pattern P x y v <- MkT True x y (v::a)
    

    Здесь универсальная переменная типа a охватывает определение P, но экзистенциальная b — нет. (См. обсуждение на #14998.)

  • Для двунаправленного псевдонима шаблона использование псевдонима шаблона в качестве выражения имеет тип

    (CReq, CProv) => t1 -> t2 -> ... -> tN -> t
    

    Итак, в предыдущем примере, при использовании в выражении, ExNumPat имеет тип

    ExNumPat :: (Num a, Eq a, Show b) => b -> T t
    

    Обратите внимание, что это немного более ограничительно, чем выражение MkT 42 x, которое не требует (Eq a).

  • Рассмотрим эти два псевдонима шаблонов:

    data S a where
       S1 :: Bool -> S Bool
    
    pattern P1 :: Bool -> Maybe Bool
    pattern P1 b = Just b
    
    pattern P2 :: () => (b ~ Bool) => Bool -> S b
    pattern P2 b = S1 b
    
    f :: Maybe a -> String
    f (P1 x) = "no no no"     -- Type-incorrect
    
    g :: S a -> String
    g (P2 b) = "yes yes yes"  -- Fine
    

    Шаблон P1 может соответствовать только значению типа Maybe Bool, поэтому функция f отклоняется, потому что подпись типа — Maybe a. (Чтобы увидеть это, представьте развертывание псевдонима шаблона.)

    С другой стороны, функция g работает нормально, потому что соответствие шаблону P2 (который оборачивает GADT S ) предоставляет локальное равенство (a~Bool) Если вы зададите явную подпись шаблона P2 :: Bool -> S Bool, то P2 станет менее полиморфным и будет вести себя точно так же, как P1 , так что g затем будет отклонено.

    Короче говоря, если вы хотите получить поведение, подобное GADT, для псевдонимов шаблонов, то (в отличие от конкретных конструкторов данных, например S1 ) вам необходимо написать его тип с явными предоставляемыми равенствами. Для конкретного конструктора данных, например S1, вы можете написать его подпись типа как S1 :: Bool -> S Bool или S1 :: (b~Bool) => Bool -> S b; оба эквивалентны. Но не так для псевдонимов шаблонов: эти две формы отличаются, чтобы различать два описанных выше случая. (См. #9953 для обсуждения этого выбора.)

6.7.4.5. Сопоставление с псевдонимами шаблонов

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

Более точно, семантика сопоставления шаблонов дана в разделе 3.17 отчёта Haskell 2010. К неофициальной семантике в разделе 3.17.2 мы добавляем это дополнительное правило:

  • Если шаблон — это шаблон конструктора (P p1 ... pn), где P — это псевдоним шаблона, определённый с помощью P x1 ... xn = p или P x1 ... xn <- p, то:

    1. Сопоставьте значение v с p . Если это сопоставление терпит неудачу или расходится, так же поступает и все (псевдоним шаблона) сопоставление. В противном случае соответствие шаблону p должно присвоить значения переменным x1 ... xn; пусть они будут присвоены значениям v1 ... vn.
    2. Сопоставьте v1 с p1, v2 с p2 и так далее. Если любое из этих сопоставлений терпит неудачу или расходится, так же поступает и все сопоставление.
    3. Если все сопоставления с pi выполнены успешно, сопоставление успешно, присваивая значения переменным, связанным pi . (Переменные xi не присваиваются; они остаются локальными для объявления псевдонима шаблона.)

Например, в следующей программе f и f' эквивалентны:

pattern Pair x y <- [x, y]

f (Pair True True) = True
f _                = False

f' [x, y] | True <- x, True <- y = True
f' _                              = False

Обратите внимание, что строгость f отличается от строгости g , определённой ниже:

g [True, True] = True
g _            = False

*Main> f (False:undefined)
*** Exception: Prelude.undefined
*Main> g (False:undefined)
False

6.7.4.6. Прагмы для псевдонимов шаблонов

Прагма INLINABLE, INLINE и NOINLINE поддерживаются для псевдонимов шаблонов начиная с GHC 9.2. Например:

pattern InlinablePattern x = [x]
{-# INLINABLE InlinablePattern #-}
pattern InlinedPattern x = [x]
{-# INLINE InlinedPattern #-}
pattern NonInlinedPattern x = [x]
{-# NOINLINE NonInlinedPattern #-}

Как и другие прагмы INLINABLE, INLINE и NOINLINE, можно указать, к какой фазе применяется прагма:

pattern Q x = [x]
{-# NOINLINE[1] Q #-}

Прагмы применяются как при использовании шаблона в качестве сопоставителя, так и в качестве конструктора данных. Для явно двунаправленных псевдонимов шаблонов прагма должна находиться на верхнем уровне, а не вложенной в оператор where. Например, это не скомпилируется:

pattern HeadC x <- x:xs where
  HeadC x = [x]
  {-# INLINE HeadC #-}

но это скомпилируется:

pattern HeadC x <- x:xs where
  HeadC x = [x]
{-# INLINE HeadC #-}

Если для шаблона не указана прагма, решение об инлайнинге принимает собственная эвристика инлайнинга GHC.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.12.1/docs/users_guide/exts/pattern_synonyms.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API