Знакомство с Haskell. Компилятор GHC. Интерпретатор GHCi. Среда разработки. Выражения. Операторы.

Установка компилятора GHC

На текущий момент существует несколько способов получения компилятора GHC. Наиболее простые варианты это:

  1. Установка дистрибутива Haskell Platform https://www.haskell.org/platform/
  2. Установка менеджера проектов 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:

main = putStrLn "Hello, World!"

Сохраним эту программу в файле исходных кодов с названием 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
sayHello = putStrLn "Hello, World!"

-- Файл main.hs
import Lib
main = sayHello
> 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!, приведённой ранее,

main = putStrLn "Hello, World!"

вся строчка является связыванием, объявляющим имя main для вычисления в правой части равенства. putStrLn "Hello, World" является вычислением, как и любая составная его часть (например "Hello, World!").

Вычисления так же называются выражениями, а связывания – декларациями или объявлениями.

Основой для вычислительной модели Haskell является λ-исчисление, поэтому про вычисления, которые невозможно упростить применением операций (т.е. которые по сути являются значениями), мы говорим, что они находятся в нормальной форме. Например, 1+1 не находится в нормальной форме, а 2 – находится. Следует отметить, что функции без аргументов находятся в нормальной форме по определению.

В Haskell существует несколько способов ввести новые связывания (объявления):

  1. Объявления верхнего уровня. Эти связывания записываются непосредственно в файле исходного кода, как main в примере выше. Интерпретатор GHCi тоже поддерживает такой синтаксис.

  2. let-связывания в выражениях. Позволяет объявить локальные переменные, область видимости которых ограничена друг другом и одним выражением. Синтаксис таких связываний let <одно или несколько связываний> in <выражение>, например:

    main = let hello = "Hello, World!"
           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.

  3. where-связывания. Позволяют определить локальные связывания, доступные в правой части другого связывания. Общий синтаксис

    name = expression
      where
      <локальные связывания видимые в expression>

    например

    main = putStrLn (hello <> world)
      where
      hello = "Hello, "
      world = "World!"

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

    -- корректный синтаксис
    main = putStrLn (hello <> world)
      where hello = "Hello, "
            world = "World!"
    -- корректный синтаксис
    main = putStrLn (hello <> world)
      where
        hello = "Hello, "
        world = "World!"
    -- корректный синтаксис
    main = putStrLn (hello <> world)
           where
      hello = "Hello, "
      world = "World!"
    -- НЕкорректный синтаксис
    main = putStrLn (hello <> world)
          where
    hello = "Hello, "
        world = "World!"
    -- НЕкорректный синтаксис
    main = putStrLn (hello <> world)
      where
        hello = "Hello, "
      world = "World!"

    where-связывания тоже могут быть разделены ;, но это считается плохим стилем:

    -- корректный синтаксис, но плохой стиль
    main = putStrLn (hello <> world)
      where hello = "Hello, "; world = "World!"
  4. “Императивные” связывания и let-связывания. Haskell поддерживает симуляцию императивного стиля, которая “включается” ключевым словом do. Внутри do-блока связывания аналогичны синтаксису let ... in ..., но без in ..., и они не являются выражением. Также есть специальный синтаксис для вычислений с побочными эффектами: x <- someComputation.

    Например:

    main = do
      putStr "Пожалуйста, представьтесь: "
      name <- getLine
      putStrLn ("Здравствуйте, " <> name)

    Будем обсуждать это подробнее в следующих работах.

Связывания внутри одного блока являются взаимно рекурсивными, т.е. порядок их объявления не имеет значения:

-- корректная программа
main = putStrLn greeting
  where hello = "Hello, "
        world = "World!"
        greeting = hello <> world
-- тоже корректная программа
main = putStrLn greeting
  where greeting = hello <> world
        hello = "Hello, "
        world = "World!"

Все имена вычислений в Haskell начинаются со строчной буквы, и чувствительны к регистру. Имя может начинаться на любую строчную букву (не обязательно латинскую!), и содержать в себе заглавные и строчные буквы, десятичные цифры и символы _ и '. GHC поддерживает Unicode и ожидает, что исходный код в кодировке UTF-8.

В качестве вычислений могут использоваться имена (где-то объявленные), литералы (в т.ч. числовые, например 123, строковые, например "string", символьные, например 'a'), операторы (инфиксные бинарные), анонимные функции (λ-функции или сокращённо “лямбды”) и пр.

