Установка компилятора GHC
На текущий момент существует несколько способов получения компилятора GHC. Наиболее простые варианты это:
- Установка дистрибутива Haskell Platform https://www.haskell.org/platform/
- Установка менеджера проектов stack https://docs.haskellstack.org/en/stable/README/
Первый вариант достаточно прямолинейный, для 64-битных систем под управлением Windows доступен инсталлятор, на многих системах Linux возможна установка из пакетного менеджера дистрибутива.
На системах под управлением macOS дистрибутив Haskell Platform недоступен, однако компилятор GHC и система сборки cabal-install доступны в homebrew https://brew.sh/. При наличии в системе установленного homebrew, установка компилятора и инструментов сводится к командам
brew install ghc
brew install cabal-install
brew install haskell-stack
Подробно рассмотрим установку с использованием stack на системах Windows.
Для систем Windows на https://docs.haskellstack.org/en/stable/README/ есть ссылка на установщик для 64-битных систем (рекомендуется). Установщик для 32-битных систем можно найти на https://docs.haskellstack.org/en/stable/install_and_upgrade/.
После установки stack, в командной строке (cmd или powershell) становится доступна команда stack
. Версии библиотек и компилятора управляются настройкой stack, называемой resolver. Для систем Windows на момент написания рекомендуется lts-14, работающий с компилятором GHC 8.6. По умолчанию при установке выбирается последняя версия resolver (на момент написания lts-15.4). Изменить эту настройку можно, выполнив команду
stack config set resolver lts-14
Установка компилятора затем производится командой
stack setup
После установки компилятора, если компилятор установлен непосредственно из Haskell Platform или другими средствами, исполняемый файл компилятора доступен по команде ghc
. Проверить версию компилятора можно выполнив команду
ghc --version
Результатом будет текст, аналогичный
The Glorious Glasgow Haskell Compilation System, version 8.6.5
Интерпретатор вызывается командой ghci
.
Если компилятор установлен при помощи stack, то компилятор вызывается командой stack ghc
, а интерпретатор – командой stack ghci
. При передаче аргументов командной строки компилятору или интерпретатору, необходимо перед прочими аргументами добавить аргумент --
, например,
stack ghc -- --version
Компилятор GHC
Haskell – компилируемый язык, и основным способом получения исполняемых файлов является компиляция.
Обычно для компиляции проектов используется система управления проектами Cabal (вместе с системой сборки cabal-install или stack). Для простых случаев, компиляцию можно производить прямым вызовом компилятора.
Рассмотрим программу “Hello, World!” на Haskell:
= putStrLn "Hello, World!" main
Сохраним эту программу в файле исходных кодов с названием main.hs
и выполним команду
ghc --make main.hs
или, при использовании stack,
stack ghc -- --make main.hs
В результате выполнения этой команды будут созданы файлы main.o
, main.hi
и исполняемый файл main
(на системах Windows – main.exe
).
main.o
– это объектный файл, содержащий перемещаемый машинный код, полученный компиляцией исходного файла main.hs
.
main.hi
– это т.н. файл интерфейсов, который используется компилятором GHC для разрешения модулей.
Исполняемый файл main
(main.exe
) – это, собственно, исполняемый файл.
Выполнение исполняемого файла в терминале даёт ожидаемый вывод:
> main.exe
Hello, World!
Если для компиляции требуется несколько файлов, команде ghc --make
достаточно передать файл с функцией main
, остальные он найдёт автоматически:
-- Файл Lib.hs
module Lib where
= putStrLn "Hello, World!"
sayHello
-- Файл main.hs
import Lib
= sayHello main
> ghc --make main.hs
[1 of 2] Compiling Lib ( Lib.hs, Lib.o )
[2 of 2] Compiling Main ( main.hs, main.o )
Linking main ...
Интерпретатор GHCi
Интерпретатор запускается командой ghci
или stack ghci
(при использовании stack). Кроме того, возможен прямой запуск исходных кодов Haskell, имеющих функцию main
в режиме интерпретатора, с использованием команды runhaskell
или stack runhaskell
, например,
> runhaskell main.hs
Hello, World!
> stack runhaskell main.hs
Hello, World!
При запуске интерпретатора ghci
запускается интерактивный текстовый интерфейс, выводящий приглашение и ожидающий ввода пользователя:
> ghci
GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help
Prelude>
По умолчанию приглашение имеет вид >
, слева от него указывается список загруженных модулей. По умолчанию, автоматически загружается модуль Prelude
, содержащий основные (типовые) определения из стандартной библиотеки Haskell.
Документация по стандартной библиотеке Haskell доступна в сети Интернет по адресу https://www.stackage.org/lts-14.27/package/base-4.12.0.0 (версия для GHC 8.6.5) или https://www.stackage.org/lts/package/base-4.12.0.0 (последняя актуальная версия). Та же документация доступна на странице http://hackage.haskell.org/package/base (может быть устаревшей).
Конкретно, документация Prelude доступна на странице https://www.stackage.org/haddock/lts-14.27/base-4.12.0.0/Prelude.html
Попробуем выполнить простые арифметические выражения в интерактивном интерпретаторе GHCi:
Prelude> 2+2
4
Prelude> 7<9
True
Prelude> 10^2
100
Чтобы выйти из интерпретатора, нужно выполнить команду интерпретатора :quit
(или сокращённо :q
):
Prelude> :quit
Leaving GHCi.
Все команды интерпретатора начинаются на :
. Получить справку по командам интерпретатора можно выполнив команду :?
или :help
.
В частности, полезные команды включают:
:info
или :i
– показывает информацию об имени, переданном аргументом, например,
Prelude> :info putStrLn
putStrLn :: String -> IO () -- Defined in ‘System.IO’
выводится информация об объявленном типе функции и где находится объявление (в данном случае – в системном модуле).
:type
или :t
– показывает тип выражения, переданного аргументом, например
Prelude> :t "Hello, World!"
"Hello, World!" :: [Char]
Здесь [Char]
означает “список символов”, т.е. строку.
:load
или :l
– позволяет загрузить файл исходных текстов в интерактивном контексте. Все объявления в файле становятся доступны в интерактивном интерпретаторе. Например,
Prelude> main
<interactive>:8:1: error:
• Variable not in scope: main
Prelude> :l main.hs
[1 of 2] Compiling Lib ( Lib.hs, interpreted )
[2 of 2] Compiling Main ( main.hs, interpreted )
Ok, two modules loaded.
*Main> main
Hello, World!
*
перед Main
означает, что модуль Main
загружен в режиме интерпретатора.
По умолчанию, все выражения, вводимые GHCi – однострочные. Однако, можно вводить многострочные выражения, если выполнить команду :{
в начале и :}
в конце:
Prelude> :{
Prelude| 2
Prelude| +3
Prelude| :}
5
Среда разработки
Для Haskell не существует “одной среды разработки чтоб всеми править”, как, например, для C# (Microsoft Visual Studio), однако поддержка Haskell реализована во многих текстовых редакторах, в частности в Visual Studio Code, Atom, GNU Emacs, ViM, Sublime Text и других. Уровень поддержки может быть разным, от поддержки синтаксиса до предоставления интегрированной среды разработки.
Здесь в основном рассмотрена поддержка в Atom и кратко в Visual Studio Code.
Поддержка Haskell в текстовом редакторе Atom реализована на основе пакетов, разрабатываемых сообществом Atom-Haskell (https://atom-haskell.github.io/). На момент написания некоторые компоненты несколько устарели (работы по модернизации ведутся), но основная функциональность работает практически без нареканий.
Полный список пакетов поддержки Haskell доступен на странице https://atom.io/users/atom-haskell/packages.
Основные необходимые пакеты:
- language-haskell – подсветка синтаксиса и определение использования языка
- ide-haskell – интерфейс среды разработки и поддержка прочих компонентов
- ide-haskell-cabal – поддержка систем сборки
Рекомендованные пакеты:
- ide-haskell-repl – поддержка работы с интерактивным интерпретатором GHCi
- ide-haskell-hlint – отображение подсказок статического анализатора (“линтера”)
Если компилятор установлен при помощи stack, то может потребоваться в настройках ide-haskell-repl установить опцию ‘Default repl’ в stack
.
ide-haskell-hlint требует наличия в системе исполняемого файла hlint
. Его можно установить используя cabal-install или stack соответственно командами
cabal install hlint
или
stack install hlint
В настройках ide-haskell-hlint может дополнительно потребоваться указать путь к исполняемому файлу.
Дополнительные пакеты:
- ide-haskell-hasktags – переход к объявлению идентификатора
- ide-haskell-hoolge – поиск по документации библиотек
- haskell-pointfree – преобразования кода между “бесточечной” и “точечной” нотациями (“бесточечная” нотация опускает имена некоторых “очевидных” переменных и использует композицию функций)
Устаревшие пакеты (настоятельно не рекомендуются к установке):
- haskell-ghc-mod – поддержка вспомогательного инструмента ghc-mod
- autocomplete-haskell – поддержка автодополнения (типа IntelliSense) библиотечных идентификаторов на основе ghc-mod
Эти пакеты требуют стороннего ПО, которое не работает с новыми версиями GHC.
Выражения
Любые выражения на языке Haskell являются либо вычислениями, либо связываниями. Отличие вычисления от связывания в том, что вычисление имеет значение, тогда как связывание только объявляет имя для какого-то вычисления.
В программе Hello, World!
, приведённой ранее,
= putStrLn "Hello, World!" main
вся строчка является связыванием, объявляющим имя main
для вычисления в правой части равенства. putStrLn "Hello, World"
является вычислением, как и любая составная его часть (например "Hello, World!"
).
Вычисления так же называются выражениями, а связывания – декларациями или объявлениями.
Основой для вычислительной модели Haskell является λ-исчисление, поэтому про вычисления, которые невозможно упростить применением операций (т.е. которые по сути являются значениями), мы говорим, что они находятся в нормальной форме. Например, 1+1
не находится в нормальной форме, а 2
– находится. Следует отметить, что функции без аргументов находятся в нормальной форме по определению.
В Haskell существует несколько способов ввести новые связывания (объявления):
Объявления верхнего уровня. Эти связывания записываются непосредственно в файле исходного кода, как
main
в примере выше. Интерпретатор GHCi тоже поддерживает такой синтаксис.let
-связывания в выражениях. Позволяет объявить локальные переменные, область видимости которых ограничена друг другом и одним выражением. Синтаксис таких связыванийlet <одно или несколько связываний> in <выражение>
, например:= let hello = "Hello, World!" main in putStrLn hello
Несколько связываний должны быть разделены символом
;
или переносом строки. Если они разделены переносом строки, все связывания после первого должны начинаться в том же столбце, что и первое, например:-- корректный синтаксис let hello = "Hello, " = "World!" world in putStrLn (hello <> world)
-- тоже корректный синтаксис let hello = "Hello, "; world = "World!" in putStrLn (hello <> world)
-- НЕкорректный синтаксис let hello = "Hello, " = "World!" world in putStrLn (hello <> world)
(оператор
<>
здесь – оператор конкатенации)Само по себе выражение
let ... in ...
является вычислением, а не связыванием; связывания находятся междуlet
иin
.where
-связывания. Позволяют определить локальные связывания, доступные в правой части другого связывания. Общий синтаксис= expression name where <локальные связывания видимые в expression>
например
= putStrLn (hello <> world) main where = "Hello, " hello = "World!" world
Перед ключевым словом
where
обязательно должен быть отступ. Все новые связывания, объявляемые послеwhere
должны начинаться в одном столбце, и иметь отступ больший, чем связывание, к которому они относятся:-- корректный синтаксис = putStrLn (hello <> world) main where hello = "Hello, " = "World!" world
-- корректный синтаксис = putStrLn (hello <> world) main where = "Hello, " hello = "World!" world
-- корректный синтаксис = putStrLn (hello <> world) main where = "Hello, " hello = "World!" world
-- НЕкорректный синтаксис = putStrLn (hello <> world) main where = "Hello, " hello = "World!" world
-- НЕкорректный синтаксис = putStrLn (hello <> world) main where = "Hello, " hello = "World!" world
where-связывания тоже могут быть разделены
;
, но это считается плохим стилем:-- корректный синтаксис, но плохой стиль = putStrLn (hello <> world) main where hello = "Hello, "; world = "World!"
“Императивные” связывания и let-связывания. Haskell поддерживает симуляцию императивного стиля, которая “включается” ключевым словом
do
. Внутриdo
-блока связывания аналогичны синтаксисуlet ... in ...
, но безin ...
, и они не являются выражением. Также есть специальный синтаксис для вычислений с побочными эффектами:x <- someComputation
.Например:
= do main putStr "Пожалуйста, представьтесь: " <- getLine name putStrLn ("Здравствуйте, " <> name)
Будем обсуждать это подробнее в следующих работах.
Связывания внутри одного блока являются взаимно рекурсивными, т.е. порядок их объявления не имеет значения:
-- корректная программа
= putStrLn greeting
main where hello = "Hello, "
= "World!"
world = hello <> world greeting
-- тоже корректная программа
= putStrLn greeting
main where greeting = hello <> world
= "Hello, "
hello = "World!" world
Все имена вычислений в Haskell начинаются со строчной буквы, и чувствительны к регистру. Имя может начинаться на любую строчную букву (не обязательно латинскую!), и содержать в себе заглавные и строчные буквы, десятичные цифры и символы _
и '
. GHC поддерживает Unicode и ожидает, что исходный код в кодировке UTF-8.
В качестве вычислений могут использоваться имена (где-то объявленные), литералы (в т.ч. числовые, например 123
, строковые, например "string"
, символьные, например 'a'
), операторы (инфиксные бинарные), анонимные функции (λ-функции или сокращённо “лямбды”) и пр.
В том числе, вычисление может включать синтаксис if ... then ... else ...
. Следует отметить, что это конструкция if ... then ... else ...
из простого типизированного λ-исчисления, т.е. ведёт себя скорее как тренарный оператор из C-подобных языков (условие ? значение если истина : значение если ложь
).
Любые связывания могут быть как значениями (как в примерах выше), так и функциями.
Круглые скобки используются в выражениях для группировки.
Анонимные функции объявляются непосредственно в месте использования. Синтаксис похож на синтаксис объявления λ-абстракции, и имеет вид \<имя аргумента> -> <выражение>
. Тело функции (<выражение>
) продолжается до конца блока, поэтому обычно объявление анонимной функции берётся в круглые скобки. Например,
-> x + 3) (\x
Применение функций к аргументам, так же как в λ-исчислении, записывается просто через пробел:
-> x + 3) 6 -- == 9 (\x
Символом --
начинаются комментарии.
Функции поддерживают более одного аргумента, аргументы разделяются пробелом:
-> x + y) 6 5 -- == 11 (\x y
Следует однако иметь в виду, что, так же как и в λ-исчислении, все функции каррированные, т.е. функция 2 аргументов – это функция одного аргумента, возвращающая функцию одного аргумента, возвращающую значение. Функции могут быть частично применены просто указанием не всех аргументов, так
-> x + y) 6 (\x y
это функция одного аргумента, по поведению такая же, как
-> 6 + y) (\y
и вообще объявление функции нескольких аргументов по поведению аналогично объявлению нескольких вложенных функций одного аргумента:
-- functionA и functionB ведут себя одинаково
= \x y z -> x + y * z
functionA = \x -> \y -> \z -> x + y * z functionB
Возможно объявлять функции, просто связывая λ-выражения с именами, как в примере выше. Однако это несколько многословно. В Haskell есть сокращённый синтаксис для объявления именованных функций – аргументы функции записываются через пробел после имени функции, справа от знака равенства записывается тело функции:
-- functionShort и functionLambda ведут себя одинаково
= x + y * z
functionShort x y z = \x y z -> x + y * z functionLambda
Литералы
Основные литералы включают:
- целые
123
- дробные
123.123
, или в научной нотации123e3
,123e-3
. - символы
'a'
,'b'
,'→'
и т.п. Символы заключаются в одинарные кавычки - строки
"asd"
,"→←∀"
и т.п. Строки заключаются в двойные кавычки - булевские значения
True
иFalse
Haskell также допускает объявление пользовательских типов-перечислений со значениями-литералами, и имеет некоторое количество встроенных.
Операторы
Haskell позволяет объявлять пользовательские операторы как функции двух аргументов. Для этого в связывании символы, составляющие оператор, берутся в скобки. Например,
%) x y = mod x y (
объявляет функцию двух аргументов (%)
и одновременно инфиксный бинарный оператор %
(возвращающий остаток целочисленного деления):
5 % 2 -- == 1
9 % 3 -- == 0
15 % 4 -- == 3
У любого бинарного оператора в Haskell существует соответствующая ему функция, имеющая имя оператора в круглых скобках, например, (+)
, (*)
и т.п.
Операторы могут состоять из любого количества символов Unicode (как то, математические символы, знаки валют и т.п.), и символов пунктуации, кроме следующих: (),;[]`{}_"'
. Кроме того, оператор не может целиком состоять из двух или более символов -
(это пересекается с синтаксисом комментариев) и ..
не может быть объявлено оператором (зарезервированное ключевое слово)
По умолчанию, любой оператор, объявленный таким образом, считается левоассоциативным и имеет приоритет 9 (наивысший возможный для операторов). Чем выше приоритет, тем раньше оператор применяется (так, у оператора +
приоритет 6, а у *
приоритет 7) Переопределить это возможно директивами infix*
:
5 % infix
устанавливает оператору %
приоритет 5 и делает его неассоциативным. Аналогично, директива infixl
объявляет его как левоассоциативный, а infixr
как правоассоциативный. Эти директивы должны находится рядом с объявлением соответствующей функции.
Минимальный приоритет операторов – 0.
Применение функций (вызов с аргументами) имеет наивысший приоритет (выше любых операторов).
Любая именованная бинарная функция может быть использована как бинарный оператор, если её имя взято в обратные кавычки:
6 `mod` 4 -- == 2
Для функций тоже можно назначить ассоциативность и приоритет. Например, в стандартной библиотеке для функции mod
объявлен приоритет 7 и левая ассоциативность:
infixl 7 `mod`
Арифметические функции в Haskell
Базовые арифметические операторы в стандартной библиотеке включают:
Оператор/Функция | Смысл | Приоритет | Ассоциативность |
---|---|---|---|
+ |
Сложение | 6 | Левая |
- |
Вычитание | 6 | Левая |
* |
Умножение | 7 | Левая |
/ |
Деление | 7 | Левая |
^ |
Возведение в натуральную степень | 8 | Правая |
^^ |
Возведение в целую степень | 8 | Правая |
div |
Целочисленное деление с округлением вниз | 7 | Левая |
mod |
Остаток после div |
7 | Левая |
quot |
Целочисленное деление с округлением к нулю | 7 | Левая |
rem |
Остаток после rem |
7 | Левая |
Для операций целочисленного деления и остатков от него выполняются алгебраические законы:
quot x y) * y + (rem x y) == x
(div x y) * y + (mod x y) == x (
Приоритет применения функций выше любого оператора, поэтому код выше можно записать без скобок:
quot x y * y + rem x y == x
div x y * y + mod x y == x
Оператор ==
проверяет на равенство. Выражения выше всегда истины.
div
и mod
удобны при реализации модульной арифметики.
Подвох с унарным минусом
Как можно было заметить, все обсуждаемые операторы – бинарные. Как там насчёт унарного минуса?
В принципе, в Haskell есть унарный минус, но он не может быть определён средствами языка, как прочие бинарные операторы, и, как следствие, может вести себя несколько неожиданно.
В некоторых случаях, всё работает ожидаемо:
Prelude> -9
-9
В других случаях, возможны не слишком понятные ошибки:
Prelude> 1000 + -9
<interactive>:10:1: error:
Precedence parsing error
cannot mix ‘+’ [infixl 6] and prefix `-' [infixl 6] in the same infix expression
Здесь компилятор интерпретирует -
как бинарный оператор “минус” и сообщает, что не может разрешить приоритет операторов (+
и -
имеют одинаковый приоритет).
В общем и целом, любое однозначное подвыражение, начинающееся со знака -
считается унарным отрицанием и интерпретируется как вызов функции negate
. Унарный минус – единственный унарный оператор в стандартном Haskell. Ключевое слово здесь – “однозначное”. Чтобы сделать выражение выше однозначным, мы должны взять -9
в скобки:
Prelude> 1000 + (-9)
991
На практике, поскольку унарный минус – синоним (или, точнее, “синтаксический сахар”) для функции negate
, можно использовать функцию negatе
непосредственно:
Prelude> 1000 + negate 9
991
Операторы сравнения
Оператор | Смысл | Приоритет | Ассоциативность |
---|---|---|---|
== |
Равенство | 4 | Нет |
/= |
Неравенство | 4 | Нет |
< |
Меньше | 4 | Нет |
> |
Больше | 4 | Нет |
<= |
Меньше или равно | 4 | Нет |
>= |
Больше или равно | 4 | Нет |
Результатом всех перечисленных операций сравнения является булевское значение. Они могут быть использованы в условии if ... then ... else ...
, например
=
showCoolness x if x > 80 then "Pretty cool"
else if x > 60 then "It needs to be about 20% cooler"
else if x > 40 then "So-so"
else "Pretty lame"
Булевские операторы
Булевские значения можно комбинировать при помощи булевских операторов
Оператор | Смысл | Приоритет | Ассоциативность |
---|---|---|---|
&& |
Конъюнкция | 3 | Правая |
|| |
Дизъюнкция | 2 | Правая |
Кроме того, есть функция not
, соответствующая инверсии.
Булевские литералы имеют вид True
и False
.
Секционирование операторов
Как любая функция в Haskell, оператор может быть частично применён. Например,
= (*) 3 triple
создаст функцию одного аргумента, умножающую этот аргумент на три.
Такой синтаксис может показаться на первый взгляд странным, но (*) 3
– это функция двух аргументов, частично применённая (к первому аргументу); это функция одного (т.е. оставшегося второго) аргумента. Мы можем записать эквивалентное λ-выражение как
-> (*) 3 x \x
однако это то же самое, что просто (*) 3
(такая эквивалентность называется правилом η-редукции – эта-редукции). Затем этой функции (одного аргумента) мы назначаем имя triple
. Эквивалентно можно было бы написать
= (*) 3 x triple x
но мы можем опустить x
с обеих сторон по правилу η-редукции:
= (*) 3 triple
Это удобно для коммутативных операторов, однако, как быть в случае некоммутативных операторов, например, /
?
= x / 2 -- = (/) x 2 half x
Запись функции half
можно упростить (сократить), воспользовавшись т.н. секционированием операторов.
Любой оператор можно частично применить к первому либо ко второму аргументу, записав соответствующую часть выражения в круглых скобках. Например,
= (/2) half
Можно записать следующие соотношения эквивалентности:
*) == (*) x
(x*x) == \y -> (*) y x (
где *
символизирует любой бинарный оператор
Можно заметить, что во второй строчки по сути порядок аргументов меняется местами. В стандартной библиотеке есть функция flip
, которая делает именно это:
flip f = \x y -> f y x
или более коротко то же самое:
flip f x y = f y x
тогда второе соотношение можно так же записать в виде
*x) == flip (*) x
(-- эквивалентно (*x) == \y -> (*) y x
Единственный оператор, с которым этот синтаксис даёт сбой – оператор вычитания -
, поскольку (-x)
интерпретируется как унарное отрицание x
. Чтобы обойти это ограничение, в стандартной библиотеке объявлена функция subtract
:
subtract x y = y - x
она ведёт себя так же, как flip (-)
:
subtract = flip (-)
Сразу может быть не очень понятно, в каких случаях секционирование операторов оказывается полезным. Однако в дальнейшем этот синтаксис будет активно использоваться.
Упражнения
Упражнение 1
В интерактивной среде GHCi.
Объявите именованную функцию одного аргумента, способную вычислить любое из следующих выражений:
3.14*(5*5)
3.14*(10*10)
3.14*(2*2)
3.14*(4*4)
Используйте короткий и длинный (с λ-выражением) синтаксис для объявления функции.
Вычислите значение этой функции от различных аргументов.
Упражнение 2
Исправьте код на Haskell:
= 3. 14 * (x * x) area x
= b * 2 double x
let x = 7 = 10 y in x + y
Упражнение 3
Каков будет результат следующего кода на Haskell? Выполните вычисления в уме, затем проверьте себя в GHCi:
let x = 5 in x
let x = 5 in x * x
let x = 5; y = 6 in x * y
let x = 5; y = 1000 in x + 3
let y = 10; x = 10*5 + y in x * 5
let x = 7 = negate x y = y * 10 z in z / x + y
Упражнение 4
Перепишите выражения 2-6 из предыдущего упраженения, используя синтаксис where
вместо let
. Например, (1) можно записать в виде:
= x
five where x = 5
Имена произвольные.
Упражнение 5
Пусть в текущей области видимости имеется связывание
= 10 x
Вычислите в уме результат следующих выражений и проверьте себя в GHCi:
7 + x
+7) x (
-) 7 x (
-) x 7 (
subtract 5) x (
-x) + x (
Упражнение 6
Напишите функцию myAbs
, возвращающую абсолютное значение числа, используя выражение if ... then ... else
: