{ "cells": [ { "cell_type": "markdown", "id": "088a86ab", "metadata": {}, "source": [ "# Числовые типы и классы\n", "\n", "Числовых типов много — это типы, которые реализуют класс Num:\n", "\n", "1. Double\n", "1. Float\n", "1. Int (Int8, Int16, ...)\n", "1. Integer\n", "1. RealFloat a => Num (Complex a) — это означает, что класс Complex будет являться числом, если выполняются некоторые условия на тип a. Условно, что с a можно работать как с вещественными числами. А вообще Complex — это число, составленное из двух частей, если мы пишем `2 :+ 3`, имеется в виду $2 + 3i$\n", "1. Integral a => Num (Ratio a)" ] }, { "cell_type": "code", "execution_count": 10, "id": "017bbbdc", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5.0 :+ 1.0" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import Data.Complex\n", "\n", "(2 :+ 3) * (1 :+ (-1)) -- (2+3i)(1-i) = 5+i" ] }, { "cell_type": "markdown", "id": "175acadf", "metadata": {}, "source": [ "Попробуем изучить, как устроен тип `Ratio`, он означает дроби типа 2/3 или 5/7, с ними можно производить арифметические операции. Значение этого типа создаётся с помощью конструктора `:%` (не доступен для нашего использования). Нам доступна функция (%), которая требует, чтобы оба аргумента были целыми числами (класс `Integral`)." ] }, { "cell_type": "code", "execution_count": 21, "id": "987ccd8d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2 % 3" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "1 % 2" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import Data.Ratio\n", "2 % 3\n", "1 % 3 + 1 % 6 -- 1/3 + 1/6 = 1/2" ] }, { "cell_type": "markdown", "id": "5c15cde4", "metadata": {}, "source": [ "Тип `type Rational = Ratio Integer`, т.е. отношение целых чисел реализует классы `Eq`, `Ord`, т.е. значения можно сравнивать:" ] }, { "cell_type": "code", "execution_count": 23, "id": "ad92dc9d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "1 % 2 < 2 % 3" ] }, { "cell_type": "markdown", "id": "d331b767", "metadata": {}, "source": [ "Еще `Rational` реализует класс `Num`, посмотрим, наконец, что можно делать с `Num`: `+`, `-`, `*`, `abs`, `signum`, `negate`, `fromInteger`:" ] }, { "cell_type": "code", "execution_count": 30, "id": "b71b4273", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(-2) % 3" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "1 % 1" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "42 % 1" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "85 % 2" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "negate $ 2 % 3\n", "signum $ (-2) % (-6) -- получаем 1 % 1, знак должен тоже иметь \n", "(fromInteger 42)::Rational -- можно рассмотреть результат как рациональное число\n", "-- (42::Integer) + (1 % 2) -- Integer и Rational не сложить вместе\n", "fromInteger (42::Integer) + (1 % 2) -- надо преобразовать с помощью fromInteger" ] }, { "cell_type": "markdown", "id": "986909b8", "metadata": {}, "source": [ "Кроме того `Rational` реализует классы `Real` и `RealFrac`. Посмотрим, что должны уметь значения типов, принадлежащих классу `Real`. Оказывается, для класса `Real` надо иметь класс `Num` (быть числом), `Ord` (быть упорядоченым) и реализовывать метод `toRational :: a -> Rational`." ] }, { "cell_type": "code", "execution_count": 31, "id": "f739e3c6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2 % 3" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "toRational $ 2 % 3 -- для рациональных чисел превращение в рациональное ничего не делает" ] }, { "cell_type": "markdown", "id": "ce9982cc", "metadata": {}, "source": [ "Кто еще реализует `Real`: `Double`, `Float`, `Int`, `Integer`:" ] }, { "cell_type": "code", "execution_count": 43, "id": "31733fb4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "42 % 1" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "5 % 8" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "4728779608739021 % 1125899906842624" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "884279719003555 % 281474976710656" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "toRational 42\n", "toRational 0.625 -- хранится без потери точности (в двоичной системе счисления = 0.конечное число)\n", "toRational 4.2\n", "toRational pi" ] }, { "cell_type": "markdown", "id": "ec1e7600", "metadata": {}, "source": [ "А что означает класс `RealFrac`: нужно реализовывать классы `Real` (уже изучили), `Fractional` (изучим следующим) и функции: `properFraction`, `truncate`, `round`, `ceiling`, `floor` (округление к 0, округление, округление вверх, округление вниз):" ] }, { "cell_type": "code", "execution_count": 49, "id": "6552e401", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(4,2 % 3)" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "5" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "properFraction :: forall a b. (RealFrac a, Integral b) => a -> (b, a)" ], "text/plain": [ "properFraction :: forall a b. (RealFrac a, Integral b) => a -> (b, a)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "properFraction $ 42 % 9 -- 4 + 2/3\n", "round $ 42 % 9 -- округление\n", ":type properFraction" ] }, { "cell_type": "markdown", "id": "b42e5cc1", "metadata": {}, "source": [ "Типы `Double` и `Float` тоже реализуют `RealFrac`, поэтому их тоже можно округлять:" ] }, { "cell_type": "code", "execution_count": 52, "id": "e0717b2c", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Redundant bracket
Found:
(4.5)
Why Not:
4.5
Redundant bracket
Found:
(4.2)
Why Not:
4.2
" ], "text/plain": [ "Line 2: Redundant bracket\n", "Found:\n", "(4.5)\n", "Why not:\n", "4.5Line 3: Redundant bracket\n", "Found:\n", "(4.2)\n", "Why not:\n", "4.2" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "4" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "(4,0.5)" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "(4,0.20000000000000018)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "round 4.2\n", "properFraction (4.5)\n", "properFraction (4.2)" ] }, { "cell_type": "markdown", "id": "3e83103d", "metadata": {}, "source": [ "Еще для Ratio реализуется класс `Fractional`: фактически, это числа, которые можно делить друг на друга. Есть методы `fromRational`, `recip`, `(\\)`. Типы `Double`, `Float` тоже имеют этот класс" ] }, { "cell_type": "code", "execution_count": 76, "id": "d2fe6d2e", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Redundant bracket
Found:
(toRational (0.5 :: Double)) / (2 % 3)
Why Not:
toRational (0.5 :: Double) / (2 % 3)
" ], "text/plain": [ "Line 8: Redundant bracket\n", "Found:\n", "(toRational (0.5 :: Double)) / (2 % 3)\n", "Why not:\n", "toRational (0.5 :: Double) / (2 % 3)" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "3 % 2" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "2.0" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "4 % 5" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "0.2" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "3 % 4" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "0.75" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "-- обратное число\n", "recip $ 2 % 3\n", "recip 0.5\n", "(2 % 3) / (5 % 6)\n", "0.5 / 2.5\n", "\n", "-- (0.5::Double) / (2 % 3) -- Double на Rational не поделить\n", "(toRational (0.5::Double)) / (2 % 3) -- приходится превращать в дробь или\n", "(0.5::Double) / fromRational (2 % 3) -- превращать дробь в вещественное" ] }, { "cell_type": "markdown", "id": "f915c7c1", "metadata": {}, "source": [ "Отдельные функции для `Rational`:" ] }, { "cell_type": "code", "execution_count": 83, "id": "af341bb4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "3" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "201 % 64" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "22 % 7" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "numerator (2 % 3)\n", "denominator (2 % 3)\n", "approxRational pi 0.001 -- округли pi с точностью до 0.0001\n", "approxRational pi 0.02" ] }, { "cell_type": "markdown", "id": "83fd934e", "metadata": {}, "source": [ "# Проект на Haskell\n", "\n", "Нужен инструмент, который позовляет собирать вашу программу, разбитую на несколько файлов с исходными кодами и использует сторонние библиотеки. (build tool). Самые распространенные для Haskell — это Cabal и Stack. Cabal более ранний, Stack частично его использует и решает часть его проблем. Stack основан на принципе, что разные сборки одной и той же программы должны получаться одинаковыми.\n", "В Cabal это не так, сегодня сборка прошла одним образом, завтра одна из сторонних библиотек обновилсась, ваша программа собралась по-другому. В хорошем случае в библиотеке была исправлена какая-то ошибка, и ваша программа стала работать лучше. В плохом случае, ваша программа перестанет собираться из-за изменения какой-нибудь функции в билиотеке.\n", "\n", "Чтобы Stack мог собирать программу одинаково, он пользуется особым источником сторонних библиотек, в котором может не быть последних версий этих библиотек.\n", "\n", "Хорошая новость, между проектами на Stack и на Cabal легко переходить, т.е. можно начать на Stack, потом использовать Cabal и наоборот.\n", "\n", "У нас будем использовать Stack: [Установка и использование](https://docs.haskellstack.org/en/stable/README/). [Установка и использование](https://docs.haskellstack.org/en/stable/README/). Более подробное [руководство по использованию](https://docs.haskellstack.org/en/stable/GUIDE/).\n", "\n", "Пользуемся из командной строки (или установите plugin к IDEA).\n", "\n", "Команда `stack new my-project` создаёт новый проект: `stack new projName [необязательный шаблон]`. Получаем каталог `my-project` со следующими файлами (часть этих файлов появится чуть позже)\n", "\n", "```\n", "my-project/\n", "├── app\n", "│   └── Main.hs\n", "├── ChangeLog.md\n", "├── LICENSE\n", "├── .gitignore\n", "├── my-project.cabal\n", "├── package.yaml\n", "├── README.md\n", "├── Setup.hs\n", "├── .stack-work - ...\n", "├── src\n", "│   └── Lib.hs\n", "├── stack.yaml\n", "├── stack.yaml.lock\n", "└── test\n", " └── Spec.hs\n", "```" ] }, { "cell_type": "markdown", "id": "b5a84d92", "metadata": {}, "source": [ "Перед тем, как смотреть, что есть в каталоге, давайте попробуем запустить проект. Вводим команду `stack run`, выводится результат `someFunc` (вместо `Hello World`)\n", "\n", "Что внутри каталога:\n", "1. `app` содержит код, который запускает программу, там есть функция Main\n", "1. `src` содержит все исходники, именно там надо писать код, именно в Lib.hs реализована функция someFunc, которая вызывается из Main при старте программы.\n", "1. `test` содержит код для тестирования программы.\n", "1. `ChangeLog.md`, `LICENSE`, `README.md`, `.gitignore` — стандартные файлы для программного проекта, выложенного в .git репозиторий.\n", "1. `my-project.cabal` — это настройка вашего проекта для инструмента Cabal, файл можно читать, там понятно написано, какие есть библиотеки, где исходный код, кто автор и т.п. Из-за того, что есть этот файл, Stack собирает программу с его помощью, вы можете начать использовать Cabal вместо Stack в любой момент. Но (!!!) этот файл нельзя редактировать, пока вы пользуетесь Stack, потому что Stack создает его на основе других настроек.\n", "1. `stack.yaml` это настройки проекта для Stack. \n", "1. `stack.yaml.lock` содержит информацию об используемых версиях библиотек, чтобы при новой сборке программе использовать те же самые.\n", "1. `.stack-work` — каталог, куда загружается компилятор Haskell нужной вам версии, и все библоиотеки. Благодаря тому, что в каждом проекте свой `.stack-work`, все проекты могут пользоваться разными версиями комплятора и библиотек." ] } ], "metadata": { "kernelspec": { "display_name": "Haskell", "language": "haskell", "name": "haskell" }, "language_info": { "codemirror_mode": "ihaskell", "file_extension": ".hs", "mimetype": "text/x-haskell", "name": "haskell", "pygments_lexer": "Haskell", "version": "8.10.4" } }, "nbformat": 4, "nbformat_minor": 5 }