Описание Haskell 98: Выражения Описание Haskell 98
наверх | назад | вперед | содержание | предметный указатель функций

3  Выражения

В этой главе мы опишем синтаксис и неформальную семантику выражений 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) )
на самом деле обозначает 30 правил вывода с 10 подстановками для i и 3 для a.

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/лямбда.

Ради ясности остальная часть этого раздела описывает синтаксис выражений без указания их приоритетов.

3.1  Ошибки

Ошибки во время вычисления выражений, обозначаемые как _|_, в программе на Haskell не отличимы от незавершенного вычисления. Поскольку Haskell является языком с нестрогой типизацией данных, все типы Haskell включают _|_. Другими словами, значение любого типа может быть связано с вычислением, которое будет завершено, когда потребуется результат вычисления, и завершится с ошибкой. При своем вычислении ошибки вызывают немедленное завершение программы и не могут быть отловлены пользователем. Prelude содержит две функции, которые сразу вызывают такие ошибки:

error     :: String -> a
undefined :: a

Вызов функции error завершает выполнение программы и возвращает в операционную систему соответствующий признак ошибки. Он также должен вывести на экран строку некоторым, зависящим от системы, способом. Когда используется функция undefined, сообщение об ошибке создается компилятором.

Трансляции выражений Haskell используют error и undefined для явного указания мест, где могли возникнуть ошибки времени выполнения. Реальное поведение программы в случае возникновения ошибки зависит от реализации. Сообщения, передаваемые в функцию error при этих трансляциях, являются лишь указаниями, реализации могут выбирать между отображением большей или меньшей информации в случае возникновения ошибки.

3.2  Переменные, конструкторы, операторы и литералы

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.

3.3  Производные функции и лямбда-абстракции

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.

Набор образцов должен быть линейным: ни одна переменная не должна появляться в наборе более одного раза.

Трансляция:

Выполняются следующие тождества:
\ p1 ... pn -> e = \ x1 ... xn -> case (x1, ..., xn) of (p1, ..., pn) -> e
где xi --- новые идентификаторы.
Используя эту трансляцию в комбинации с с семантикой case-выражений и сопоставлений с образцом, описанной в разделе 3.17.3, получим: если сопоставление с образцом завершится неудачно, то результатом будет _|_.

3.4  Применение операторов

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.

Трансляция:

Выполняются следующие тождества:
e1 op e2 = (op) e1 e2
-e = negate (e)

3.5  Сечения

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

Трансляция:

Выполняются следующие тождества:
(op e) = \ x -> x op e
(e op) = \ x -> e op x
где op --- бинарный оператор, e --- выражение, а x --- переменная, которая не является свободной в e.

3.6  Условные выражения

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, и _|_ --- иначе.

Трансляция:

Выполняются следующие тождества:
if e1 then e2 else e3 = case e1 of { True -> e2 ; False -> e3 }
где True и False --- конструкторы с нулевым числом аргументов из типа Bool, определенные в Prelude. Тип e1 должен быть Bool, e2 и e3 должны иметь тот же тип, который также является типом всего условного выражения.

3.7  Списки

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

Трансляция:

Выполняются следующие тождества:
[e1, ..., ek] = e1 : (e2 : ( ... (ek : [])))
где : и [] --- конструкторы списков, определенные в Prelude (см. раздел 6.1.3). Выражения e1, .... , ek должны быть одного типа (назовем его t), а типом всего выражения является [t] (см. раздел 4.1.2).
Конструктор ":" предназначен исключительно для построения списка; как и [], он является обоснованной частью синтаксиса языка и не может быть скрыт или переопределен. Он представляет собой правоассоциативный оператор с уровнем приоритета 5 (раздел 4.4.2).

3.8  Кортежи

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

3.9  Единичные выражения и выражения в скобках

aexp -> gcon
| ( exp )
gcon -> ()

Перевод:
выражение-аргумента -> общий-конструктор
| ( выражение )
общий-конструктор -> ()

Выражение вида (e) представляет собой просто выражение в скобках и эквивалентно e. Единичное выражение () имеет тип () (см. раздел 4.1.2). Этот единственный член указанного типа, отличный от _|_, может рассматриваться как "кортеж нулевого размера" (см. раздел 6.1.5).

Трансляция:

(e) эквивалентно e.

3.10  Арифметические последовательности

aexp -> [ exp1 [, exp2] .. [exp3] ]

Перевод:
выражение-аргумента -> [ выражение1 [, выражение2] .. [выражение3] ]

Арифметическая последовательность [e1, e2 .. e3] обозначает список значений типа t, где каждое из выражений ei имеет тип t, и t является экземпляром класса Enum.

Трансляция:

Для арифметических последовательностей выполняются следующие тождества:
e1.. ] = enumFrom e1
e1,e2.. ] = enumFromThen e1 e2
e1..e3 ] = enumFromTo e1 e3
e1,e2..e3 ] = enumFromThenTo e1 e2 e3
где enumFrom, enumFromThen, enumFromTo и enumFromThenTo являются методами класса Enum, определенные в Prelude (см. рис. 6.1).

Семантика арифметических последовательностей поэтому полностью зависит от объявления экземпляра для типа t. Для получения более детальной информации о том, какие типы Prelude являются подтипами Enum и какова их семантика, смотрите раздел 6.3.4.

3.11  Описание списка

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]

Трансляция:

Для описаний списков выполняются следующие тождества, которые могут быть использованы в качестве трансляции в ядро:
e | True ] = [e]
e | q ] = [ e | q, True ]
e | b, Q ] = if b then e | Q ] else []
e | p <- l, Q ] = let ok p = e | Q ]
    ok _ = []
in concatMap ok l
e | let decls, Q ] = let decls in e | Q ]
где e --- выражения, p --- образцы, l --- выражения, значениями которых являются списки, b --- булевы выражения, decls --- списки объявлений, q --- квалификаторы, а Q --- последовательности квалификаторов. ok --- новая переменная. Функция concatMap и булево значение True определены в Prelude.

Как показывает трансляция описаний списков, переменные, связанные с помощью let, имеют полностью полиморфные типы, тогда как переменные, определенные с помощью <-, связаны лямбда-выражением и поэтому мономорфны (см. раздел 4.5.4).

3.12  Let-выражения

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. Однажды сделав это, эти тождества выполняются, и это можно использовать в качестве трансляции в ядро:
let {p1=e1 ... pn=en} in e0 = let (~p1, ... ,~pn) = (e1, ... ,en) in e0
let p = e1  in  e0 = case e1 of ~p -> e0
где ни одна переменная в p не является свободной в e1
let p = e1  in  e0 = let p = fix ( \ ~p -> e1) in e0
где fix --- наименьший ассоциативный оператор. Обратите внимание на использование неопровержимого образца ~p. Эта трансляция не сохраняет статическую семантику, потому что использование case препятствует полностью полиморфной типизации связанных переменных. Статическая семантика связываний имен в let-выражениях описана разделе 4.4.3.

3.13  Case-выражения

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.

3.14  Do-выражения

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, можно использовать в качестве трансляции в ядро:
do {e} = e
do {e;stmts} = e >> do {stmts}
do {p <- estmts} = let ok p = do {stmts}
    ok _ = fail "..."
  in e >>= ok
do {let declsstmts} = let decls in do {stmts}
Пропуски "..." обозначают генерируемое компилятором сообщение об ошибке, передаваемое функции fail, желательно давая некоторое указание на местоположение ошибки сопоставления с образцом; функции >>, >>= и fail являются операциями в классе Monad, определенными в Prelude; ok является новым идентификатором.
Как показано в трансляции do, переменные, связанные let, имеют полностью полиморфные типы, тогда как те переменные, которые определены с помощью <-, являются связанными лямбда-выражением и поэтому являются мономорфными.

3.15  Типы данных с именованными полями

Объявление типа данных может содержать необязательные определения имен полей (см. раздел 4.2.1). Эти имена полей можно использовать для создания, извлечения и обновления полей способом, который не зависит от всей структуры типа данных.

