пятница, 23 января 2015 г.

Об именах в Haskell

Имя любого идентификатора в Haskell начинается с буквы, за которой следует ноль или более букв, цифр, символов подчёркивания _ и одинарной кавычки '. В качестве буквы рассматриваются только латинские символы в интервалах a..z и A..Z. Символ _ принято считать буквой, в следствии чего имя функции может начинаться с этого символа, но не может состоять только из него, в виду того, что в образцах Haskell он обозначает любое значение. Имена функций, составленные не из символов набора ascSymbol, обязательно должны начинаться со строчной буквы или символа _. Имена пространств имён, типов данных, конструкторов данных и классов типов составленные не из символов набора ascSymbol должны начинаться с прописной буквы. В данной заметке даётся некоторая информация об использовании символов набора ascSymbol в идентификаторах Haskell.

"Специальные" символы

Согласно § 2.2 стандарта Haskell 2010 специальными (special) считаются следующие символы:
( ) , ; [ ] ` { }
Там же отдельно определён и следующий набор символов, именованный как ascSymbol:
! # $ % & * + . / < = > ? @ \ ˆ | - ˜ :
Буквенно-цифровые символы в стандарте выделены в отдельные наборы: ascSmall, ascLarge, uniSmall, uniLarge, ascDigit, uniDigit и т.д.
Иногда специальными ошибочно называют символы, входящие в набор ascSymbol. Например, давая определение оператору порой говорят, что их имена состоят только из специальных символов. На самом же деле специальные символы вовсе не могут использоваться в составе имён операторов (да и вообще в составе любых имён).
Из символов набора ascSymbol разрешено формировать любые имена за исключением следующих, зарезервированных:
.. : :: = \ | <- -> @ ~ =>

Функция или оператор?

Оператором в Haskell называется любая функция, вызванная в инфиксной форме, либо частично применённая посредством сечений. Т.о. можно ли назвать ту или иную функцию оператором зависит от контекста её использования. В следующих примерах функции elem и * являются операторами:
λ: 5 `elem` [0..10]
True
λ: 4 * 10
40
Некоторая пользовательская функция для использования её в качестве сечений:
mySomeFunc :: Integral a => a -> a -> a
_ `mySomeFunc` 0 = error "Zero division."
a `mySomeFunc` b = a `div` b
Используем функцию mySomeFunc в качестве сечений, т.е. в данном контексте она является оператором:
λ: let n = (8 `mySomeFunc`)
λ: let m = (`mySomeFunc` 2)
λ: n 2
4
λ: m 10
5
Функция вызванная в префиксной форме записи не является оператором в данном контексте использования. В следующих примерах функции elem и * не являются операторами:
λ: elem 5 [0..10]
True
λ: (*) 4 10
40

Инфиксная и префиксная формы

Если имя функции состоит из символов набора ascSymbol, то при указании её сигнатуры такое имя необходимо заключать в круглые скобки.
(###) :: Int -> Int -> Int -- Сигнатура функции
Если имя функции состоит из символов набора ascSymbol, и её определение даётся в префиксной форме записи, то это имя так же необходимо задавать в круглых скобках.
(@@@) a = (a +) -- Определение функции в префиксной форме
Если имя функции состоит не из символов набора ascSymbol, и её определение даётся в инфиксной форме записи, то имя необходимо обосабливать символами обратной кавычки `.
a `myFunc` b = a * b -- Определение функции в инфиксной форме
Примеры использование инфиксной и префиксной форм записей в коде определения функций:
(###) :: Int -> Int -> Int -- Сигнатура функции
a ### b = a * b -- Определение функции в инфиксной форме

(@@@) :: Int -> Int -> Int -- Сигнатура функции
(@@@) a = (a +) -- Определение функции в префиксной форме

myFunc :: Int -> Int -> Int -- Сигнатура функции
a `myFunc` b = a * b -- Определение функции в инфиксной форме

myFunc' :: Int -> Int -> Int -- Сигнатура функции
myFunc' a = (a -) -- Определение функции в префиксной форме

В инфиксной форме можно вызывать любую функцию, количество параметров которой больше одного, например:
λ: ((+) `foldr` 0) [1..10]
55
Или вот к примеру функция, принимающая четыре параметра:
someFunc :: Int -> Int -> Int -> Int -> Int
someFunc a b c d = a + b + c + d
Префиксный и инфиксный варианты её вызова:
λ: someFunc 1 2 3 4
10
λ: (1 `someFunc` 2) 3 4
10

Имена конструкторов данных

Как уже отмечалось выше - имена конструкторов данных начинаются с прописной буквы, состоят из буквенно-цифровых символов, а так же символов _ и ' (при необходимости). Однако допускается и система наименований, схожая с той, которая применяется по отношению к функциям...
Стандартом Haskell разрешается формировать имена конструкторов данных из символов набора ascSymbol. Подобно обычным функциям, такие конструкторы могут использоваться как в инфиксной, так и в префиксной форме. Кроме того, их имена обязательно должны начинаться с символа : (двоеточие). Т.е. если вы где-то в коде увидели нечто вроде 123 :#$% "ASDF", то сразу же можете быть уверенными в том, что перед вами вызов конструктора :#$% с параметрами 123 и "ASDF".

data Symbolic n
  = Constant n
   | Variable String
   | Symbolic n :=> Symbolic n
   | Symbolic n :<= Symbolic n
   | (:<=>) (Symbolic n) (Symbolic n)
  deriving Show
Cоздадим в ghci несколько экземпляров типа Symbolic, используя разные конструкторы данных:
λ: let a = Constant 10; b = Variable "Hello"
λ: let n = a :=> b; m = a :<= b; k = a :<=> b
λ: n
Constant 10 :=> Variable "Hello"
λ: m
Constant 10 :<= Variable "Hello"
λ: k
(:<=>) (Constant 10) (Variable "Hello")

Имена конструкторов типов

По умолчанию, имена конструкторов типов не могут состоять из символов набора ascSymbol. Однако можно принудительно разрешить использование таких имён для конструкторов типов. Это делается либо путём указания опции -XTypeOperators при вызове ghc или ghci, либо путём добавления в начало hs-файла следующей строки:
{-# LANGUAGE TypeOperators #-}
В отличие от конструктора данных, имя конструктора типа не обязано начинаться с символа : (двоеточие).

{-# LANGUAGE TypeOperators #-}
data a @# b -- Конструктор типа
  -- Конструкторы данных:
  = XLeft a
   | XRight b
   | (a @# b) :$% (a @# b)
   | (a @# b) :!~ (a @# b)
   | (:!~>) (a @# b) (a @# b) (a @# b)
  deriving Show
Пробуем создать экземпляры нашего типа данных:
λ: let a = XLeft 10; b = XRight "ABCD"
λ: let c = a :$% b; d = b :!~ a;
λ: let e = (:!~>) a a a
λ: c
XLeft 10 :$% XRight "ABCD"
λ: d
XRight "ABCD" :!~ XLeft 10
λ: e
(:!~>) (XLeft 10) (XLeft 10) (XLeft 10)

Имена образцов

Имена образцов так же могут состоять из символов набора ascSymbol.
λ: let ($%%) = (*)
λ: :t ($%%)
($%%) :: Num a => a -> a -> a
λ: 5 $%% 2
10
λ: ($%%) 3 4
12
λ: let (###) = (3 +)
λ: (###) 2
5

UPD
Имена функциям можно назначать используя и другие символы Unicode. Например, в книгах порой можно встретить в качестве имён функций символы математических операторов. Например, можно написать такую функцию:

(∀) :: (a -> b) -> [a] -> [b]
f  [] = []
f  (x:xs) = f x : f  xs
Эта функция успешно загрузится в ghci, но вызвать её будет проблематично, в виду того, что в cmd.exe и powershell.exe может оказаться затруднительным ввести в командную строку символ . У меня не получилось сделать это ни через буфер обмена (соответствующий пункт в контекстном меню), ни через комбинацию клавиш (Alt + 8704). Не помогает и использование шрифта Lucida console, и предварительный вызов команды chcp.com 65001.

Тем не менее, в некоторых книгам можно увидеть весьма активное использование математических символов в качестве имён функций в исходном коде Haskell. Например, в книге Ричарда Бёрда "Жемчужины проектирования алгоритмов. Функциональный подход. С примерами на Haskell".

Подводя итоги

Если речь идёт не о функциях, то вы вряд ли будете в именах идентификаторов использовать символы набора ascSymbol. Однако знание того, как они могут применяться за рамками имён функций поможет вам понимать код, в котором они по каким-либо причинам всё же используются.

Комментариев нет: