content: -- Мы работаем с языком, где любой объект имеет тип.
  -- Пока остановимся на типах для целых чисел:
  -- Int: это стандартное 32(64)-битное число;
  -- Integer: это тип для чисел произвольной величины (bigint).
  -- В Haskell функции являются объектами первого класса.
  -- Это означает, что мы можем принимать их в качестве аргументов
  -- и возвращать из других функций.
  -- Это лишает нас необходимости такого понятия, как функция нескольких аргументов.
  -- Любая функция принимает один аргумент, и, возможно, возращает функцию
  -- которая может требовать ещё аргументов.
  -- Подобный переход зачастую называется "каррированием".
  -- В HS есть стандартная функция curry. 
  -- Узнайте её тип на https://hoogle.haskell.org/ !
  -- Как вы можете увидеть, это ((a, b) -> c) -> a -> b -> c.
  -- Она полиморфна, но об этом мы поговорим позже, интуитивно этот тип понятен :)
  -- Так же есть двойственная ей uncurry :: (a -> b -> c) -> (a, b) -> c
  -- Давайте напишем нашу первую функцию и попробуем посмотреть на каррирование.
  
  sum' :: Integer -> Integer -> Integer
  sum' a b = a + b
  
  -- В GHCI узнайте тип sum' 5 3 и тип sum' 5, вычислите sum' 5 3.
  
  pairSum = uncurry sum'
  
  -- В GHCI узнайте тип pairSum.
  -- Она принимает на вход кортеж! Про устройство кортежей мы поговорим подробнее позже,
  -- пока нам стоит понимать, что кортеж состоит из фиксированного количества элементов
  -- с заранее определенными типами.
  -- В GHCI вычислите pairSum (5, 3).
  -- Давайте познакомимся с удобным синтаксисом guard statements.
  
  min' :: Integer -> Integer -> Integer
  min' x y
      | x < y = x
      | otherwise = y
  
  -- Пришло время познакомиться с ещё одним типом: Bool.
  -- Как вы и ожидаете, данный тип имеет всего два значения: true и false.
  -- В целом, на задание функции в HS стоит смотреть как на некоторый набор уравнений.
  -- При этом для разных аргументов уравнения могут быть разные.
  -- Это соответсвует синтаксису pattern matching.
  -- Давайте попробуем задать при помощи него xor и maj_3.
  
  xor :: Bool -> Bool -> Bool
  xor False True = True
  xor True False = True
  xor _ _ = False
  
  maj_3 :: Bool -> Bool -> Bool -> Bool
  maj_3 True True _ = True
  maj_3 True _ True = True
  maj_3 _ True True = True
  maj_3 _ _ _ = False
  
  -- Заметьте, что сопоставление происходит сверху вниз!
  -- Теперь мы готовы решить первую серьёзную задачу :)
  -- Напишем функцию вычисления факториала.
  -- Функциональное программирование накладывает на нас ограничение
  -- в виде неизменяемости переменных.
  -- Но ведь любой цикл требует переменной, по которой мы итерируемся...
  -- Как же нам обойтись без циклов? Рекурсия!
  -- Напишем самую примитивную реализцию Фибоначи.
  
  fib :: Integer -> Integer
  fib 0 = 1
  fib 1 = 1
  fib n = fib (n - 1) + fib (n - 2)
  
  -- Вычислите в GHCI fib 5.
  -- В чём проблема? Каждый вызов функции вызывает её еще два раза!
  -- Таким образом, мы заново считаем одно значение несколько раз.
  -- Как этого избежать? Надо "хранить" пару значений и обновлять её.
  
  step :: (Integer, Integer) -> Integer  -> (Integer, Integer)
  -- Заметьте, как мы можем распаковать кортеж в pattern matching!
  -- Будем поддерживать инвариант, что справа в кортеже у нас находится
  -- необходимое число Фибоначи.
  step pair 0 = pair
  step (a, b) n = step (b, a+b) (n-1)
  
  fib' :: Integer -> Integer
  -- Обратите внимание на то, что уравнения для функций не всегда требуют аргументов.
  -- Как понять, что нужна функция snd? Hoogle!
  -- $ просто меняет приоритет операций
  fib' x = snd $ step (0, 1) x
  
  -- Заметим, что step не нужна нам за пределами fib',
  -- для этого есть синтаксис where
  
  fib'' :: Integer -> Integer
  fib'' x = snd $ step' (0, 1) x
      where 
          step' pair 0 = pair
          step' (a, b) n = step' (b, a+b) (n-1)
      
  