Различные типы данных не могут совместно использовать общие имена полей в одной области видимости. Имя поля можно использовать не более одного раза в конструкторе. В пределах типа данных, тем не менее, имя поля можно использовать в более чем одном конструкторе, при условии, что поле имеет один и тот же тип во всех конструкторах. Для того чтобы проиллюстрировать последнее замечание, рассмотрим:

  data S = S1 { x :: Int } | S2 { x :: Int }   -- OK
  data T = T1 { y :: Int } | T2 { y :: Bool }  -- ПЛОХО

Здесь S является допустимым типом данных, а T --- нет, потому что в последнем случае для y указан другой тип, противоречащий указанному ранее.

3.15.1  Извлечение полей

aexp -> qvar

Перевод:
выражение-аргумента -> квалифицированная-переменная

Имена полей используются в качестве селекторной функции. Когда имя поля используется в качестве переменной, оно действует как функция, которая извлекает поле из объекта. Селекторы являются связываниями верхнего уровня, и поэтому они могут быть перекрыты локальными переменными, но не могут конфликтовать с другими связываниями верхнего уровня с тем же именем. Это сокрытие затрагивает только селекторные функции, при создании записей (раздел 3.15.2) и их обновлении (раздел 3.15.3) имена полей не могут быть спутаны с обычными переменными.

Трансляция:

Имя поля f представляет собой селекторную функцию в соответствии с определением:
f x =case x of { C1 p11 ...p1k  ->  e1 ; ... ; Cn pn1 ...pnk  ->  en }
где все C1 ...Cn --- конструкторы типа данных, содержащие поле с именем f, pij --- это y, когда f именует собой j-ю компоненту Ci, или _ иначе, а ei --- это y, когда некоторое поле в Ci имеет имя f, или undefined иначе.

3.15.2  Создание типов данных с использованием имен полей

aexp -> qcon { fbind1 , ... , fbindn } (именованная конструкция, n>=0)
fbind -> qvar = exp

Перевод:
выражение-аргумента -> квалифицированный-конструктор { связывание-имени-поля1 , ... , связывание-имени-поляn } (именованная конструкция, n>=0)
связывание-имени-поля -> квалифицированная-переменная = выражение

Конструктор с именованными полями может использоваться для создания значения, в котором компоненты задаются именем, а не позицией. В отличие от фигурных скобок, используемых в списках объявлений, здесь присутствие фигурных скобок не зависит от размещения текста, символы { и } должны использоваться явно. (То же самое относится к обновлению полей и образцам полей.) Создание объектов с использованием имен полей подчинено следующим ограничениям:

Выражение F {}, где F --- конструктор данных, является допустимым независимо от того, было или нет F объявлено с использованием синтаксиса записи (при условии, что F не имеет обязательных полей, см. пункт третий в приведенном выше списке); оно обозначает F _|_1 ... _|_n, где n --- число аргументов F.

Трансляция:

В связывании f = v поле f именует v.
C { bs } = C (pickC1 bs undefined) ...(pickCk bs undefined)
где k --- число аргументов C.

Вспомогательная функция pickCi bs d определена следующим образом:

Если i-ый компонент конструктора C имеет имя поля f и если f=v появляется в списке связываний bs, то pickCi bs d равно v. Иначе pickCi bs d равно значению по умолчанию d.

3.15.3  Обновления с использованием имен полей

aexp -> aexp<qcon> { fbind1 , ... , fbindn } (именованное обновление, n>=1)

Перевод:
выражение-аргумента -> выражение-аргумента<квалифицированный-конструктор> { связывание-имени-поля1 , ... , связывание-имени-поляn } (именованное обновление, n>=1)

Значения, принадлежащие типу данных с именованными полями, можно обновлять, не боясь разрушить структуру данных. При этом создается новое значение, в котором заданные значения полей замещают те, что были в существующем значении. Обновления подчиняются следующим правилам:

Трансляция:

Используя предыдущее определение функции pick,
e { bs } = case e of
        C1 v1 ... vk1 -> C1 (pickC11 bs v1) ... (pickC1k1 bs vk1)
             ...
        Cj v1 ... vkj -> Cj (pickCj1 bs v1) ... (pickCjkj bs vkj)
        _ -> error "Ошибка обновления"
