Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MakeevGA-Haskell-a4-shortcode_2014_05_31.doc
Скачиваний:
15
Добавлен:
19.01.2023
Размер:
1.79 Mб
Скачать

Синтаксический хлеб и синтаксический сахар

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

Условия и ограничения

max :: Ord a => a -> a -> a

max x y = if x > y then x else y

Функция, возвращающая из двух большее значение. Первая строчка определяет тип функции, но в большинстве случаев без нее можно обойтись: компилятор сможет сам вывести тип функции. При этом он постарается вывести такой тип, чтобы он подходил под как можно большее число типов. Например, тип функции max мог бы быть: Integer -> Integer -> Integer, но это означало бы, что функцию нельзя применить ни к каким другим типам.

Вот как примерно будет думать компилятор: "Так, посмотрим, что у нас внутри функции делается с двумя параметрами? Ага, к ним применяется операция сравнения (>), а какие у нее требования? Она требует любой тип a, принадлежащий классу Ord. Стало быть x и y оба принадлежат этому типу. А возвращает эта функция как раз или x, или y, значит, тот же тип является и возвращаемым".

Под всей этой с виду алхимией лежит на самом деле вполне серьезная математика, которая называется – система типов Хиндли-Милнера. Вообще, под всем функциональным программированием лежит серьезная математика – лямбда-исчисление и прочие чёрчи.

Возвращаясь к нашей функции: мы видим условную конструкцию языка. В отличие от оператора if в императивных языках, где он имеет вид if выражение then действие else действие, в языке Haskell конструкция if выражение1 then выражение2 else выражение3 – это именно выражение, значение которого равно значению выражения2 или выражения3 в зависимости от того, равно ли True выражение1. Понятно, что ветка else обязана быть в такой конструкции, а выражение2 и выражение3 должны быть одного типа.

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

sign x | x > 0 = 1

| x < 0 = (-1)

| otherwise = 0

Здесь мы видим три определения одной функции, причем у последних двух левая часть определения до условия для краткости опущена. На самом деле здесь работает старое доброе сопоставление с образцом, только образец x, который раньше срабатывал всегда, теперь срабатывает, только если прилагаемое к нему условие равно True. Красивое решение, которым, я уверен, очень гордятся разработчики языка (я бы гордился на их месте) – слово "otherwise". Это совсем не какая-то особенная часть языка, как слова then или else. Где-то глубоко в стандартных библиотеках написано, оцените юмор:

otherwise = True

Так что, это просто замена такому условию, которое срабатывает всегда.

Локальные определения

Простая функция, находящая решение квадратного уравнения:

solve a b c

| d < 0 = []

| otherwise = [(-b + sqrt d)/2/a, (-b - sqrt d)/2/a] where

d = b*b - 4*a*c

Заметили это where? После него идет локальное определение имени d, используемого в теле основной функции. Можно было бы объявить глобальную функцию:

d a b c = b*b - 4*a*c

Но тогда и вызывать мы ее пришлось с параметрами, – да и пригодится ли где-то еще значение дискриминанта, кроме как внутри решения квадратного уравнения? Различных функций и констант в секции where может быть много, и все они видны только из главной функции, к которой относится where. Зато они легко могут использовать параметры главной функции, что здесь и продемонстрировано.

Есть и другая альтернатива – выражение let определения in выражение:

solve a b c =

let

d = b*b - 4*a*c

in

if d < 0 then

[]

else

[(-b + sqrt d)/2/a, (-b - sqrt d)/2/a]

Здесь мы сначала даем все нужные локальные определения, а затем уже используем их в теле функции. Еще одно отличие заключается в том, что секция where включает локальные определения, тогда как let определения in выражение само по себе является выражением, имеющим значение – то есть может подставляться в функции и появляться везде, где может быть выражение.