Spec-Zone.ru › Haskell 9

6.9.8. Перегруженные метки

OverloadedLabels
Начиная с:

8.0.1

Включает использование синтаксиса перегруженных меток #foo.

GHC поддерживает перегруженные метки — разновидность идентификаторов, интерпретация которых может зависеть как от их типа, так и от буквального текста. Когда включено расширение OverloadedLabels, перегруженную метку можно записать с префиксом в виде решётки, например #foo. Тип этого выражения — IsLabel "foo" a => a.

Класс IsLabel определяется следующим образом:

class IsLabel (x :: Symbol) a where
  fromLabel :: a

Это во многом похоже на класс IsString (см. Перегруженные строковые литералы), но с дополнительным параметром типа, благодаря которому текст метки доступен как строка на уровне типов (см. Литералы уровня типов). Обратите внимание, что fromLabel в GHC 8.0 имел дополнительный аргумент Proxy# x, но в GHC 8.2 его убрали, поскольку вместо него можно использовать применение типа (см. Видимое применение типа).

Для этого класса нет предопределённых экземпляров. По умолчанию он не находится в области видимости, но его можно сделать видимым, импортировав GHC.OverloadedLabels. В отличие от IsString, для IsLabel не предусмотрено специальных правил вывода типа по умолчанию.

При проверке типов GHC заменяет вхождение перегруженной метки, например #foo, на fromLabel @"foo". Это выражение будет иметь некоторый тип alpha, а также потребует разрешения ограничения класса IsLabel "foo" alpha.

Предполагается, что IsLabel будет использоваться для поддержки перегруженных полей записей и, возможно, анонимных записей. Поэтому в будущем для него могут быть определены экземпляры для базовых типов данных (в частности, (->)).

Если включено расширение RebindableSyntax, перегруженные метки будут преобразовываться в код низкого уровня с использованием той функции fromLabel, которая находится в области видимости, а не всегда с использованием GHC.OverloadedLabels.fromLabel.

При записи перегруженной метки между знаком решётки и следующим за ним идентификатором не должно быть пробела. Расширение MagicHash использует знаки решётки в постфиксной позиции; если включены оба расширения — OverloadedLabels и MagicHash, то x#y означает x# y, а если включено только OverloadedLabels, то это означает x #y. Расширение UnboxedTuples превращает (# в одну лексему, поэтому при включённом UnboxedTuples необходимо ставить пробел между открывающей скобкой и перегруженной меткой. Во избежание неоднозначности настоятельно рекомендуется ставить пробел перед решёткой при использовании OverloadedLabels.

При использовании OverloadedLabels (или других расширений, использующих знаки решётки) в файле .hsc (см. Написание интерфейсов Haskell для кода на C: hsc2hs) знаки решётки необходимо удваивать (писать ##foo вместо #foo), чтобы они не воспринимались как директивы hsc2hs.

Ниже показано расширение примера доступа к полям записи из раздела Литералы уровня типов, демонстрирующее использование перегруженной метки в качестве селектора записи:

{-# LANGUAGE DataKinds, KindSignatures, MultiParamTypeClasses,
             FunctionalDependencies, FlexibleInstances,
             OverloadedLabels, ScopedTypeVariables #-}

import GHC.OverloadedLabels (IsLabel(..))
import GHC.TypeLits (Symbol)

data Label (l :: Symbol) = Get

class Has a l b | a l -> b where
  from :: a -> Label l -> b

data Point = Point Int Int deriving Show

instance Has Point "x" Int where from (Point x _) _ = x
instance Has Point "y" Int where from (Point _ y) _ = y

instance Has a l b => IsLabel l (a -> b) where
  fromLabel x = from x (Get :: Label l)

example = #x (Point 1 2)

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

Примеры нового допустимого синтаксиса:

  • Начальные заглавные буквы: #Foo эквивалентно getLabel @”Foo”
  • Цифры: #3.14 эквивалентно getLabel @”3.14”
  • Произвольные строки: #”Hello, World!” эквивалентно getLabel @”Hello, World!”

Ниже приведён пример более свободного использования этого расширения, доступного начиная с GHC 9.6:

{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedLabels      #-}
{-# LANGUAGE MagicHash             #-}

import Data.Foldable (traverse_)
import Data.Proxy (Proxy(..))
import GHC.OverloadedLabels (IsLabel(..))
import GHC.TypeLits (KnownSymbol, symbolVal)
import GHC.Prim (Addr#)

instance KnownSymbol symbol => IsLabel symbol String where
  fromLabel = symbolVal (Proxy :: Proxy symbol)

(#) :: String -> Int -> String
(#) _ i = show i

f :: Addr# -> Int -> String
f _ i = show i

main :: IO ()
main = traverse_ putStrLn
  [ #a
  , #number17
  , #do
  , #type
  , #Foo
  , #3
  , #199.4
  , #17a23b
  , #f'a'
  , #'a'
  , #'
  , #''notTHSplice
  , #...
  , #привет
  , #こんにちは
  , #"3"
  , #":"
  , #"Foo"
  , #"The quick brown fox"
  , #"\""
  , (++) #hello#world
  , (++) #"hello"#"world"
  , #"hello"# 1 -- equivalent to `(fromLabel @"hello") # 1`
  , f "hello"#2 -- equivalent to `f ("hello"# :: Addr#) 2`
  ]

Дополнительные сведения см. в предложении GHC № 170.

© 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.14.1/docs/users_guide/exts/overloaded_labels.html

Spec-Zone.ru

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