где {C1,...,Cj} --- набор конструкторов, содержащих все имена в bs, а ki --- число аргументов Ci.
Вот некоторые примеры, использующие именованные поля:

data T    = C1 {f1,f2 :: Int}
          | C2 {f1 :: Int,
                f3,f4 :: Char}

Выражение Трансляция
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'}, произойдет ошибка компиляции.

3.16  Сигнатуры типов выражений

exp -> exp :: [context =>] type

Перевод:
выражение -> выражение :: [контекст =>] тип

Сигнатуры типов выражений имеют вид e :: t, где e --- выражение, а t --- тип (раздел 4.1.2); они используются для явного указания типа выражения, в частности, для того чтобы разрешить неоднозначность типов из-за перегрузки (см. раздел 4.3.4). Значением выражения является значение exp. Как и с обычными сигнатурами типов (см. раздел 4.4.1), объявленный тип может быть более частным, чем основной тип, выводимый из exp, но будет ошибкой указать тип, который окажется более общим или не сопоставимым с основным типом.

Трансляция:

e :: t = let { v :: t v = e } in v

3.17  Сопоставление с образцом

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

3.17.1  Образцы

Образцы имеют следующий синтаксис:

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 }

3.17.2  Неформальная семантика сопоставления с образцом

Образцы сопоставляются значениям. Попытка сопоставить образец может иметь один из трех результатов: потерпеть неудачу, иметь успех, при этом каждая переменная в образце связывается с соответствующим значением, или быть отклонена (т.е. вернуть _|_). Сопоставление с образцом выполняется слева направо и извне вовнутрь, в соответствии со следующими правилами:

  1. Сопоставление образца var значению v всегда имеет успех и связывает var с v.
  2. Сопоставление образца ~apat значению v всегда имеет успех. Свободные переменные в apat связываются с соответствующими значениями, если сопоставление apat с v завершится успешно, или с _|_, если сопоставление apat с v потерпит неудачу или будет отклонено. (Связывание не подразумевает вычисление.)

    С точки зрения операций, это означает, что никакое сопоставление не будет сделано с образцом ~apat до тех пор, пока одна из переменных в apat не будет использована. В этот момент весь образец сопоставляется значению, и если сопоставление потерпит неудачу или будет отклонено, так выполняется все вычисление.

  3. Сопоставление образца _ любому значению всегда имеет успех, при этом никаких связываний не происходит.

  4. Сопоставление образца con pat значению, где con --- конструктор, определенный с помощью newtype, зависит от значения:
    • Если значение имеет вид con v, то pat сопоставляется v.
    • Если значением является _|_, то pat сопоставляется _|_.
    То есть конструкторы, связанные с newtype, служат только для того, чтобы изменить тип значения.

  5. Сопоставление образца con pat1 ...patn значению, где con --- конструктор, определенный с помощью data, зависит от значения:
    • Если значение имеет вид con v1 ...vn, части образца сопоставляются слева направо компонентам значения данных, если все сопоставления завершатся успешно, результатом всего сопоставления будет успех; первая же неудача или отклонение приведут к тому, что сопоставление с образцом соответственно потерпит неудачу или будет отклонено.

    • Если значение имеет вид con' v1 ...vm, где con --- конструктор, отличный от con', сопоставление потерпит неудачу.

    • Если значение равно _|_, сопоставление будет отклонено.

  6. Сопоставление с конструктором, использующим именованные поля, --- это то же самое, что и сопоставление с обычным конструктором, за исключением того, что поля сопоставляются в том порядке, в котором они перечислены (названы) в списке полей. Все перечисленные поля должны быть объявлены конструктором, поля не могут быть названы более одного раза. Поля, которые не названы в образце, игнорируются (сопоставляются с _).

  7. Сопоставление числового, символьного или строкового литерала k значению v имеет успех, если v == k, где == перегружен на основании типа образца. Сопоставление будет отклонено, если эта проверка будет отклонена.

    Интерпретация числовых литералов в точности описана в разделе 3.2, то есть перегруженная функция fromInteger или fromRational применяется к литералу типа Integer или Rational (соответственно) для преобразования его к соответствующему типу.

  8. Сопоставление n+k-образца (где n --- переменная, а k --- положительный целый литерал) значению v имеет успех, если x >= k, при этом n связывается с x - k, и терпит неудачу иначе. Снова, функции >= и - являются перегруженными в зависимости от типа образца. Сопоставление будет отклонено, если сравнение будет отклонено.

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

  9. Сопоставление "такого как"-образца var@apat значению v является результатом сопоставления apat с v, дополненного связыванием var с v. Если сопоставление apat с v потерпит неудачу или будет отклонено, такой же результат будет у сопоставления с образцом.

