Haskell/VariablesAndFunctions
(Все примеры из этой главы могут быть сохранены в файле и вычислены: просто загрузите этот файл в GHC или Hugs)
Переменные
[править]Мы уже видели, как можно использовать GHCi в качестве калькулятора. Разумеется, такой подход приемлем лишь для очень коротких вычислений. Для длинных вычислений, а также для написания программ на Haskell, нам нужно отслеживать промежуточные результаты.
Промежуточные результаты хранятся в переменных. Например, рассмотрим следующее вычисление:
ghci> 3.1416 * 5^2 78.53999999999999
Результат — площадь круга с радиусом 5
. Это очень неудобно — каждый раз печатать или запоминать последовательность . На самом деле, вся цель програмирования заключается в том, чтобы поручить машине бездумное повторение и механическое запоминание. Именно поэтому в Haskell определена переменная под названием pi
, которая содержит в себе более дюжины цифр из записи числа pi
:
ghci> pi 3.141592653589793 ghci> pi * 5^2 78.53981633974483
Другими словами, как переменная pi
, так и её значение 3.141592653589793
одинаково успешно могут быть использованы в вычисленииях, они взаимозаменяемы.
Исходные файлы Haskell
[править]Создайте новый файл с именем Varfun.hs
в вашем любимом текстовом редакторе (расширение «hs» означает «Haskell») и вставьте туда следующее определение:
r = 5.0
Убедитесь в отсутствии пробельных символов перед r
, так как Haskell чувствителен к этому.
Теперь, перейдите в каталог с сохранённым файлом, запустите GHCi и воспользуйтесь командой :load
(или, для краткости можно использовать :l
):
Prelude> :load Varfun.hs Compiling Main ( Varfun.hs, interpreted ) Ok, modules loaded: Main. *Main>
Загрузка исходного файла Haskell сделает доступными из командной строки GHCi все содержащиеся там определения.
Если GHCi выдаёт ошибку «Could not find module 'Varfun.hs'», возможно, вы забыли перейти в каталог с исходным файлом. Воспользуйтесь командой :cd
для того, чтобы сменить текущий каталог на тот, где находится файл «Varfun.hs».
Prelude> :cd c:\myDirectory Prelude> :load Varfun.hs Compiling Main ( Varfun.hs, interpreted ) Ok, modules loaded: Main. *Main>
Теперь вы можете использовать в вычислениях недавно определённую переменную r
.
*Main> r 5 *Main> pi * r^2 78.53981633974483
Итак, чтобы вычислить площадь круга с радиусом 5, мы просто определяем r = 5.0
и затем вписываем известную формулу для вычисления площади круга. Теперь нет необходимости каждый раз писать числа, и это очень удобно!
Давайте добавим другое определение. Измените содержимое исходного файла на следующее:
r = 5
area = pi * r ^ 2
Сохраните файл и выполните команду :reload
в GHCi, чтобы загрузить туда новое содержимое файла. (Также вместо :reload
, для краткости можно исполбзовать просто :r
)
*Main> :reload Compiling Main ( Varfun.hs, interpreted ) Ok, modules loaded: Main. *Main>
Теперь нам доступны две переменные: r
и area
:
*Main> area 78.53981633974483 *Main> area / r 15.707963267948966
Переменные в императивных языках
[править]Если вы уже знакомы с императивными языками программирования (например, C или Python), вы уже могли заметить, что в Haskell переменные весьма отличаются от того, что вам о них известно. Теперь мы объясним суть и причины этих отличий.
Если у вас нет опыта в программировании, вы можете пропустить этот раздел и продолжить чтение, начиная с раздела Функции.
Переменные не изменяются |
В отличие от императивных языков, переменные в Haskell не изменяются. Однажды определённые, они никогда не изменяют своих значений: они неизменяемые (англ. Immutable). Например, нижеприведённый код работать не будет:
-- этот код не работает
r = 5
r = 2
так как он определяет одну сущность дважды, что не имеет смысла. Компилятор пожалуется на «множественные объявления r
» (англ. multiple declarations of r
). Вероятно, вы приучили себя понимать этот код как установку значения r
в 5
, а затем установку этого же значения в 2
, но Haskell работает не так. Однажды определённое определено навсегда. Переменные в Haskell больше похожи на аббревиатуры длинных выражений (подобно вычислениям на бумаге), чем на название участка изменяющейся компьютерной памяти.
Другой пример, который работает не так, как вы ожидаете:
r = r + 1 -- это не увеличивает r
Вместо «увеличения переменной r
», это на самом деле означает рекурсивное определение r
в терминах самой себя. Мы подробно разберём рекурсию позже. Пока что запомните, что этот фрагмент кода означает кое-что совершенно отличное от того, к чему вы (возможно) привыкли.
Всё вышесказанное подразумевает, что порядок, в котором вы объявляете различные переменные, не важен. Например, следующие фрагменты кода означают одно и то же:
y = x * 2
x = 3
|
x = 3
y = x * 2
|
Мы можем записывать определения в таком порядке, в каком захотим, и нет такого понятия, как «x
объявлена до y
» или наоборот. (Это — вторая причина того, почему вы не можете объявить что-либо больше одного раза: иначе возникли бы неоднозначности.)
Возможно, сейчас вам непонятно, как вообще можно что-либо сделать в Haskell, если переменные не изменяются. Однако, прочитав данную книгу вы сможете написать любую программу, в которой переменные вообще не меняются.
Функции
[править]А теперь представте, что у нас не одна, а несколько окружностей с разными радиусами, и нужно вычислить их площадь. Например, чтобы рассчитать площадь круга с радиусом 3, мы объявим новые переменные r2
и area2
, и запишем их в наш файл.
r = 5
area = pi*r^2
r2 = 3
area2 = pi*r2^2
Конечно, это не удобно, так как мы повторяем формулы, абсолютно идентичные друг другу. Гораздо лучше записать эту формулу один раз, а затем применять её для разных радиусов. Функции, как раз позволяют сделать так.
Функции определяют для конкретного значения аргумента (или параметра) конкретное результирующее значение. Функции в Хаскеле определяются просто и подобно определению переменных. Для того, чтобы определить функцию, слева от знака «=» мы пишем имя функции, затем через пробел локальную переменную (или несколько переменных)которая и будет использована в функции, а справа от знака «=» мы пишем выражение, определяющее результирующее значение. Например, для определения площади круга с известным радиусом определим функцию area
, зависящую от параметра r
area r = pi * r^2
Теперь мы можем использовать любые значения параметра, для вычисления площади нужного круга. Напишите это определение в своём файле и попробуйте следующее:
*Main> area 5 78.53981633974483 *Main> area 3 28.274333882308138 *Main> area 17 907.9202768874502
Как вы видите, мы можем использовать разные радиусы для расчёта площади соответствующих кругов.
Круглые скобки «()» используются, чтобы сгруппировать значения. Так, например
area (5+3)
интерпретируется так: сначала сложить 5 и 3, затем вычислить функцию от получившейся суммы; в то время как
area 5 + 3
означает: вычислить значение функции от 5, а затем, к получившемуся результату прибавить 3.
У функции также может быть много параметров, записываются они также через пробел. Например, объявим функцию для вычисления площади прямоугольника:
areaRect a b = a * b
И воспользуемся ею:
*Main> areaRect 6 10 60
Очевидно, что можно использовать и большее количество переменных, для расчёта функции по какой-либо формуле, они также будут записываться через пробел.
Для задания функций также можно использовать уже заданные функции (обратите внимание на необходимость использования скобок во второй строчке, справа от знака «=»: double
— функция одной переменной, поэтому отсутствие скобок выглядело бы как желание использовать double
как функцию двух переменных (одна из которых — также является функцией)):
double x = 2*x
quadruple x = double (double x)
Область видимости переменных
[править]Иногда, писать одну и ту же формулу внутри одной функции не хочется, и при этом она может нигде больше не использоваться, а занимать хорошее, короткое имя не хочется, и писать длинное имя внутри функции каждый раз тоже. Для этого используются локальные переменные, которые будут доступны только внутри функции, в которой они объявлены. Для этой цели используется оператор where
. Перед этим оператором и всеми локальными переменными внутри функции отбиваются четырьмя пробелами. Так компилятор отличает локальные переменные от глобальных. Например, для вычисления площади треугольника по формуле Герона:
heron a b c = sqrt (s*(s-a)*(s-b)*(s-c))
where
s = (a+b+c) / 2
s
— это половина периметра писать эту формулу каждый раз утомительно. Также нельзя определять s
, как глобальную переменную,
heron a b c = sqrt (s*(s-a)*(s-b)*(s-c)) -- Это работать не будет.
s = (a+b+c) / 2
это не будет работать, так как переменные a
, b
и c
доступны только в правой части функции heron
(то есть они локальны) и не будут определены для переменной s
.
В следующем примере рассмотрено использование нескольких функций с локальными переменными:
areaTriangleTrig a b c = c * height / 2 -- use trigonometry
where
cosa = (b^2 + c^2 - a^2) / (2*b*c)
sina = sqrt (1 - cosa^2)
height = b*sina
areaTriangleHeron a b c = result -- use Heron's formula
where
result = sqrt (s*(s-a)*(s-b)*(s-c))
s = (a+b+c)/2
Также, глобальные переменные не будут влиять на локальные, например:
area r = pi * r^2
r = 0
Если вы воспользуетесь этой функцией, она не будет возвращать значение «0», потому что r
внутри функции area
, и вне её — это две разные, независимые переменные.
Однако, стоит заметить, что если внутри функции не определена одноимённая локальная переменная, то за неё принимается глобальная. Например:
z = 4
zat a = z*a
Если набрать это в исходном файле, затем в GHCi ввести zat 2
, то интерпретатор вернёт «8». Это также позволяет говорить о переменных, как о функциях от 0 переменных.