-
OverloadedLabels -
- Since:
-
8.0.1
Включить использование синтаксиса перегруженных меток
#foo.
GHC поддерживает перегруженные метки, вид идентификаторов, чьё толкование может зависеть как от типа, так и от литерального текста. Когда включён расширение OverloadedLabels, перегруженную метку можно записать с префиксом решётка, например #foo. Тип этого выражения IsLabel "foo" a => a.
Класс IsLabel определяется как:
class IsLabel (x :: Symbol) a where fromLabel :: a
Это довольно похоже на класс IsString (см. Перегруженные строковые литералы), но с дополнительным параметром типа, который делает текст метки доступным в виде строкового типа уровня типа (см. Типы уровня типа). Обратите внимание, что fromLabel имел дополнительный аргумент Proxy# x в GHC 8.0, но он был удалён в 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 для получения более подробной информации.