Помимо очевидных ограничений статических типов (например, статической ошибкой является сопоставление символа с булевским значением), выполняются следующие ограничения статических классов:

Многие люди считают, что n+k-образцы не следует использовать. Эти образцы могут быть удалены или изменены в будущих версиях Haskell .

Иногда полезно различать два вида образцов. Сопоставление с неопровержимым образцом не является строгим: образец сопоставляется, даже если сопоставляемое значение равно _|_. Сопоставление с опровержимым образцом является строгим: если сопоставляемое значение равно _|_, сопоставление будет отклонено. Неопровержимыми являются следующие образцы: переменная, символ подчеркивания, N apat, где N --- конструктор, определенный с помощью newtype, а apat --- неопровержимый образец (см. раздел 4.2.3), var@apat, где apat --- неопровержимый образец, и образцы вида ~apat (независимо от того, является ли apat неопровержимым образцом или нет). Все остальные образцы являются опровержимыми.

Приведем несколько примеров:

  1. Если образец ['a','b'] сопоставляется ['x',_|_], то сопоставление 'a' с 'x' потерпит неудачу, и все сопоставление завершится неудачей. Но если ['a','b'] сопоставляется [_|_,'x'], то попытка сопоставить 'a' с _|_ приведет к тому, что все сопоставление будет отклонено.

  2. Эти примеры демонстрируют сопоставление опровержимых и неопровержимых образцов:

    (\ ~(x,y) -> 0) 
    _|_    =>    0
    (\  (x,y) -> 0) 
    _|_    =>    _|_



    (\ ~[x] -> 0) []    
    =>    0
    (\ ~[x] -> x) []    
    =>    _|_



    (\ ~[x,~(a,b)] -> x) [(0,1),
    _|_]    =>    (0,1)
    (\ ~[x, (a,b)] -> x) [(0,1),
    _|_]    =>    _|_



    (\  (x:xs) -> x:x:xs) 
    _|_   =>   _|_
    (\ ~(x:xs) -> x:x:xs) 
    _|_   =>   _|_:_|_:_|_

  3. Рассмотрим следующие объявления:

      newtype N = N Bool
      data    D = D !Bool

    Эти примеры показывают различие между типами, определенными с помощью data и newtype, при сопоставлении с образцом:

    (\  (N True) -> True) 
    _|_     =>    _|_
    (\  (D True) -> True) 
    _|_     =>    _|_
    (\ ~(D True) -> True) 
    _|_     =>    True

    Дополнительные примеры вы найдете в разделе 4.2.3.

Образцы верхнего уровня в case-выражениях и набор образцов верхнего уровня в связываниях имен функций или образцах могут иметь ноль или более связанных с ними стражей. Страж --- это булево выражение, которое вычисляется только после того, как все аргументы были успешно сопоставлены, и, для того чтобы все сопоставление с образцом имело успех, значением этого выражения должно быть истина. Окружением стража является то же окружение, что и для правой части альтернативы case-выражения, определения функции или связывания с образцом, к которому он прикреплен.

Семантика стража имеет очевидное влияние на характеристики строгости функции или case-выражения. В частности, из-за стража может быть вычислен иной неопровержимый образец . Например, в

f :: (Int,Int,Int) -> [Int] -> Int
f ~(x,y,z) [a] | (a == y) = 1

и a, и y будут вычислены с помощью == в страже.

3.17.3  Формальная семантика сопоставления с образцом

