Конструкции и выражения в Haskell


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

Prelude> let f x = if x > 0 then 1 else (-1)
Prelude> f 5
1
Prelude> f (-5)
-1

В Haskell отрицательные числа получаются с помощью знака - перед соответствующим литералом и во многих случаях их следует заключать в скобки. Если бы мы использовали синтаксис без скобок, то получили бы выражение f - 5.

Обе ветви then и else должны присутствовать. В ветвях then и else должны стоять выражения одного и того же типа, иначе GHCi вернет сообщение об ошибке, потому что функция определена неверно.

Условное выражение можно использовать в построении более сложных выражений. 
Prelude> let g x = (if x > 0 then 1 else (-1)) + 3
Prelude> g 5
4
Prelude> g (-7)
2

Функция, которая возвращает 1, если ей передано положительное число, (-1), если отрицательное, и 0 в случае, когда передан 0:
sign x = if x > 0 then 1 else if x == 0 then 0 else (-1)


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

Использование условного выражения if then else не всегда удобно. В Haskell существует гораздо более мощный инструмент решающий ту же задачу. Это так называемое сопоставление с образцом. Основная идея заключается в том что мы определяем функцию не с помощью одного уравнения, а с помощью нескольких уравнений. Каждое из этих уравнений описывает одну из возможных ветвей программы.
factorial' 0 = 1
factorial' n = n * factorial' (n - 1)
Если нужно определить в интерпретаторе функцию из нескольких уравнений, то следует писать, например, так:
Prelude> let {factorial' 0 = 1; factorial' n = n * factorial' (n - 1)}
Prelude> factorial' 0
1


Следующие функции, могут привести к расходимости (мы называем выражение расходящимся, если вычисление его значения приводит к бесконечному циклу или аварийному завершению):
grault x 0 = x
grault x y = x
garply = grault 'q'
В определении функции рассматриваются два случая. Несмотря на то, что мы видим, что итоговый результат в обоих случаях одинаковый, программа, тем не менее, должна выбрать, по какому из двух путей пойдет вычисление. Соответственно, чтобы осуществить этот выбор, необходимо вычислить второй аргумент.


Охранные выражения

Сопоставление с образцом не всегда идеально подходит для описания подходящего ветвления. Если условие на основании которого осуществляется ветвление представляет собой большое выражение, то довольно трудно осуществить описание этого выражения на языке сопоставления с образцом. Для этого случая в Haskell есть другой инструмент, который зовется "охранные выражения".

Охранные выражения позволяют расщепить одно уравнение на несколько. Охранное выражение представляет собой булево выражение, которое отделяется от параметров функции вертикальной чертой. Это булево выражение проверяется, оно может зависеть от формальных параметров функции. При вызове функции, если сопоставление с образцом произошло удачно, то происходит проверка охранных выражений. Если охранное выражение обращается в истину, то тогда выполняется правая часть, а если охранное выражение ложно, то происходит переход к следующему охранному выражению (их может быть два или больше) и тоже проверяется истинность указанного логического выражения и если оно истинно, то тоже происходит переход в правую часть. Если же все охранные выражения являются ложными, то происходит переход к следующей строчке сопоставления с образцом (если таковая присутствует). Если же следующего образца нет, то программа аварийно прерывается сообщением об ошибке о том, что сопоставление с образцом является не полным.

factorial''' 0 = 1
factorial''' n | n < 0 = error "arg must be >= 0" 
               | n > 0 = n * factorial''' (n - 1)
      
factorial4 :: Integer -> Integer
factorial4 n | n == 0    = 1
             | n > 0     = n * factorial4 (n - 1)
             | otherwise = error "arg must be >= 0"

Идентификатор otherwise. Это не ключевое слово, а константа, определенная для удобства в стандартной библиотеке:
Prelude> otherwise
True


Выражение let in

Конструкция let in позволяет ликвидировать повторяющиеся выражения.
Prelude> let x = True in (True,x)
(True,True)
Prelude> (let x = 'w' in [x,'o',x]) ++ "!"
"wow!"
Первая часть после ключевого слова let описывает локальное связывание. Здесь некоторое выражение связывается с некоторой переменной. Эта переменная может использоваться во второй части выражения let in, в части in. Значение всего выражения определяется частью in, а часть let носит вспомогательную роль.
roots' a b c =
  let d = sqrt (b ^ 2 - 4 * a * c) in
  ((-b - d) / (2 * a), (-b + d) / (2 * a))

Выражение let in может задавать сразу несколько связываний.
roots'' a b c =
  let {d = sqrt (b ^ 2 - 4 * a * c); x1 = (-b - d) / (2 * a); x2 = (-b + d) / (2 * a)}
  in (x1, x2)

Можно организовывать связываемые выражения с помощью отступов.
roots''' a b c =
  let
    x1 = (-b - d) / aTwice
    x2 = (-b + d) / aTwice
    d = sqrt $ b ^ 2 - 4 * a * c
    aTwice = 2 * a
  in (x1, x2)
Локальные связывания должны иметь один и тот же отступ.

С помощью выражения let in можно также определять локальные функции.
factorial6 n | n >= 0    = let
                   helper acc 0 = acc
                   helper acc n = helper (acc * n) (n - 1)
               in helper 1 n
             | otherwise = error "arg must be >= 0"

Возможно также не только локальное определение функции, но и локальное связывание образцов.
rootsDiff a b c = let
 (x1,x2) = roots a b c
 in x2 - x1


Попытка вычислить следующее выражение приводит к бесконечному циклу.
quux = let x = x in x



Конструкция where

Еще одна конструкция позволяющая обеспечивать локальные связывания. Конструкция where очень похожа на выражение let in, но устроена ровно наоборот. В выражении let in часть в которой находится локальное связывание стоит сначала, а потом фигурирует выражение в котором это локальное связывание используется. В конструкции where сначала идет выражение в котором используются какие-то переменные, а потом внутри выражение where происходит локальное связывание.
roots'''' a b c = (x1, x2) where
    x1 = (-b - d) / aTwice
    x2 = (-b + d) / aTwice
    d = sqrt $ b ^ 2 - 4 * a * c
    aTwice = 2 * a

Между конструкцией where и выражением let in есть одно отличие. Конструкция let in является выражением.
Prelude> let x = 2 in x^2
4
Prelude> (let x = 2 in x^2)^2
16
Конструкция where выражением не является. Конструкция where может использоваться только в определении функции и только на определенном месте, в качестве глобальной части тела этой функции. Это сделано для того чтобы использовать функцию where в некоторых контекстах в которых выражение let in использовать нельзя. Мы можем писать предложение where общее сразу для нескольких уравнений с охранными выражениями.
factorial7 :: Integer -> Integer
factorial7 n | n >= 0    = helper 1 n
             | otherwise = error "arg must be >= 0"
 where
  helper acc 0 = acc
  helper acc n = helper (acc * n) (n - 1)

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

Отправить комментарий