Опубликован: 19.09.2008 | Доступ: свободный | Студентов: 658 / 70 | Оценка: 4.50 / 5.00 | Длительность: 21:25:00
Лекция 4:

Выражения

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 потерпит неудачу или будет отклонено, такой же результат будет у сопоставления с образцом.

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

  • Целочисленный литеральный образец можно сопоставить только значению класса Num.
  • Литеральный образец с плавающей точкой можно сопоставить только значению в классе Fractional.
  • n+k -образец можно сопоставить только значению в классе Integral.

Многие люди считают, что 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.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" ).