Семантика всех конструкций сопоставления с образцом, отличных от case-выражений, определена с помощью тождеств, которые устанавливают связь этих конструкций с case -выражениями. В свою очередь, семантика самих case-выражений задана в виде последовательностей тождеств на рис. 3.1--3.2. Любая реализация должна обеспечивать выполнение этих тождеств; при этом не ожидается, что она будет использовать их непосредственно, поскольку это могло бы привести к генерации довольно неэффективного кода.

(a)case e of { alts } = (\v -> case v of { alts }) e
где v --- новая переменная
(b)case v of { p1 match1;  ... ; pn matchn }
=  case v of { p1 match1 ;
                _  -> ... case v of {
                           pn matchn ;
                           _  -> error "Нет сопоставлений" }...}
 где каждое matchi имеет вид:
  | gi,1  -> ei,1 ; ... ; | gi,mi -> ei,mi where { declsi }
(c)case v of { p | g1 -> e1 ; ...
             | gn -> en where { decls }
            _     -> e' }
= case e' of
  {y ->  (где y --- новая переменная)
   case v of {
         p -> let { decls } in
                if g1 then e1 ... else if gn then en else y ;
         _ -> y }}
(d)case v of { ~p -> e; _ -> e' }
= (\x1 ... xn -> e ) (case v of { p-> x1 }) ... (case v of { p -> xn})
где x1, ..., xn --- переменные в p
(e)case v of { x@p -> e; _ -> e' }
=  case v of { p -> ( \ x -> e ) v ; _ -> e' }
(f)case v of { _ -> e; _ -> e' } = e

Рис. 3

Семантика case-выражений, часть 1

(g)case v of { K p1 ...pn -> e; _ -> e' }
= case v of {
     K x1 ...xn -> case x1 of {
                    p1 -> ... case xn of { pn -> e ; _ -> e' } ...
                    _  -> e' }
     _ -> e' }
по меньшей мере один из p1, ..., pn не является переменной; x1, ..., xn --- новые переменные
(h)case v of { k -> e; _ -> e' } = if (v==k) then e else e'
где k --- числовой, символьный или строковый литерал
(i)case v of { x -> e; _ -> e' } = case v of { x -> e }
(j)case v of { x -> e } = ( \ x -> e ) v
(k)case N v of { N p -> e; _ -> e' }
= case v of { p -> e; _ -> e' }
где N --- конструктор newtype
(l)case _|_ of { N p -> e; _ -> e' } = case _|_ of { p -> e }
где N --- конструктор newtype
(m) case  v  of {  K  { f1  =  p1  ,  f2  =  p2  ,  ... } ->  e ; _ ->  e'  }
=  case e' of {
   y ->
    case  v  of { 
      K  {  f1  =  p1  } ->
            case  v  of { K  { f2  =  p2  ,  ...  } ->  e ; _ ->  y  };
            _ ->  y  }}
где f1, f2, ... --- поля конструктора K, y--- новая переменная
(n)case  v  of {  K  { f  =  p } ->  e ; _ ->  e'  }
= case  v  of {
     K p1 ... pn  ->  e ; _ ->  e'  }
где pi равно p, если f именует i-ую компоненту K, _ иначе
(o)case  v  of {  K  {} ->  e ; _ ->  e'  }
= case  v  of {
     K _ ... _ ->  e ; _ ->  e'  }
(p)case (K' e1 ... em) of { K x1 ... xn -> e; _ -> e' } = e'
где K и K' --- различные конструкторы data соответственно с n и m аргументами
(q)case (K e1 ... en) of { K x1 ... xn -> e; _ -> e' }
= (\x1 ... xn -> ee1 ... en
где K --- конструктор data с n аргументами

(r)

case _|_ of { K x1 ... xn -> e; _ -> e' } = _|_
где K --- конструктор data с n аргументами

(s)

case v of { x+k -> e; _ -> e' }
= if v >= k then (\x -> e) (v-k) else e'
где k --- числовой литерал

Рис. 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).


Описание Haskell 98
наверх | назад | вперед | содержание | предметный указатель функций
Декабрь 2002