В этой главе мы опишем синтаксис и неформальную семантику выражений Haskell , включая, где это возможно, их трансляцию в ядро Haskell . За исключением let-выражений эти трансляции сохраняют и статическую, и динамическую семантику. Свободные переменные и конструкторы, используемые в этих трансляциях, всегда ссылаются на сущности, определенные в Prelude. Например, "concatMap", используемая в трансляции описания списка (раздел 3.11), обозначает concatMap, определенную в Prelude, невзирая на то, находится ли идентификатор "concatMap" в области видимости, где используется описание списка, и (если находится в области видимости) к чему он привязан.
В синтаксисе, который следует далее, есть некоторые семейства нетерминалов, индексированные уровнями приоритета (записанными как верхний индекс). Аналогично, нетерминалы op (оператор), varop (оператор-переменной) и conop (оператор-конструктора) могут иметь двойной индекс: букву l, r или n соответственно для левоассоциативности, правоассоциативности или отсутствия ассоциативности и уровень приоритета. Переменная уровня приоритета i изменяется в пределах от 0 до 9, переменная ассоциативности a изменяется в диапазоне {l, r, n}. Например,
aexp | -> | ( expi+1 qop(a,i) ) |
exp | -> | exp0 :: [context =>] type | (сигнатура типа выражения) |
| | exp0 | ||
expi | -> | expi+1 [qop(n,i) expi+1] | |
| | lexpi | ||
| | rexpi | ||
lexpi | -> | (lexpi | expi+1) qop(l,i) expi+1 | |
lexp6 | -> | - exp7 | |
rexpi | -> | expi+1 qop(r,i) (rexpi | expi+1) | |
exp10 | -> | \ apat1 ... apatn -> exp | (лямбда-абстракция, n>=1) |
| | let decls in exp | (let-выражение) | |
| | if exp then exp else exp | (условное выражение) | |
| | case exp of { alts } | (case-выражение) | |
| | do { stmts } | (do-выражение) | |
| | fexp | ||
fexp | -> | [fexp] aexp | (применение функции) |
aexp | -> | qvar | (переменная) |
| | gcon | (общий конструктор) | |
| | literal | ||
| | ( exp ) | (выражение в скобках) | |
| | ( exp1 , ... , expk ) | (кортеж, k>=2) | |
| | [ exp1 , ... , expk ] | (список, k>=1) | |
| | [ exp1 [, exp2] .. [exp3] ] | (арифметическая последовательность) | |
| | [ exp | qual1 , ... , qualn ] | (описание списка, n>=1) | |
| | ( expi+1 qop(a,i) ) | (левое сечение) | |
| | ( lexpi qop(l,i) ) | (левое сечение) | |
| | ( qop(a,i)<-> expi+1 ) | (правое сечение) | |
| | ( qop(r,i)<-> rexpi ) | (правое сечение) | |
| | qcon { fbind1 , ... , fbindn } | (именованная конструкция, n>=0) | |
| | aexp<qcon> { fbind1 , ... , fbindn } | (именованное обновление, n >= 1) |
Перевод: | |||
выражение | -> | выражение0 :: [контекст =>] тип | (сигнатура типа выражения) |
| | выражение0 | ||
выражениеi | -> | выражениеi+1 [квалифицированный-оператор(n,i) выражениеi+1] | |
| | левое-сечение-выраженияi | ||
| | правое-сечение-выраженияi | ||
левое-сечение-выраженияi | -> | (левое-сечение-выраженияi | выражениеi+1) квалифицированный-оператор(l,i) выражениеi+1 | |
левое-сечение-выражения6 | -> | - выражение7 | |
правое-сечение-выраженияi | -> | выражениеi+1 квалифицированный-оператор(r,i) (правое-сечение-выраженияi | выражениеi+1) | |
выражение10 | -> | \ такой-как-образец1 ... такой-как-образецn -> выражение | (лямбда-абстракция, n>=1) |
| | let списки-объявлений in выражение | (let-выражение) | |
| | if выражение then выражение else выражение | (условное выражение) | |
| | case выражение of { список-альтернатив } | (case-выражение) | |
| | do { список-инструкций } | (do-выражение) | |
| | функциональное-выражение | ||
функциональное-выражение | -> | [функциональное-выражение] выражение-аргумента | (применение функции) |
выражение-аргумента | -> | квалифицированная-переменная | (переменная) |
| | общий-конструктор | (общий конструктор) | |
| | литерал | ||
| | ( выражение ) | (выражение в скобках) | |
| | ( выражение1 , ... , выражениеk ) | (кортеж, k>=2) | |
| | [ выражение1 , ... , выражениеk ] | (список, k>=1) | |
| | [ выражение1 [, выражение2] .. [выражение3] ] | (арифметическая последовательность) | |
| | [ выражение | квалификатор1 , ... , квалификаторn ] | (описание списка, n>=1) | |
| | ( выражениеi+1 квалифицированный-оператор(a,i) ) | (левое сечение) | |
| | ( левое-сечение-выраженияi квалифицированный-оператор(l,i) ) | (левое сечение) | |
| | ( квалифицированный-оператор(a,i)<-> выражениеi+1 ) | (правое сечение) | |
| | ( квалифицированный-оператор(r,i)<-> правое-сечение-выраженияi ) | (правое сечение) | |
| | квалифицированный-конструктор { связывание-имени-поля1 , ... , связывание-имени-поляn } | (именованная конструкция, n>=0) | |
| | выражение-аргумента<квалифицированный-конструктор> { связывание-имени-поля1 , ... , связывание-имени-поляn } | (именованное обновление, n>=1) |
Неоднозначность выражений, включая инфиксные операторы, разрешается с помощью ассоциативности и приоритета оператора (см. раздел 4.4.2). Следующие друг за другом операторы без скобок, имеющие один и тот же приоритет, должны быть оба либо левоассоциативными, либо правоассоциативными, во избежание синтаксической ошибки. Для заданного выражения без скобок "x qop(a,i) y qop(b,j) z", где qop --- оператор, часть "x qop(a,i) y" или "y qop(b,j) z" следует взять в скобки, когда i=j, за исключением a=b=l или a=b=r.
Отрицание является единственным префиксным оператором в Haskell , он имеет тот же приоритет, что и инфиксный оператор -, определенный в Prelude (см. раздел 4.4.2, рис. 4.1).
Грамматика является неоднозначной по отношению к пространству лямбда-абстракций, let-выражений и условных выражений. Неоднозначность разрешается с помощью мета-правила, согласно которому каждая из этих конструкций рассматривается слева направо насколько это возможно.
Примеры интерпретации при разборе показаны ниже.
Это | интерпретируется как |
f x + g y | (f x) + (g y) |
- f x + y | (- (f x)) + y |
let { ... } in x + y | let { ... } in (x + y) |
z + let { ... } in x + y | z + (let { ... } in (x + y)) |
f x y :: Int | (f x y) :: Int |
\ x -> a+b :: Int | \ x -> ((a+b) :: Int) |
Замечание относительно разбора. Выражения, которые затрагивают взаимодействие ассоциативности и приоритетов с применением мета-правила для let/лямбда,
могут оказаться трудными для разбора. Например, выражение
let x = True in x == x == True
не может означать
let x = True in (x == x == True)
потому что (==) является неассоциативным оператором, поэтому выражение должно быть интерпретировано таким образом:
(let x = True in (x == x)) == True
Тем не менее, реализации могут прекрасно обойтись дополнительным проходом после завершения разбора, чтобы обработать ассоциативность и приоритеты операторов, поэтому они могут спокойно передать предыдущий неправильный разбор. Мы советуем программистам избегать использования конструкций, чей разбор
затрагивает взаимодействие (отсутствия) ассоциативности с применением мета-правила для
let/лямбда.
Ради ясности остальная часть этого раздела описывает синтаксис выражений без указания их приоритетов.
Трансляции выражений Haskell используют error и undefined для явного указания мест, где могли возникнуть ошибки времени выполнения. Реальное поведение программы в случае возникновения ошибки зависит от реализации. Сообщения, передаваемые в функцию error при этих трансляциях, являются лишь указаниями, реализации могут выбирать между отображением большей или меньшей информации в случае возникновения ошибки.
aexp | -> | qvar | (переменная) |
| | gcon | (общий конструктор) | |
| | literal |
gcon | -> | () | |
| | [] | ||
| | (,{,}) | ||
| | qcon | ||
var | -> | varid | ( varsym ) | (переменная) |
qvar | -> | qvarid | ( qvarsym ) | (квалифицированная переменная) |
con | -> | conid | ( consym ) | (конструктор) |
qcon | -> | qconid | ( gconsym ) | (квалифицированный конструктор) |
varop | -> | varsym | `varid ` | (оператор переменной) |
qvarop | -> | qvarsym | `qvarid ` | (квалифицированный оператор переменной) |
conop | -> | consym | `conid ` | (оператор конструктора) |
qconop | -> | gconsym | `qconid ` | (квалифицированный оператор конструктора) |
op | -> | varop | conop | (оператор) |
qop | -> | qvarop | qconop | (квалифицированный оператор) |
gconsym | -> | : | qconsym |
Перевод: | |||
выражение-аргумента | -> | квалифицированная-переменная | (переменная) |
| | общий-конструктор | (общий конструктор) | |
| | литерал | ||
общий-конструктор | -> | () | |
| | [] | ||
| | (,{,}) | ||
| | квалифицированный-конструктор | ||
переменная | -> | идентификатор-переменной | ( символ-переменной ) | (переменная) |
квалифицированная-переменная | -> | квалифицированный-идентификатор-переменной | ( квалифицированный-символ-переменной ) | (квалифицированная переменная) |
конструктор | -> | идентификатор-конструктора | ( символ-конструктора ) | (конструктор) |
квалифицированный-конструктор | -> | квалифицированный-идентификатор-конструктора | ( символ-общего-конструктора ) | (квалифицированный конструктор) |
оператор-переменной | -> | символ-переменной | `идентификатор-переменной ` | (оператор переменной) |
квалифицированный-оператор-переменной | -> | квалифицированный-символ-переменной | `квалифицированный-идентификатор-переменной ` | (квалифицированный оператор переменной) |
оператор-конструктора | -> | символ-конструктора | `идентификатор-конструктора ` | (оператор конструктора) |
квалифицированный-оператор-конструктора | -> | символ-общего-конструктора | `квалифицированный-идентификатор-конструктора ` | (квалифицированный оператор конструктора) |
оператор | -> | оператор-переменной | оператор-конструктора | (оператор) |
квалифицированный-оператор | -> | квалифицированный-оператор-переменной | квалифицированный-оператор-конструктора | (квалифицированный оператор) |
символ-общего-конструктора | -> | : | квалифицированный-символ-конструктора |
Для поддержки инфиксной записи в Haskell используется специальный синтаксис. Оператор --- это функция, которая может быть применена, используя инфиксный синтаксис (раздел 3.4), или частично применена, используя сечения (раздел 3.5).
Оператор представляет собой символ оператора, например, + или $$, или обычный идентификатор, заключенный в обратные кавычки, например, `op`. Вместо префиксной записи op x y можно использовать инфиксную запись x `op` y. Если ассоциативность и приоритет для `op` не заданы, то по умолчанию используется наивысший приоритет и левоассоциативность (см. раздел 4.4.2).
Наоборот, символ оператора может быть преобразован в обычный идентификатор, если записать его в круглых скобках. Например, (+) x y эквивалентно x + y, а foldr (*) 1 xs эквивалентно foldr (\x y -> x*y) 1 xs.
Для обозначения некоторых конструкторов встроенных типов используется специальный синтаксис, как это видно из грамматики для gcon (общего-конструктора) и literal (литерала). Они описаны в разделе 6.1.
Целый литерал представляет собой применение функции fromInteger к соответствующему значению типа Integer. Аналогично, литерал с плавающей точкой обозначает применение функции fromRational к значению типа Rational (то есть Ratio Integer).
Трансляция:Целый литерал i эквивалентен fromInteger i, где fromInteger --- метод класса Num (см. раздел 6.4.1).Литерал с плавающей точкой f эквивалентен fromRational (n Ratio.% d), где fromRational --- метод класса Fractional, а Ratio.% составляет из двух целых чисел рациональное число в соответствии с определением, заданным в библиотеке Ratio. Если заданы целые числа n и d, то n/d = f. |
fexp | -> | [fexp] aexp | (применение функции) |
exp | -> | \ apat1 ... apatn -> exp | (лямбда-абстракция, n>=1) |
Перевод: | |||
функциональное-выражение | -> | [функциональное-выражение] выражение-аргумента | (применение функции) |
выражение | -> | \ такой-как-образец1 ... такой-как-образецn -> выражение | (лямбда-абстракция, n>=1) |
Применение функции записывается в виде e1 e2. Применение левоассоциативно, поэтому в (f x) y скобки можно опустить. Поскольку e1 может являться конструктором данных, возможно частичное применение конструкторов данных.
Лямбда-абстракции записываются в виде \ p1 ... pn -> e, где pi --- образцы. Выражение вида \x:xs->x синтаксически неправильно, его можно правильно записать в виде \(x:xs)->x.
Набор образцов должен быть линейным: ни одна переменная не должна появляться в наборе более одного раза.
Трансляция:Выполняются следующие тождества:
|
exp | -> | exp1 qop exp2 | |
| | - exp | (префиксное отрицание) | |
qop | -> | qvarop | qconop | (квалифицированный оператор) |
Перевод: | |||
выражение | -> | выражение1 квалифицированный-оператор выражение2 | |
| | - выражение | (префиксное отрицание) | |
квалифицированный-оператор | -> | квалифицированный-оператор-переменной | квалифицированный-оператор-конструктора | (квалифицированный оператор) |
Форма e1 qop e2 представляет собой инфиксное применение бинарного оператора qop к выражениям e1 и e2.
Специальная форма -e обозначает префиксное отрицание, единственный префиксный оператор в Haskell , и является синтаксической записью отрицания (e). Бинарный оператор - необязательно ссылается на определение - в Prelude, он может быть переопределен системой модуля. Тем не менее, унарный оператор - будет всегда ссылаться на функцию negate, определенную в Prelude. Нет никакой связи между локальным значением оператора - и унарным отрицанием.
Префиксный оператор отрицания имеет тот же приоритет, что и инфиксный оператор -, определенный в Prelude (см. таблицу 4.1). Поскольку e1-e2 при разборе интерпретируется как инфиксное применение бинарного оператора -, для альтернативной интерпретации нужно писать e1(-e2). Аналогично, (-) является синтаксической записью (\ x y -> x-y), как и любой инфиксный оператор, и не обозначает (\ x -> -x) --- для этого нужно использовать negate.
Трансляция:Выполняются следующие тождества:
|
aexp | -> | ( expi+1 qop(a,i) ) | (левое сечение) |
| | ( lexpi qop(l,i) ) | (левое сечение) | |
| | ( qop(a,i)<-> expi+1 ) | (правое сечение) | |
| | ( qop(r,i)<-> rexpi ) | (правое сечение) |
Перевод: | |||
выражение-аргумента | -> | ( выражениеi+1 квалифицированный-оператор(a,i) ) | (левое сечение) |
| | ( левое-сечение-выраженияi квалифицированный-оператор(l,i) ) | (левое сечение) | |
| | ( квалифицированный-оператор(a,i)<-> выражениеi+1 ) | (правое сечение) | |
| | ( квалифицированный-оператор(r,i)<-> правое-сечение-выраженияi ) | (правое сечение) |
Сечения записываются в виде ( op e ) или ( e op ), где op --- бинарный оператор, а e --- выражение. Сечения представляют собой удобный синтаксис для записи частичного применения бинарных операторов.
Синтаксические правила приоритетов применяются к сечениям следующим образом.
(op e) допустимо, если и только если (x op e) при разборе интерпретируется так же, как и (x op (e));
аналогично для (e op).
Например, (*a+b) синтаксически недопустимо, но (+a*b) и
(*(a+b)) допустимы. Поскольку оператор (+) левоассоциативен, (a+b+) синтаксически правильно,
а (+a+b) --- нет, его можно правильно записать в виде
(+(a+b)).
В качестве другого примера рассмотрим выражение
(let n = 10 in n +)
которое является недопустимым в соответствии с
мета-правилом для let/лямбда (раздел 3).
Выражение
(let n = 10 in n + x)
при разборе интерпретируется как
(let n = 10 in (n + x))
вместо
((let n = 10 in n) + x)
Поскольку - интерпретируется в грамматике специальным образом, (- exp) является не сечением, а применением префиксного оператора отрицания, в соответствии с описанием в предыдущем разделе. Тем не менее, имеется функция subtract, определенная в Prelude таким образом, что (subtract exp) эквивалентно недопустимому ранее сечению. Для той же цели может служить выражение (+ (- exp)).
Трансляция:Выполняются следующие тождества:
|
exp | -> | if exp1 then exp2 else exp3 |
Перевод: | ||
выражение | -> | if выражение1 then выражение2 else выражение3 |
Условное выражение имеет вид if e1 then e2 else e3 и возвращает: значение e2 --- если значение e1 равно True, e3 --- если e1 равно False, и _|_ --- иначе.
Трансляция:Выполняются следующие тождества:
|
exp | -> | exp1 qop exp2 | |
aexp | -> | [ exp1 , ... , expk ] | (k>=1) |
| | gcon | ||
gcon | -> | [] | |
| | qcon | ||
qcon | -> | ( gconsym ) | |
qop | -> | qconop | |
qconop | -> | gconsym | |
gconsym | -> | : |
Перевод: | |||
выражение | -> | выражение1 квалифицированный-оператор выражение2 | |
выражение-аргумента | -> | [ выражение1 , ... , выражениеk ] | (k>=1) |
| | общий-конструктор | ||
общий-конструктор | -> | [] | |
| | квалифицированный-конструктор | ||
квалифицированный-конструктор | -> | ( символ-общего-конструктора ) | |
квалифицированный-оператор | -> | квалифицированный-оператор-конструктора | |
квалифицированный-оператор-конструктора | -> | символ-общего-конструктора | |
символ-общего-конструктора | -> | : |
Списки записываются в виде [e1, ..., ek], где k>=1. Конструктором списка является :, пустой список обозначается []. Стандартные операции над списками описаны в Prelude (см. раздел 6.1.3 и главу 8, особенно раздел 8.1).
Трансляция:Выполняются следующие тождества:
|
aexp | -> | ( exp1 , ... , expk ) | (k>=2) |
| | qcon | ||
qcon | -> | (,{,}) |
Перевод: | |||
выражение-аргумента | -> | ( выражение1 , ... , выражениеk ) | (k>=2) |
| | квалифицированный-конструктор | ||
квалифицированный-конструктор | -> | (,{,}) |
Кортежи записываются в виде (e1, ..., ek) и могут быть произвольной длины k>=2. Конструктор для кортежа размера n обозначается (,...,), где n-1 запятых. Таким образом, (a,b,c) и (,,) a b c обозначают одно и то же значение. Стандартные операции над кортежами описаны в Prelude (см. раздел 6.1.4 и главу 8).
Трансляция:(e1, ..., ek) для k>=2 является экземпляром кортежа размера k, в соответствии с определением в Prelude, и не требует трансляции. Если t1, ... , tk --- соответственно типы e1, ... , ek, то типом кортежа будет (t1, ..., tk) (см. раздел 4.1.2). |
aexp | -> | gcon |
| | ( exp ) | |
gcon | -> | () |
Перевод: | ||
выражение-аргумента | -> | общий-конструктор |
| | ( выражение ) | |
общий-конструктор | -> | () |
Выражение вида (e) представляет собой просто выражение в скобках и эквивалентно e. Единичное выражение () имеет тип () (см. раздел 4.1.2). Этот единственный член указанного типа, отличный от _|_, может рассматриваться как "кортеж нулевого размера" (см. раздел 6.1.5).
Трансляция:(e) эквивалентно e. |
aexp | -> | [ exp1 [, exp2] .. [exp3] ] |
Перевод: | ||
выражение-аргумента | -> | [ выражение1 [, выражение2] .. [выражение3] ] |
Арифметическая последовательность [e1, e2 .. e3] обозначает список значений типа t, где каждое из выражений ei имеет тип t, и t является экземпляром класса Enum.
Трансляция:Для арифметических последовательностей выполняются следующие тождества:
|
Семантика арифметических последовательностей поэтому полностью зависит от объявления экземпляра для типа t. Для получения более детальной информации о том, какие типы Prelude являются подтипами Enum и какова их семантика, смотрите раздел 6.3.4.
aexp | -> | [ exp | qual1 , ... , qualn ] | (описание списка, n>=1) |
qual | -> | pat <- exp | (генератор) |
| | let decls | (локальное объявление) | |
| | exp | (страж) |
Перевод: | |||
выражение-аргумента | -> | [ выражение | квалификатор1 , ... , квалификаторn ] | (описание списка, n>=1) |
квалификатор | -> | образец <- выражение | (генератор) |
| | let списки-объявлений | (локальное объявление) | |
| | выражение | (страж) |
Описание списка имеет вид [ e | q1, ..., qn ], n>=1, где квалификаторы qi являются
Такое описание списка возвращает список элементов,
порожденный путем вычисления e в последовательных окружениях,
созданных вложенным вычислением вглубину генераторов в
списке квалификаторов. Связывание имен переменных происходит согласно
правилам обычного сопоставления с образцом (см. раздел 3.17), и если сопоставление завершится неудачей,
то соответствующий элемент списка будет просто пропущен. Таким образом,
[ x | xs <- [ [(1,2),(3,4)], [(5,4),(3,2)] ],
(3,x) <- xs ]
порождает список [4,2]. Если квалификатор является стражем, то, для того чтобы предшествующее сопоставление с образцом завершилось успешно, необходимо, чтобы значение квалификатора равнялось
True.
Как обычно, связывания имен в описаниях списков могут скрыть связывания имен во внешних
областях видимости, например,
[ x | x <- x, x <- x ] | = | [ z | y <- x, z <- y] |
Трансляция:Для описаний списков выполняются следующие тождества, которые могут быть использованы в качестве трансляции в ядро:
|
Как показывает трансляция описаний списков, переменные, связанные с помощью let, имеют полностью полиморфные типы, тогда как переменные, определенные с помощью <-, связаны лямбда-выражением и поэтому мономорфны (см. раздел 4.5.4).
exp | -> | let decls in exp |
Перевод: | ||
выражение | -> | let списки-объявлений in выражение |
Let-выражения имеют общий вид
let { d1 ; ... ; dn } in e
и вводят
вложенный, лексически ограниченный,
взаимно рекурсивный список объявлений (в других языках let часто называют letrec). Областью видимости объявлений является выражение e
и правая часть объявлений. Объявления
описаны в главе 4. Сопоставление и связывание образцов выполняется лениво, неявная ~ делает эти образцы
неопровержимыми.
Например,
let (x,y) = undefined in e
не вызовет ошибку времени выполнения до тех пор, пока x или y не будут вычислены.
Трансляция:Динамическая семантика выражения let { d1 ; ... ; dn } в e0 охватывается следующей трансляцией: после удаления всех сигнатур типов каждое объявление di транслируется в уравнение вида pi = ei, где pi и ei --- соответственно образцы и выражения, при этом используется трансляция в разделе 4.4.3. Однажды сделав это, эти тождества выполняются, и это можно использовать в качестве трансляции в ядро:
|
exp | -> | case exp of { alts } | |
alts | -> | alt1 ; ... ; altn | (n>=1) |
alt | -> | pat -> exp [where decls] | |
| | pat gdpat [where decls] | ||
| | (пустая альтернатива) | ||
gdpat | -> | gd -> exp [ gdpat ] | |
gd | -> | | exp0 |
Перевод: | |||
выражение | -> | case выражение of { список-альтернатив } | |
список-альтернатив | -> | альтернатива1 ; ... ; альтернативаn | (n>=1) |
альтернатива | -> | образец -> выражение [where список-объявлений] | |
| | образец образец-со-стражами [where список-объявлений] | ||
| | (пустая альтернатива) | ||
образец-со-стражами | -> | страж -> выражение [ образец-со-стражами ] | |
страж | -> | | выражение0 |
Case-выражение имеет общий вид
case e of { p1 match1 ; ... ; pn matchn }
где каждый matchi имеет общий вид
| gi1 | -> ei1 | |
... | ||
| gimi | -> eimi | |
where declsi |
(Заметьте, что в синтаксическом правиле для gd "|" является терминальным символом, а не синтаксическим мета-символом для указания альтернатив.) Каждая альтернатива pi matchi состоит из образца pi и его сопоставлений matchi. Каждое сопоставление, в свою очередь, состоит из последовательности пар стражей gij и тел eij (выражений), за которыми следуют необязательные связывания (declsi), чья область видимости распространяется над всеми стражами и выражениями альтернативы. Альтернатива вида
pat -> exp where decls
интерпретируется как краткая запись для
pat | True | -> exp | |
where decls |
Case-выражение должно иметь по крайней мере одну альтернативу, и каждая альтернатива должна иметь по крайней мере одно тело. Каждое тело должно иметь один и тот же тип, и все выражение должно быть того же типа.
Вычисление case-выражения выполняется посредством сопоставления выражения e отдельным альтернативам. Альтернативы проверяются последовательно, сверху вниз. Если e соответствует образцу в альтернативе, выполняется связывание переменных, сначала указанных в образце, а затем --- с помощью declsi в операторе where, связанном с этой альтернативой. Если значение одного из вычисляемых стражей окажется True, в том же окружении, что и страж, будет вычислена соответствующая правая часть. Если значения всех стражей окажутся False, процесс сопоставления с образцом будет возобновлен со следующей альтернативы. Если не удастся сопоставить ни один образец, результатом будет _|_. Сопоставление с образцом описано в разделе 3.17, а формальная семантика case-выражений --- в разделе 3.17.3.
Замечание о разборе. Выражение
case x of { (a,_) | let b = not a in b :: Bool -> a }
нелегко правильно интерпретировать при разборе. Оно имеет единственную однозначную интерпретацию, а именно:
case x of { (a,_) | (let b = not a in b :: Bool) -> a }
Тем не менее, выражение Bool -> a является синтаксически правильным типом, и
синтаксические анализаторы с ограниченным предварительным просмотром могут выбрать этот неправильный вариант, и тогда программа будет признана недопустимой.
Поэтому мы советуем программистам избегать использования стражей, которые
заканчиваются указанием сигнатуры типа, именно поэтому gd содержит
exp0, а не не exp.
exp | -> | do { stmts } | (do-выражение) |
stmts | -> | stmt1 ... stmtn exp [;] | (n>=0) |
stmt | -> | exp ; | |
| | pat <- exp ; | ||
| | let decls ; | ||
| | ; | (пустая инструкция) |
Перевод: | |||
выражение | -> | do { список-инструкций } | (do-выражение) |
список-инструкций | -> | инструкция1 ... инструкцияn выражение [;] | (n>=0) |
инструкция | -> | выражение ; | |
| | образец <- выражение ; | ||
| | let список-объявлений ; | ||
| | ; | (пустая инструкция) |
Do-выражения предоставляют более удобный синтаксис для монадического программирования.
Оно позволяет записать такое выражение
putStr "x: " >>
getLine >>= \l ->
return (words l)
в более традиционном виде:
do putStr "x: "
l <- getLine
return (words l)
Трансляция:Для do-выражений выполняются следующие тождества, которые, после удаления пустых stmts, можно использовать в качестве трансляции в ядро:
|
Различные типы данных не могут совместно использовать общие имена полей в одной области видимости.
Имя поля можно использовать не более одного раза в конструкторе.
В пределах типа данных, тем не менее, имя поля можно использовать в более
чем одном конструкторе, при условии, что поле имеет один и тот же тип во всех
конструкторах. Для того чтобы проиллюстрировать последнее замечание, рассмотрим:
data S = S1 { x :: Int } | S2 { x :: Int } -- OK
data T = T1 { y :: Int } | T2 { y :: Bool } -- ПЛОХО
Здесь S является допустимым типом данных, а T --- нет, потому что в последнем случае для y указан другой тип, противоречащий указанному ранее.
aexp | -> | qvar |
Перевод: | ||
выражение-аргумента | -> | квалифицированная-переменная |
Имена полей используются в качестве селекторной функции. Когда имя поля используется в качестве переменной, оно действует как функция, которая извлекает поле из объекта. Селекторы являются связываниями верхнего уровня, и поэтому они могут быть перекрыты локальными переменными, но не могут конфликтовать с другими связываниями верхнего уровня с тем же именем. Это сокрытие затрагивает только селекторные функции, при создании записей (раздел 3.15.2) и их обновлении (раздел 3.15.3) имена полей не могут быть спутаны с обычными переменными.
Трансляция:Имя поля f представляет собой селекторную функцию в соответствии с определением:
|
aexp | -> | qcon { fbind1 , ... , fbindn } | (именованная конструкция, n>=0) |
fbind | -> | qvar = exp |
Перевод: | |||
выражение-аргумента | -> | квалифицированный-конструктор { связывание-имени-поля1 , ... , связывание-имени-поляn } | (именованная конструкция, n>=0) |
связывание-имени-поля | -> | квалифицированная-переменная = выражение |
Конструктор с именованными полями может использоваться для создания значения, в котором компоненты задаются именем, а не позицией. В отличие от фигурных скобок, используемых в списках объявлений, здесь присутствие фигурных скобок не зависит от размещения текста, символы { и } должны использоваться явно. (То же самое относится к обновлению полей и образцам полей.) Создание объектов с использованием имен полей подчинено следующим ограничениям:
Трансляция:В связывании f = v поле f именует v.
Вспомогательная функция pickCi bs d определена следующим образом: Если i-ый компонент конструктора C имеет имя поля f и если f=v появляется в списке связываний bs, то pickCi bs d равно v. Иначе pickCi bs d равно значению по умолчанию d.
|
aexp | -> | aexp<qcon> { fbind1 , ... , fbindn } | (именованное обновление, n>=1) |
Перевод: | |||
выражение-аргумента | -> | выражение-аргумента<квалифицированный-конструктор> { связывание-имени-поля1 , ... , связывание-имени-поляn } | (именованное обновление, n>=1) |
Значения, принадлежащие типу данных с именованными полями, можно обновлять, не боясь разрушить структуру данных. При этом создается новое значение, в котором заданные значения полей замещают те, что были в существующем значении. Обновления подчиняются следующим правилам:
Трансляция:Используя предыдущее определение функции pick,
|
Выражение | Трансляция |
C1 {f1 = 3} | C1 3 undefined |
C2 {f1 = 1, f4 = 'A', f3 = 'B'} | C2 1 'B' 'A' |
x {f1 = 1} | case x of C1 _ f2 -> C1 1 f2 |
C2 _ f3 f4 -> C2 1 f3 f4 |
Поле f1 является общим для обоих конструкторов в T. Этот пример транслирует выражения, использующие конструкторы, записываемые с именами полей, в эквивалентные выражения, использующие те же самые конструкторы без имен полей. Если не будет единого конструктора, который определяет набор имен полей в обновлении, такого как x {f2 = 1, f3 = 'x'}, произойдет ошибка компиляции.
exp | -> | exp :: [context =>] type |
Перевод: | ||
выражение | -> | выражение :: [контекст =>] тип |
Сигнатуры типов выражений имеют вид e :: t, где e --- выражение, а t --- тип (раздел 4.1.2); они используются для явного указания типа выражения, в частности, для того чтобы разрешить неоднозначность типов из-за перегрузки (см. раздел 4.3.4). Значением выражения является значение exp. Как и с обычными сигнатурами типов (см. раздел 4.4.1), объявленный тип может быть более частным, чем основной тип, выводимый из exp, но будет ошибкой указать тип, который окажется более общим или не сопоставимым с основным типом.
Трансляция:
|
Образцы появляются в лямбда-абстракциях, определениях функций, связываниях с образцом, описаниях списков, do-выражениях и case-выражениях. Тем не менее, первые пять из них в конечном счете транслируются в case-выражения, поэтому достаточно ограничиться определением семантики сопоставления с образцом для case-выражений.
Образцы имеют следующий синтаксис:
pat | -> | var + integer | (образец упорядочивания) |
| | pat0 | ||
pati | -> | pati+1 [qconop(n,i) pati+1] | |
| | lpati | ||
| | rpati | ||
lpati | -> | (lpati | pati+1) qconop(l,i) pati+1 | |
lpat6 | -> | - (integer | float) | (отрицательный литерал) |
rpati | -> | pati+1 qconop(r,i) (rpati | pati+1) | |
pat10 | -> | apat | |
| | gcon apat1 ... apatk | (число аргументов конструктора gcon = k, k>=1) | |
apat | -> | var [@ apat] | ("такой как"-образец) |
| | gcon | (число аргументов конструктора gcon = 0) | |
| | qcon { fpat1 , ... , fpatk } | (именованный образец, k>=0) | |
| | literal | ||
| | _ | (любые символы) | |
| | ( pat ) | (образец в скобках) | |
| | ( pat1 , ... , patk ) | (образец кортежа, k>=2) | |
| | [ pat1 , ... , patk ] | (образец списка, k>=1) | |
| | ~ apat | (неопровержимый образец) | |
fpat | -> | qvar = pat |
Перевод: | |||
образец | -> | переменная + целый-литерал | (образец упорядочивания) |
| | образец0 | ||
образецi | -> | образецi+1 [квалифицированный-оператор-конструктора(n,i) образецi+1] | |
| | левый-образецi | ||
| | правый-образецi | ||
левый-образецi | -> | (левый-образецi | образецi+1) квалифицированный-оператор-конструктора(l,i) образецi+1 | |
левый-образец6 | -> | - (целый-литерал | литерал-с-плавающей-точкой) | (отрицательный литерал) |
правый-образецi | -> | образецi+1 квалифицированный-оператор-конструктора(r,i) (правый-образецi | образецi+1) | |
образец10 | -> | такой-как-образец | |
| | общий-конструктор такой-как-образец1 ... такой-как-образецk | (число аргументов конструктора gcon = k, k>=1) | |
такой-как-образец | -> | переменная [@ такой-как-образец] | ("такой как"-образец) |
| | общий-конструктор | (число аргументов конструктора gcon = 0) | |
| | квалифицированный-конструктор { образец-с-именем1 , ... , образец-с-именемk } | (именованный образец, k>=0) | |
| | литерал | ||
| | _ | (любые символы) | |
| | ( образец ) | (образец в скобках) | |
| | ( образец1 , ... , образецk ) | (образец кортежа, k>=2) | |
| | [ образец1 , ... , образецk ] | (образец списка, k>=1) | |
| | ~ такой-как-образец | (неопровержимый образец) | |
образец-с-именем | -> | квалифицированная-переменная = образец |
Число аргументов конструктора должно соответствовать числу образцов, связанных с ним; нельзя сопоставлять частично примененный конструктор.
Все образцы должны быть линейными
: ни одна переменная не может появляться более одного раза.
Например, следующее определение недопустимо:
f (x,x) = x -- ЗАПРЕЩЕНО; x дважды используется в образце
Образцы вида var@pat называются "такими как"-образцами,
и позволяют использовать var
в качестве имени для значения, сопоставляемого pat. Например,
case e of { xs@(x:rest) -> if x==0 then rest else xs }
эквивалентено
let { xs = e } in
case xs of { (x:rest) -> if x==0 then rest else xs }
Образцы вида _ обозначают
группы любых символов и полезны, когда некоторая часть образца
не используется в правой части. Это как если бы
идентификатор, не используемый где-либо в другом месте, был помещен на свое место. Например,
case e of { [x,_,_] -> if x==0 then True else False }
эквивалентно
case e of { [x,y,z] -> if x==0 then True else False }
Образцы сопоставляются значениям. Попытка сопоставить образец может иметь один из трех результатов: потерпеть неудачу, иметь успех, при этом каждая переменная в образце связывается с соответствующим значением, или быть отклонена (т.е. вернуть _|_). Сопоставление с образцом выполняется слева направо и извне вовнутрь, в соответствии со следующими правилами:
С точки зрения операций, это означает, что никакое сопоставление не будет сделано с образцом ~apat до тех пор, пока одна из переменных в apat не будет использована. В этот момент весь образец сопоставляется значению, и если сопоставление потерпит неудачу или будет отклонено, так выполняется все вычисление.
Интерпретация числовых литералов в точности описана в разделе 3.2, то есть перегруженная функция fromInteger или fromRational применяется к литералу типа Integer или Rational (соответственно) для преобразования его к соответствующему типу.
Интерпретация литерала k является точно такой же, как и для числовых литералов, за исключением того, что допустимы только целые литералы.
Помимо очевидных ограничений статических типов (например, статической ошибкой является сопоставление символа с булевским значением), выполняются следующие ограничения статических классов:
Многие люди считают, что n+k-образцы не следует использовать. Эти образцы могут быть удалены или изменены в будущих версиях Haskell .
Иногда полезно различать два вида образцов. Сопоставление с неопровержимым образцом не является строгим: образец сопоставляется, даже если сопоставляемое значение равно _|_. Сопоставление с опровержимым образцом является строгим: если сопоставляемое значение равно _|_, сопоставление будет отклонено. Неопровержимыми являются следующие образцы: переменная, символ подчеркивания, N apat, где N --- конструктор, определенный с помощью newtype, а apat --- неопровержимый образец (см. раздел 4.2.3), var@apat, где apat --- неопровержимый образец, и образцы вида ~apat (независимо от того, является ли apat неопровержимым образцом или нет). Все остальные образцы являются опровержимыми.
Приведем несколько примеров:
Образцы верхнего уровня в case-выражениях и набор образцов верхнего уровня в связываниях имен функций или образцах могут иметь ноль или более связанных с ними стражей. Страж --- это булево выражение, которое вычисляется только после того, как все аргументы были успешно сопоставлены, и, для того чтобы все сопоставление с образцом имело успех, значением этого выражения должно быть истина. Окружением стража является то же окружение, что и для правой части альтернативы case-выражения, определения функции или связывания с образцом, к которому он прикреплен.
Семантика стража имеет очевидное влияние на
характеристики строгости функции или case-выражения. В
частности, из-за стража может быть вычислен иной неопровержимый образец
. Например, в
f :: (Int,Int,Int) -> [Int] -> Int
f ~(x,y,z) [a] | (a == y) = 1
и a, и y будут вычислены с помощью == в страже.
Семантика всех конструкций сопоставления с образцом, отличных от case-выражений, определена с помощью тождеств, которые устанавливают связь этих конструкций с case -выражениями. В свою очередь, семантика самих case-выражений задана в виде последовательностей тождеств на рис. 3.1--3.2. Любая реализация должна обеспечивать выполнение этих тождеств; при этом не ожидается, что она будет использовать их непосредственно, поскольку это могло бы привести к генерации довольно неэффективного кода.
Рис. 4Семантика case-выражений, часть 2 |
На рис. 3.1 - 3.2: e, e' и ei --- выражения, g и gi --- булевы выражения, p и pi --- образцы, v, x и xi --- переменные, K и K' --- конструкторы алгебраических типов данных (data) (включая конструкторы кортежей), а N --- конструктор newtype.
Правило (b) соответствует основному case-выражению исходного языка, независимо от того, включает ли оно стражей: если стражи не указаны, то в формах matchi вместо стражей gi,j будет подставлено True. Последующие тождества управляют полученным case-выражением все более и более простой формы.
Правило (h) на рис. 3.2 затрагивает перегруженный оператор ==; именно это правило определяет смысл сопоставления образца с перегруженными константами.
Все эти тождества сохраняют статическую семантику. Правила (d), (e), (j), (q) и (s) используют лямбду, а не let; это указывает на то, что переменные, связанные с помощью case, являются мономорфными (раздел 4.1.4).