Россия, Петерубрг, СПБ-ГПУ, 1998 |
Выражения
3.17.2 Неформальная семантика сопоставления с образцом
Образцы сопоставляются значениям. Попытка сопоставить образец может иметь один из трех результатов: потерпеть неудачу, иметь успех, при этом каждая переменная в образце связывается с соответствующим значением, или быть отклонена (т.е. вернуть _|_). Сопоставление с образцом выполняется слева направо и извне вовнутрь, в соответствии со следующими правилами:
- Сопоставление образца var значению v всегда имеет успех и связывает var с v.
- Сопоставление образца ~apat значению v всегда имеет успех. Свободные переменные в apat связываются с соответствующими значениями, если сопоставление apat с v завершится успешно, или с _|_, если сопоставление apat с v потерпит неудачу или будет отклонено. (Связывание не подразумевает вычисление.)
С точки зрения операций, это означает, что никакое сопоставление не будет сделано с образцом ~apat до тех пор, пока одна из переменных в apat не будет использована. В этот момент весь образец сопоставляется значению, и если сопоставление потерпит неудачу или будет отклонено, так выполняется все вычисление.
- Сопоставление образца _ любому значению всегда имеет успех, при этом никаких связываний не происходит.
- Сопоставление образца con pat значению, где con - конструктор, определенный с помощью newtype, зависит от значения:
- Если значение имеет вид con v, то pat сопоставляется v.
- Если значением является _|_, то pat сопоставляется _|_.
То есть конструкторы, связанные с newtype, служат только для того, чтобы изменить тип значения.
- Сопоставление образца con pat1 ...patn значению, где con - конструктор, определенный с помощью data, зависит от значения:
- Если значение имеет вид con v1 ...vn, части образца сопоставляются слева направо компонентам значения данных, если все сопоставления завершатся успешно, результатом всего сопоставления будет успех; первая же неудача или отклонение приведут к тому, что сопоставление с образцом соответственно потерпит неудачу или будет отклонено.
- Если значение имеет вид con' v1 ...vm, где con - конструктор, отличный от con', сопоставление потерпит неудачу.
- Если значение равно _|_, сопоставление будет отклонено.
- Сопоставление с конструктором, использующим именованные поля, - это то же самое, что и сопоставление с обычным конструктором, за исключением того, что поля сопоставляются в том порядке, в котором они перечислены (названы) в списке полей. Все перечисленные поля должны быть объявлены конструктором, поля не могут быть названы более одного раза. Поля, которые не названы в образце, игнорируются (сопоставляются с _).
- Сопоставление числового, символьного или строкового литерала
k
значению v имеет успех, если v == k, где == перегружен на основании типа образца. Сопоставление будет отклонено, если эта проверка будет отклонена.Интерпретация числовых литералов в точности описана в разделе "3.2" , то есть перегруженная функция fromInteger или fromRational применяется к литералу типа Integer или Rational (соответственно) для преобразования его к соответствующему типу.
- Сопоставление n+k -образца (где n - переменная, а k - положительный целый литерал) значению v имеет успех, если x >= k, при этом n связывается с x - k, и терпит неудачу иначе. Снова, функции >= и - являются перегруженными в зависимости от типа образца. Сопоставление будет отклонено, если сравнение будет отклонено.
Интерпретация литерала k является точно такой же, как и для числовых литералов, за исключением того, что допустимы только целые литералы.
- Сопоставление "такого как"-образца var@apat значению v является результатом сопоставления apat с v, дополненного связыванием var с v. Если сопоставление apat с v потерпит неудачу или будет отклонено, такой же результат будет у сопоставления с образцом.
Помимо очевидных ограничений статических типов (например, статической ошибкой является сопоставление символа с булевским значением), выполняются следующие ограничения статических классов:
- Целочисленный литеральный образец можно сопоставить только значению класса Num.
- Литеральный образец с плавающей точкой можно сопоставить только значению в классе Fractional.
- n+k -образец можно сопоставить только значению в классе Integral.
Многие люди считают, что n+k -образцы не следует использовать. Эти образцы могут быть удалены или изменены в будущих версиях Haskell .
Иногда полезно различать два вида образцов. Сопоставление с неопровержимым образцом не является строгим: образец сопоставляется, даже если сопоставляемое значение равно _|_ _. Сопоставление с опровержимым образцом является строгим: если сопоставляемое значение равно _|_сопоставление будет отклонено. Неопровержимыми являются следующие образцы: переменная, символ подчеркивания, N apat, где N - конструктор, определенный с помощью newtype, а apat - неопровержимый образец (см. раздел "4.2.3" ), var@apat, где apat - неопровержимый образец, и образцы вида ~apat (независимо от того, является ли apat неопровержимым образцом или нет). Все остальные образцы являются опровержимыми.
Приведем несколько примеров:
- Если образец ['a','b'] сопоставляется ['x' _|_, ], то сопоставление 'a' с 'x' потерпит неудачу, и все сопоставление завершится неудачей. Но если ['a','b'] сопоставляется [_|_ ,'x'], то попытка сопоставить 'a' с _|_ приведет к тому, что все сопоставление будет отклонено.
- Эти примеры демонстрируют сопоставление опровержимых и неопровержимых образцов:
(\ ~(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) _|_ => _|_:_|_:_|_
- Рассмотрим следующие объявления:
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.1. Семантика 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 -> e) e1 ... 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 - числовой литералЛистинг 3.2. Семантика 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" ).