-
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.