В том числе, вычисление может включать синтаксис if ... then ... else .... Следует отметить, что это конструкция if ... then ... else ... из простого типизированного λ-исчисления, т.е. ведёт себя скорее как тренарный оператор из C-подобных языков (условие ? значение если истина : значение если ложь).

Любые связывания могут быть как значениями (как в примерах выше), так и функциями.

Круглые скобки используются в выражениях для группировки.

Анонимные функции объявляются непосредственно в месте использования. Синтаксис похож на синтаксис объявления λ-абстракции, и имеет вид \<имя аргумента> -> <выражение>. Тело функции (<выражение>) продолжается до конца блока, поэтому обычно объявление анонимной функции берётся в круглые скобки. Например,

(\x -> x + 3)

Применение функций к аргументам, так же как в λ-исчислении, записывается просто через пробел:

(\x -> x + 3) 6 -- == 9

Символом -- начинаются комментарии.

Функции поддерживают более одного аргумента, аргументы разделяются пробелом:

(\x y -> x + y) 6 5 -- == 11

Следует однако иметь в виду, что, так же как и в λ-исчислении, все функции каррированные, т.е. функция 2 аргументов – это функция одного аргумента, возвращающая функцию одного аргумента, возвращающую значение. Функции могут быть частично применены просто указанием не всех аргументов, так

(\x y -> x + y) 6

это функция одного аргумента, по поведению такая же, как

(\y -> 6 + y)

и вообще объявление функции нескольких аргументов по поведению аналогично объявлению нескольких вложенных функций одного аргумента:

-- functionA и functionB ведут себя одинаково
functionA = \x y z -> x + y * z
functionB = \x -> \y -> \z -> x + y * z

Возможно объявлять функции, просто связывая λ-выражения с именами, как в примере выше. Однако это несколько многословно. В Haskell есть сокращённый синтаксис для объявления именованных функций – аргументы функции записываются через пробел после имени функции, справа от знака равенства записывается тело функции:

-- functionShort и functionLambda ведут себя одинаково
functionShort x y z = x + y * z
functionLambda = \x y z -> x + y * z

Литералы

Основные литералы включают:

  • целые 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*:

infix 5 %

устанавливает оператору % приоритет 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, оператор может быть частично применён. Например,

triple = (*) 3

создаст функцию одного аргумента, умножающую этот аргумент на три.

Такой синтаксис может показаться на первый взгляд странным, но (*) 3 – это функция двух аргументов, частично применённая (к первому аргументу); это функция одного (т.е. оставшегося второго) аргумента. Мы можем записать эквивалентное λ-выражение как

\x -> (*) 3 x

однако это то же самое, что просто (*) 3 (такая эквивалентность называется правилом η-редукции – эта-редукции). Затем этой функции (одного аргумента) мы назначаем имя triple. Эквивалентно можно было бы написать

triple x = (*) 3 x

но мы можем опустить x с обеих сторон по правилу η-редукции:

triple = (*) 3

Это удобно для коммутативных операторов, однако, как быть в случае некоммутативных операторов, например, /?

half x = x / 2 -- = (/) x 2

Запись функции half можно упростить (сократить), воспользовавшись т.н. секционированием операторов.

Любой оператор можно частично применить к первому либо ко второму аргументу, записав соответствующую часть выражения в круглых скобках. Например,

half = (/2)

Можно записать следующие соотношения эквивалентности:

(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:

  1. area x = 3. 14 * (x * x)
  2. double x = b * 2
  3. let x = 7
      y = 10
      in x + y

Упражнение 3

Каков будет результат следующего кода на Haskell? Выполните вычисления в уме, затем проверьте себя в GHCi:

  1. let x = 5 in x
  2. let x = 5 in x * x
  3. let x = 5; y = 6 in x * y
  4. let x = 5; y = 1000 in x + 3
  5. let y = 10; x = 10*5 + y in x * 5
  6. let x = 7
        y = negate x
        z = y * 10
    in z / x + y

Упражнение 4

Перепишите выражения 2-6 из предыдущего упраженения, используя синтаксис where вместо let. Например, (1) можно записать в виде:

five = x
  where x = 5

Имена произвольные.

Упражнение 5

Пусть в текущей области видимости имеется связывание

x = 10

Вычислите в уме результат следующих выражений и проверьте себя в GHCi:

  1. 7 + x
  2. (+7) x
  3. (-) 7 x
  4. (-) x 7
  5. (subtract 5) x
  6. (-x) + x

Упражнение 6

Напишите функцию myAbs, возвращающую абсолютное значение числа, используя выражение if ... then ... else: