├── CHANGELOG.md ├── README.md ├── basic-katas.md ├── bot-task.md ├── echo-bot-template ├── .gitignore ├── README.md ├── Setup.hs ├── app │ └── Main.hs ├── package.yaml ├── scripts │ └── lint ├── src │ ├── Config.hs │ ├── ConfigurationTypes.hs │ ├── EchoBot.hs │ ├── FrontEnd │ │ └── Console.hs │ ├── Logger.hs │ └── Logger │ │ └── Impl.hs ├── stack.yaml ├── stack.yaml.lock └── test │ ├── EchoBotSpec.hs │ └── Spec.hs ├── employment-faq.md ├── exercises-task.md ├── haskell-platform-package-list.md ├── how-to-learn.md ├── interview.md ├── optional-katas.md ├── review-task.md ├── server-task.md ├── theoretical-task.md └── why-haskell.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Список изменений 2 | 3 | Если вы уже сделали часть проекта, которую по новым правилам делать не нужно, - 4 | ничего страшного, можно не переделывать (а можно и переделать, если вам так 5 | проще). 6 | 7 | 8 | ## Обновление 29.03.2022 9 | 10 | 11 | - Бот: 12 | - Теперь вместо второго мессенджера (VK) предлагается использовать консоль, 13 | стандартный ввод-вывод. Это должно сильно уменьшить объем задания, сохранив 14 | его дух: общее ядро логики и два механизма ввода-вывода. 15 | - добавлен [шаблон проекта](https://github.com/fullstack-development/haskell-internship/tree/master/echo-bot-template) 16 | с тестами, типами и заглушками функций. Он поможет быстрее реализовать логику бота. 17 | 18 | - Сервер: убрали много ненужного, чтобы ускорить прохождение, и конкретизировали 19 | требования. 20 | - убрана сущность авторов. Вместо них у юзера появляется флажок "может 21 | создавать новости". 22 | - убраны поля фамилии и аватарки у юзеров 23 | - убрана сущность тегов 24 | - убрана сущность комментариев к новостям 25 | - убрана сущность черновиков. Теперь новость может редактироваться и у нее 26 | новый флажок "опубликовано". 27 | - убрано поле главной фотографии у новости. Теперь новость может содержать 28 | любое количество картинок, включая ноль. 29 | - убраны API удаления всего. 30 | - убраны API получения _одной сущности_ для всего, кроме картинок. Остальные 31 | сущности возвращаются только списками. 32 | - упрощена аутентификация. 33 | - уточнение: фильтр новостей и сортировка должны задаваться в URI query 34 | - уточнение: должно поддерживаться сразу несколько фильтров вместе с 35 | сортировкой 36 | - уточнение: запросы на редактирование должны поддерживать редактирование не 37 | фиксированного количества полей (одного либо всех), а любого. 38 | - уточнение требований к пагинации. 39 | - уточнение требований к получению и созданию картинок. В ответе с новостями 40 | нужно передавать URI картинок. В ответе с картинкой нужно посылать 41 | правильный `Content-Type`. 42 | - расширен список библиотек. Теперь можно использовать `servant` и любые 43 | высокоуровневые библиотеки для СУБД. 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn Haskell with MetaLamp 2 | 3 | 4 | Мы создали этот ресурс, чтобы дать одинаковые тестовые задания для всех 5 | кандидатов. Программа обучения открыта и бесплатна 6 | для всех. Подробнее о нас можно почитать в [FAQ](employment-faq.md). 7 | 8 | Не стесняйтесь задавать любые вопросы Светлане в Telegram (@Lana_Dulceva) или в 9 | [чате для обучающихся][students-chat]. 10 | 11 | Заданий в целом 5 штук, они довольно объемные, даже при усиленном прохождении это 12 | займет около 5 месяцев. Формат задач нацелен на то, чтобы системно и по порядку 13 | изучать основы и принципы разработки. Мы считаем, что очень грубая ошибка при 14 | изучении — кинуться в омут реальных практических задач и штудировать кучу 15 | конкретных высокоуровневых технологий (веб-фреймворки, как отличный пример, где 16 | помимо языка еще столько же времени надо потратить на изучение документации 17 | самого фреймворка). Мы сторонники того, что без фундаментальных знаний в 18 | технологии лучше не лезть :) Поэтому базис — сам язык, основные паттерны и 19 | принципы архитектуры. Далее самые базовые технологии (простейшие веб-сервера, 20 | простейшие обертки для работы с базами данных и тд). Ну а на выходе - большой 21 | рефакторинг на основе кучи разных источников и проверка нами ваших 22 | github-репозиториев с созданием issues. 23 | 24 | Также мы сторонники подхода, при котором новичков нельзя ставить на реальные 25 | проекты, пока они не сделали несколько своих средних проектов минимум на 2-3 26 | тысячи строк. Если сразу подключаться к реальным проектам, то есть огромный риск 27 | утонуть в чужом и непонятном легаси, потерять мотивацию, но главное — получить 28 | несистемный, очень отрывистый опыт, так как задачи будут довольно 29 | узконаправленные и кривая обучения будет неоднородной. В итоге, даже если стажер 30 | не растеряет всю мотивацию, его опыт за тот же промежуток времени будет гораздо 31 | слабее, чем у коллеги, который системно с нуля делал парочку своих проектов. 32 | 33 | В целом это должно быть отличной стажировкой для вас, мы готовы с радостью 34 | отвечать на ваши вопросы, 35 | а сами задания мы постарались сделать такими, чтобы на выходе у вас был максимум 36 | полезных практических и теоретических знаний. Еще нам бы очень хотелось видеть, 37 | как вы сами кооперируетесь друг с другом и решаете вместе возникающие проблемы. 38 | На вашу коммуникацию мы тоже будем обращать внимание - в первую очередь мы 39 | ожидаем увидеть в вас приветливого хорошего человека, с которым приятно 40 | совместно работать по 8 часов в день минимум :) 41 | 42 | [Часто задаваемые вопросы](employment-faq.md) 43 | 44 | [Почему именно Haskell](why-haskell.md) 45 | 46 | [Важное требование: отчеты](how-to-learn.md) 47 | 48 | [Задание 1: теория](theoretical-task.md) 49 | 50 | [Задание 2: задачки по языку](exercises-task.md) 51 | 52 | [Задание 3: бот](bot-task.md) 53 | 54 | [Задание 4: веб-сервер](server-task.md) 55 | 56 | [Задание 5: ревью](review-task.md) 57 | 58 | [Интервью по теоретическим вопросам](interview.md) 59 | 60 | [Список изменений](CHANGELOG.md) поможет понять, что изменилось в требованиях к 61 | заданиям, которые мы периодически дорабатываем. 62 | 63 | [students-chat]: https://t.me/learn_haskell_with_fsd 64 | -------------------------------------------------------------------------------- /basic-katas.md: -------------------------------------------------------------------------------- 1 | # Рекомендуемые каты 2 | 3 | 4 | Ниже рекомендуемые каты из 5 | [Codewars](https://www.codewars.com/kata/search/haskell). Они примерно 6 | отсортированы от легких к сложным, выбирайте сами подходящий момент, когда 7 | начинать их делать (но по большей части после 6 главы Learn you a Haskell можно 8 | смело браться за первые задания). 9 | 10 | 1. [Is this a triangle](https://www.codewars.com/kata/is-this-a-triangle) 11 | 12 | Определение, могут ли данных три числа быть длинами сторон треугольника. 13 | 14 | С этой катой тренируются базовые логические конструкции. Полезно, что есть 15 | простор и для элегантных решений, и для решений "в лоб". 16 | 17 | Есть вероятность, что практикующий потренирует так же совсем базовую работу 18 | со списками (хотя тут это необязательно). 19 | 20 | 2. [Disemvowel trolls](https://www.codewars.com/kata/disemvowel-trolls) 21 | 22 | Задача на удаление всех гласных (почти) из строки. 23 | 24 | Позволяет закрепить отношение к строкам, как к списку символов, 25 | тренирует работу со списками, особенно базовые навыки фильтрации. Ката 26 | была выбрана в том числе из-за интересного оформления самой задачи и 27 | конкретного use case, где нужно защищаться от кричащих троллей :) 28 | 29 | 3. [Highest and lowest](https://www.codewars.com/kata/highest-and-lowest) 30 | 31 | Найти самое большое и самое маленькое значение из строки чисел, которую 32 | надо предварительно распарсить. 33 | 34 | Отличная задача для начального уровня, позволяет натренировать парсинг 35 | инстансов Read и самую базовую работу со списками (на поиск наименьшего 36 | и наибольшего значений). Естественно, и то, и другое, будет полезно уже 37 | при решении реальных задач. 38 | 39 | 4. [Isograms](https://www.codewars.com/kata/isograms/) 40 | 41 | Задача на вычисление дубликатов в массиве. Это наглядный экземпляр 42 | стандартного алгоритмического задания, которое в разных вариациях часто 43 | встречается в упражнениях почти на всех языках. Здесь же можно 44 | опробовать её на Хаскеле и сравнить (на деле или интуитивно) с тем, как 45 | бы это решалось на остальных языках. 46 | 47 | Как и почти во всех классических задачах, тут есть простор для фантазии, 48 | несмотря на простоту описания. Можно решать как банальными 49 | конструкциями, а можно попытаться найти что-то изящное :) 50 | 51 | 5. [Split strings](https://www.codewars.com/kata/split-strings) 52 | 53 | Задача с предельно простым описанием, очень ясным примером, но которая 54 | не такая очевидная в решении. Хорошо иллюстрирует работы с 55 | паттерн-матчингом и конструированием списков, которые тут приходится то 56 | разбирать, то собирать. Ну и, естественно, отличная тренировка самой 57 | базовой рекурсии. 58 | 59 | 6. [Tribonacci sequence](https://www.codewars.com/kata/tribonacci-sequence) 60 | 61 | Интересная вариация набившей оскомину задачи про ряд Фибоначчи. Очень 62 | простое с виду изменение правил игры заставляет призадуматься. Ката 63 | отлично тренирует конструирование списков и рекурсию, либо умение 64 | обходиться без рекурсии стандартными функциями высшего порядка :) 65 | 66 | 7. [Title case](https://www.codewars.com/kata/title-case) 67 | 68 | Кульминация базовых тренировок, где необходимо уметь работать и с 69 | символами, и со строками, и со списками. Задача, которая действительно 70 | может встретиться в боевых условиях, и которая позволит показать навыки 71 | написания "читаемого" кода. Здесь есть возможность, как для явной 72 | рекурсии, так и для использования стандартных функций высшего порядка, 73 | есть интересное требование замены буквы на заглавный аналог и условие, 74 | когда вообще ничего делать не надо :) 75 | 76 | -------------------------------------------------------------------------------- /bot-task.md: -------------------------------------------------------------------------------- 1 | # Третье задание: бот 2 | 3 | 4 | Нужно написать эхо-бота, который умеет просто отправлять сообщение от 5 | пользователя ему же в ответ. 6 | 7 | Бот должен уметь работать с сообщениями через несколько механизмов доставки: 8 | 9 | - консоль: сообщение пользователя вводится со стандартного ввода, ответ бота 10 | выводится в стандартный вывод (например, с помощью 11 | [`getLine`](https://hackage.haskell.org/package/base-4.16.0.0/docs/Prelude.html#v:getLine) 12 | и 13 | [`putStrLn`](https://hackage.haskell.org/package/base-4.16.0.0/docs/Prelude.html#v:putStrLn)). 14 | - Telegram: https://core.telegram.org/bots/api#poll 15 | 16 | Вместо Telegram можно выбрать VK (см. [документацию](https://vk.com/dev/bots_longpoll)) или Slack. 17 | 18 | 19 | ## Функциональные требования 20 | 21 | 22 | 1. Пользователь может отправить команду `/help` и увидеть текст, описывающий бота. 23 | 24 | 2. Пользователь может отправить команду `/repeat`, и в ответ бот отправит, какое 25 | сейчас выбрано значение повторов и вопрос, сколько раз повторять сообщение в 26 | дальнейшем. К вопросу будут прилагаться кнопки для выбора ответа (кнопки с 27 | цифрами от 1 до 5). После выбора пользователем все ответы бота должны 28 | дублировать сообщение пользователя указанное кол-во раз. Кол-во повторов 29 | должно быть индивидуальным для каждого пользователя, т. е. если один 30 | пользователь выбрал 3 повторения, то второму мы по-прежнему показываем 31 | начальное кол-во сообщений. 32 | 33 | 3. Все должно быть максимально кастомизируемо через конфиги: 34 | 35 | 1. Сообщение, отправляемое в ответ на `/help`. 36 | 2. Вопрос по команде `/repeat`. 37 | 3. Начальное кол-во повторов на каждый ответ. 38 | 39 | 4. Бот должен уметь повторять только текстовые сообщения и какой-нибудь один вид 40 | мультимедийных сообщений (например, стикеры или картинки). Остальные виды 41 | сообщений можно игнорировать. Конечно, этот пункт не распространяется на 42 | сообщения из консоли. 43 | 44 | 45 | ## Технические требования 46 | 47 | 48 | 1. Можно взять за основу 49 | [шаблон проекта](https://github.com/fullstack-development/haskell-internship/tree/master/echo-bot-template). 50 | Он содержит "скелет" логики бота и некоторые тесты. Запустите `stack test` и 51 | попробуйте исправить логику, чтобы тесты начали проходить. Шаблон можно как угодно 52 | править по своему усмотрению, а можно не использовать совсем. 53 | 54 | 1. Для основного кода проекта (кроме тестов) использовать только библиотеки из 55 | стандартной поставки [Haskell Platform](haskell-platform-package-list.md) и 56 | три сторонние: 57 | 58 | 1. Для отправки http-запросов 59 | 2. Для парсинга json 60 | 3. Для работы с конфигом. 61 | 62 | 2. Все остальное должно быть сделано по максимуму без библиотек. Для тестов 63 | можете использовать любой удобный вам инструмент (`hspec`, `HUnit`, etc). 64 | 65 | 3. Обновления от Телеграма получать не посредством веб-хуков, а посредством 66 | поллинга. Отправлять запрос за апдейтами телеграму, тот сам будет ставить 67 | ответ на паузу, если обновок нет, и отвечать сразу, как только что-то 68 | появилось. Ну или отвечать пустым массивом по таймауту. Это требование вкупе 69 | с тем, что в следующем задании надо будет свой сервер на `Warp` реализовать, 70 | поможет лучше понять, что такое модель поллинга и модель пуша (через 71 | веб-хуки), в чем преимущества и недостатки каждой из моделей. 72 | 73 | 4. Результатом должно быть одно приложение, а не два. В каком режиме его 74 | запускать (Telegram или консоль), определяется параметром в конфиге или 75 | опцией командной строки. 76 | 77 | 78 | ## Следующие технические требования также распространяются и на следующее задание "Веб-сервер" 79 | 80 | 81 | 1. Проект должен быть в отдельном репозитории на github, во время выполнения 82 | задания коммиты делать как можно чаще, как минимум раз в день, когда написана 83 | хоть строчка кода. 84 | 85 | 2. Использовать [stack](https://www.haskellstack.org/), все используемые 86 | библиотеки должны быть зафиксированы в файле `package.yaml`, сам проект 87 | должен быть инициирован командой `stack new`, которая создает базовую 88 | структуру Haskell-проекта. 89 | 90 | 3. Для разворачивания должно быть достаточно клонирования репозитория и запуска 91 | `stack build`. Обязательно проверить это правило клонированием репозитория в 92 | отдельную папку у себя и запуска `stack build` — результатом должны быть 93 | собранные и рабочие бинарники. 94 | 95 | 4. У каждого проекта должно быть `README` с описанием того, как разворачивать 96 | проект локально и как его запускать, а так же с описанием базовой структуры, 97 | чтобы новичок мог легко разобраться (представьте, что после вас проект будет 98 | поддерживать совсем нулевой джуниор). Все должно быть на английском. 99 | 100 | 5. Проект должен иметь файл `.gitignore`, куда внесены все автогенерируемые файлы 101 | проекта, локальные конфиги и т.д. Обязательно добавьте туда следующие папки 102 | (даже если вы не пользуетесь редактором VSCode, им пользуемся мы и это 103 | правило для нашего удобства при проверке): 104 | 105 | - `.vscode` 106 | - `.history` 107 | 108 | 6. Проект должен быть покрыт unit-тестами, которые проверяют корректность бизнес логики. 109 | Например, тестировать функцию отправки сообщений через Telegram API не требуется, 110 | но должны быть тесты, проверяющие, что в ответ на команду `/repeat`, пользователю 111 | будет отправлен запрос на выбор числа повторений. 112 | 113 | 7. Конфиги должны быть вынесены в отдельный файл с возможностью переписать 114 | локально какие-нибудь значения, но не изменять файлы из git-репозитория, 115 | чтобы случайно не запушить пароль или токен. 116 | 117 | 8. Проект должен поддерживать логи разных уровней, все ключевые моменты должны 118 | грамотно логироваться, логи должны легко конфигурироваться хотя бы так, чтобы 119 | можно было включать/выключать логи до определенного уровня (например, 120 | показывать все от `DEBUG` и выше, или показывать все от `WARN` и выше). 121 | 122 | 9. Для понятной архитектуры рекомендуем использовать [Handle 123 | Pattern](https://jaspervdj.be/posts/2018-03-08-handle-pattern.html), так как 124 | мы применяем его в большинстве своих проектов. 125 | 126 | 10. Чтобы добиться понятной архитектуры проекта, и получить тестируемый код, 127 | также можно применять различные техники (паттерны) описанные сообществом: 128 | 129 | - [The Service Pattern](https://www.schoolofhaskell.com/user/meiersi/the-service-pattern) 130 | - [The ReaderT Design Pattern (Discussion)](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern) 131 | - [Three Layer Haskell Cake](https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html) 132 | 133 | 11. Полезно почитать требования к проекту из задания 5 (код-ревью). На старте 134 | проекта необязательно забивать себе ими голову — потом успеете, но, 135 | все-таки, если знать их заранее, придется меньше исправлять. 136 | 137 | 138 | ## Источники 139 | 140 | 141 | * Для начала можно посмотреть [простую 142 | статью](https://www.bekk.christmas/post/2019/23/making-a-small-haskell-application) 143 | про то, как начать собирать первое приложение по отправке HTTP-запроса и 144 | получению одного нужного поля из JSON 145 | 146 | * [Designing Testable Components](http://felixmulder.com/writing/2019/10/05/Designing-testable-components.html) 147 | 148 | * Полезные статьи для данного задания: 149 | 150 | * [Telegram-бот на Python: от первой строчки до запуска на Heroku](https://web.archive.org/web/20210120102233/https://tproger.ru/translations/telegram-bot-create-and-deploy/amp/) 151 | * https://artyom.me/aeson 152 | * https://ruhaskell.org/posts/packages/2015/02/03/aeson-hello-world.html 153 | * https://ruhaskell.org/posts/packages/2015/03/05/aeson-next.html 154 | -------------------------------------------------------------------------------- /echo-bot-template/.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | /echo-bot.cabal 4 | -------------------------------------------------------------------------------- /echo-bot-template/README.md: -------------------------------------------------------------------------------- 1 | # echo-bot 2 | -------------------------------------------------------------------------------- /echo-bot-template/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /echo-bot-template/app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main 2 | ( main, 3 | ) 4 | where 5 | 6 | import qualified Config 7 | import qualified ConfigurationTypes 8 | import Data.IORef (modifyIORef', newIORef, readIORef) 9 | import qualified Data.Text as T 10 | import qualified EchoBot 11 | import qualified FrontEnd.Console 12 | import qualified Logger 13 | import qualified Logger.Impl 14 | import System.Exit (die) 15 | 16 | main :: IO () 17 | main = do 18 | withLogHandle $ \logHandle -> do 19 | frontEnd <- Config.getFrontEndType 20 | case frontEnd of 21 | ConfigurationTypes.TelegramFrontEnd -> 22 | error "Not implemented" 23 | ConfigurationTypes.ConsoleFrontEnd -> do 24 | botHandle <- makeBotHandleForPlainText logHandle 25 | runConsoleFrontEnd botHandle 26 | 27 | runConsoleFrontEnd :: EchoBot.Handle IO T.Text -> IO () 28 | runConsoleFrontEnd botHandle = 29 | FrontEnd.Console.run 30 | FrontEnd.Console.Handle {FrontEnd.Console.hBotHandle = botHandle} 31 | 32 | withLogHandle :: (Logger.Handle IO -> IO ()) -> IO () 33 | withLogHandle f = do 34 | config <- Config.getLoggerConfig 35 | Logger.Impl.withHandle config f 36 | 37 | -- | Creates a bot handle. Please note: 38 | -- 39 | -- * a handle holds a reference to an 'IORef' with a 'EchoBot.State', 40 | -- so that it can only keep state of a single user. In order to 41 | -- support multiple users in a chat, you should create a new handle 42 | -- for each user and probably keep them in a 'Data.Map' keyed by a 43 | -- user id. 44 | -- 45 | -- * 'EchoBot.Handle' is parameterized with the 'Text' type, so that 46 | -- it supports only plain text messages suitable for the console. 47 | -- When implementing Telegram or another multimedia chat support, 48 | -- you should create a similar function, but parameterized with 49 | -- another message type which can represent either text or 50 | -- multimedia messages. You will need to specify different functions 51 | -- @hMessageFromText@ and @hTextFromMessage@. 52 | makeBotHandleForPlainText :: Logger.Handle IO -> IO (EchoBot.Handle IO T.Text) 53 | makeBotHandleForPlainText logHandle = do 54 | botConfig <- Config.getBotConfig 55 | initialState <- either (die . T.unpack) pure $ EchoBot.makeState botConfig 56 | stateRef <- newIORef initialState 57 | pure 58 | EchoBot.Handle 59 | { EchoBot.hGetState = readIORef stateRef, 60 | EchoBot.hModifyState' = modifyIORef' stateRef, 61 | EchoBot.hLogHandle = logHandle, 62 | EchoBot.hConfig = botConfig, 63 | EchoBot.hTextFromMessage = Just, 64 | EchoBot.hMessageFromText = id 65 | } 66 | -------------------------------------------------------------------------------- /echo-bot-template/package.yaml: -------------------------------------------------------------------------------- 1 | name: echo-bot 2 | version: 0.1.0.0 3 | github: "unknown/unknown" 4 | license: BSD3 5 | author: "John Doe" 6 | maintainer: "unknown@example.com" 7 | copyright: "John Doe" 8 | 9 | extra-source-files: 10 | - README.md 11 | 12 | # Metadata used when publishing your package 13 | # synopsis: Short description of your package 14 | # category: Web 15 | 16 | # To avoid duplicated efforts in documentation and dealing with the 17 | # complications of embedding Haddock markup inside cabal files, it is 18 | # common to point users to the README.md file. 19 | description: Please see the README 20 | 21 | dependencies: 22 | - base >= 4.7 && < 5 23 | - text 24 | - mtl 25 | - QuickCheck 26 | - hspec 27 | 28 | ghc-options: 29 | - -Wall 30 | - -Werror 31 | - -Wcompat 32 | - -Wincomplete-uni-patterns 33 | 34 | library: 35 | source-dirs: src 36 | 37 | executables: 38 | echo-bot-exe: 39 | main: Main.hs 40 | source-dirs: app 41 | ghc-options: 42 | - -threaded 43 | - -rtsopts 44 | - -with-rtsopts=-N 45 | dependencies: 46 | - echo-bot 47 | 48 | tests: 49 | echo-bot-test: 50 | main: Spec.hs 51 | source-dirs: test 52 | ghc-options: 53 | - -threaded 54 | - -rtsopts 55 | - -with-rtsopts=-N 56 | dependencies: 57 | - echo-bot 58 | -------------------------------------------------------------------------------- /echo-bot-template/scripts/lint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | cd "$(git rev-parse --show-toplevel)" 6 | find . -name '*.hs' -print0 | xargs -0n100 ormolu --mode inplace 7 | stack test 8 | stack build 9 | hlint . 10 | -------------------------------------------------------------------------------- /echo-bot-template/src/Config.hs: -------------------------------------------------------------------------------- 1 | -- | A module to provide a configuration reader for other modules. 2 | module Config 3 | ( getBotConfig, 4 | getLoggerConfig, 5 | getFrontEndType, 6 | ) 7 | where 8 | 9 | import qualified ConfigurationTypes 10 | import qualified EchoBot 11 | import qualified Logger.Impl 12 | 13 | -- | Gets the bot config. In any case it can provide reasonable 14 | -- default values. 15 | getBotConfig :: IO EchoBot.Config 16 | getBotConfig = error "Not implemented" 17 | 18 | getLoggerConfig :: IO Logger.Impl.Config 19 | getLoggerConfig = error "Not implemented" 20 | 21 | getFrontEndType :: IO ConfigurationTypes.FrontEndType 22 | getFrontEndType = error "Not implemented" 23 | -------------------------------------------------------------------------------- /echo-bot-template/src/ConfigurationTypes.hs: -------------------------------------------------------------------------------- 1 | module ConfigurationTypes 2 | ( FrontEndType (..), 3 | ) 4 | where 5 | 6 | data FrontEndType 7 | = ConsoleFrontEnd 8 | | TelegramFrontEnd 9 | -------------------------------------------------------------------------------- /echo-bot-template/src/EchoBot.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | The pure echo-bot logic module. It doesn't know anything about 4 | -- Telegram, other chat protocols, or any input/output. This is why we 5 | -- can easily test it. 6 | module EchoBot 7 | ( makeState, 8 | respond, 9 | Event (MessageEvent), 10 | Response (..), 11 | State, 12 | Handle (..), 13 | Config (..), 14 | ) 15 | where 16 | 17 | import Data.Maybe (fromMaybe) 18 | import Data.Text (Text) 19 | import qualified Data.Text as T 20 | import Logger ((.<)) 21 | import qualified Logger 22 | 23 | -- | The bot dependencies that the caller code should satisfy. 24 | -- 25 | -- Its @m@ parameter is a monad type where all actions are performed. 26 | -- Since the bot doesn't know what @m@ is, it cannot use arbitrary IO. 27 | -- Everything it can do with the monad is passed as handle fields, 28 | -- like 'hModifyState\''. For example, @m@ can be the 'IO' or 'State' 29 | -- monad. 30 | -- 31 | -- Its @a@ parameter is the message type. We don't impose any 32 | -- restrictions on the message type, hence the bot can support all 33 | -- possible message types, while 'hTextFromMessage' and 34 | -- 'hMessageFromText' are implemented. 35 | data Handle m a = Handle 36 | { hLogHandle :: Logger.Handle m, 37 | hConfig :: Config, 38 | -- | Returns the current 'State'. 39 | hGetState :: m State, 40 | -- | Updates the current state with applying the given function. 41 | -- You may find it similar to the 'modify'` method of the 'State' 42 | -- monad, as well as 'hGetState' is similar to 'get'. 43 | hModifyState' :: (State -> State) -> m (), 44 | -- | Returns @Just text@ for a pure text message, so that the bot 45 | -- could try to parse it as a command. Otherwise it returns 46 | -- 'Nothing'. 47 | hTextFromMessage :: a -> Maybe T.Text, 48 | -- | Constructs a message from a pure text. The bot can use it for 49 | -- creating a help text message. 50 | -- 51 | -- Note that instead of adding 'hTextFromMessage' and 52 | -- 'hMessageFromText' into 'Handle', we might make them into 53 | -- methods of @Message@ type class. These two variants are very 54 | -- similar for our goals. 55 | hMessageFromText :: T.Text -> a 56 | } 57 | 58 | -- | The initial configuration of the bot. 59 | data Config = Config 60 | { -- | A reply to the @help@ command 61 | confHelpReply :: Text, 62 | -- | A reply to the @repeat@ command. The string of @{count}@ in 63 | -- the text will be replaced with the current repetition count, so 64 | -- that you can use a template string like @"The new repetition 65 | -- count is {count}."@. 66 | confRepeatReply :: Text, 67 | -- | The initial repetition count for echoing messages to start 68 | -- with. 69 | confRepetitionCount :: Int 70 | } 71 | 72 | -- | An external event that the bot should process and respond to. 73 | -- It's parameterized with a message type. 74 | data Event a 75 | = -- | The user has sent a message. 76 | MessageEvent a 77 | | -- | Set the repetition count. This event is hidden from the 78 | -- outside code. The caller code cannot construct it, only get 79 | -- from a 'MenuResponse'. 80 | SetRepetitionCountEvent RepetitionCount 81 | deriving (Eq, Show) 82 | 83 | -- | The bot response to an event. It's parameterized with a message 84 | -- type. 85 | data Response a 86 | = -- | A command to output a message to the user. 87 | MessageResponse a 88 | | -- | A command to output a menu with the given title and options. 89 | -- Each option is a pair of: 90 | -- 91 | -- * a new repetition amount 92 | -- * an event that the caller code should sent to us if the user 93 | -- chooses this repetition count 94 | MenuResponse Title [(RepetitionCount, Event a)] 95 | deriving (Eq, Show) 96 | 97 | type Title = Text 98 | 99 | type RepetitionCount = Int 100 | 101 | -- | An internal state of the bot. 102 | -- 103 | -- Note that we don't have to deal with multiple users here, we only 104 | -- keep the number of repetitions for a single user. Let the caller 105 | -- code be responsible for tracking multiple users and states for 106 | -- their bots. 107 | newtype State = State 108 | { stRepetitionCount :: RepetitionCount 109 | } 110 | 111 | -- | Creates an initial, default bot state for a user. 112 | makeState :: Config -> Either Text State 113 | makeState conf = do 114 | checkConfig conf 115 | pure State {stRepetitionCount = confRepetitionCount conf} 116 | 117 | checkConfig :: Config -> Either Text () 118 | checkConfig conf = 119 | if confRepetitionCount conf < 0 120 | then Left "The repetition count must not be negative" 121 | else Right () 122 | 123 | -- | Evaluates responses for the passed event. 124 | respond :: Monad m => Handle m a -> Event a -> m [Response a] 125 | respond h (SetRepetitionCountEvent repetitionCount) = 126 | handleSettingRepetitionCount h repetitionCount 127 | respond h (MessageEvent message) 128 | | isCommand h "/help" message = handleHelpCommand h 129 | | isCommand h "/repeat" message = handleRepeatCommand h 130 | | otherwise = respondWithEchoedMessage h message 131 | 132 | isCommand :: Handle m a -> T.Text -> a -> Bool 133 | isCommand h _ message = case hTextFromMessage h message of 134 | Nothing -> False 135 | Just _ -> error "Not implemented" 136 | 137 | handleHelpCommand :: Monad m => Handle m a -> m [Response a] 138 | handleHelpCommand h = do 139 | Logger.logInfo (hLogHandle h) "Got the help command" 140 | error "Not implemented" 141 | 142 | handleSettingRepetitionCount :: Monad m => Handle m a -> Int -> m [Response a] 143 | handleSettingRepetitionCount h count = do 144 | Logger.logInfo (hLogHandle h) $ "The user has set the repetition count to " .< count 145 | error "Not implemented" 146 | 147 | handleRepeatCommand :: Monad m => Handle m a -> m [Response a] 148 | handleRepeatCommand h = do 149 | Logger.logInfo (hLogHandle h) "Got the repeat command" 150 | error "Not implemented" 151 | 152 | respondWithEchoedMessage :: Monad m => Handle m a -> a -> m [Response a] 153 | respondWithEchoedMessage h message = do 154 | Logger.logInfo (hLogHandle h) $ 155 | "Echoing user input: " .< fromMaybe "" (hTextFromMessage h message) 156 | error "Not implemented" 157 | -------------------------------------------------------------------------------- /echo-bot-template/src/FrontEnd/Console.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | The console front-end is responsible for console I/O and 4 | -- appropriate handling of other high-level bot interactions (menu 5 | -- output etc). 6 | module FrontEnd.Console 7 | ( run, 8 | Handle (..), 9 | ) 10 | where 11 | 12 | import qualified Data.Text as T 13 | import qualified Data.Text.IO as TIO 14 | import qualified EchoBot 15 | 16 | newtype Handle = Handle 17 | { hBotHandle :: EchoBot.Handle IO T.Text 18 | } 19 | 20 | run :: Handle -> IO () 21 | run _ = do 22 | TIO.putStrLn "Welcome to the echo-bot!" 23 | -- 1. Read a line from the console. 24 | -- 2. Send it to the bot, get its response and output it. 25 | -- 3. Go to 1. 26 | error "Not implemented" 27 | -------------------------------------------------------------------------------- /echo-bot-template/src/Logger.hs: -------------------------------------------------------------------------------- 1 | -- | The logger interface module. It should not define a specific 2 | -- implementation. 3 | module Logger 4 | ( Handle (..), 5 | Level (..), 6 | logDebug, 7 | logInfo, 8 | logWarning, 9 | logError, 10 | (.<), 11 | ) 12 | where 13 | 14 | import qualified Data.Text as T 15 | 16 | -- | The logger handle. This is a public logger interface that can 17 | -- have different implementations. You can use it everywhere. 18 | newtype Handle m = Handle 19 | { hLowLevelLog :: Level -> T.Text -> m () 20 | } 21 | 22 | data Level 23 | = Debug 24 | | Info 25 | | Warning 26 | | Error 27 | deriving (Show, Eq, Ord) 28 | 29 | logDebug, logInfo, logWarning, logError :: Handle m -> T.Text -> m () 30 | logDebug h = hLowLevelLog h Debug 31 | logInfo h = hLowLevelLog h Info 32 | logWarning h = hLowLevelLog h Warning 33 | logError h = hLowLevelLog h Error 34 | 35 | -- | Concatenates a text and an instance of 'Show'. This is a 36 | -- convenience function to make logger function applications more 37 | -- concise: 38 | -- 39 | -- > Log.logError (hLogger h) "The error code is " .< e 40 | (.<) :: (Show a) => T.Text -> a -> T.Text 41 | text .< a = text <> T.pack (show a) 42 | 43 | infixr 7 .< 44 | -------------------------------------------------------------------------------- /echo-bot-template/src/Logger/Impl.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | The default implementation of the Logger interface. 4 | module Logger.Impl 5 | ( withHandle, 6 | Config (..), 7 | ) 8 | where 9 | 10 | import qualified Data.Text as T 11 | import qualified Logger 12 | import qualified System.IO 13 | 14 | data Config = Config 15 | { -- | A file handle to output formatted log messages to with 16 | -- 'System.IO.hPutStrLn' or 'Data.Text.IO.hPutStrLn'. For example, 17 | -- it might be 'System.IO.stderr' or a handle of a regular open 18 | -- file. 19 | confFileHandle :: System.IO.Handle, 20 | -- | The minimum level of a printable log message. Messages with 21 | -- lower levels should not be printed. 22 | confMinLevel :: Logger.Level 23 | } 24 | 25 | withHandle :: Config -> (Logger.Handle IO -> IO ()) -> IO () 26 | withHandle config f = f Logger.Handle {Logger.hLowLevelLog = logWith config} 27 | 28 | logWith :: Config -> Logger.Level -> T.Text -> IO () 29 | logWith _ _ _ = error "Not implemented" 30 | -------------------------------------------------------------------------------- /echo-bot-template/stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # https://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # 15 | # The location of a snapshot can be provided as a file or url. Stack assumes 16 | # a snapshot provided as a file might change, whereas a url resource does not. 17 | # 18 | # resolver: ./custom-snapshot.yaml 19 | # resolver: https://example.com/snapshots/2018-01-01.yaml 20 | resolver: 21 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/28.yaml 22 | 23 | # User packages to be built. 24 | # Various formats can be used as shown in the example below. 25 | # 26 | # packages: 27 | # - some-directory 28 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 29 | # subdirs: 30 | # - auto-update 31 | # - wai 32 | packages: 33 | - . 34 | # Dependency packages to be pulled from upstream that are not in the resolver. 35 | # These entries can reference officially published versions as well as 36 | # forks / in-progress versions pinned to a git hash. For example: 37 | # 38 | # extra-deps: 39 | # - acme-missiles-0.3 40 | # - git: https://github.com/commercialhaskell/stack.git 41 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 42 | # 43 | # extra-deps: [] 44 | 45 | # Override default flag values for local packages and extra-deps 46 | # flags: {} 47 | 48 | # Extra package databases containing global packages 49 | # extra-package-dbs: [] 50 | 51 | # Control whether we use the GHC we find on the path 52 | # system-ghc: true 53 | # 54 | # Require a specific version of stack, using version ranges 55 | # require-stack-version: -any # Default 56 | # require-stack-version: ">=2.7" 57 | # 58 | # Override the architecture used by stack, especially useful on Windows 59 | # arch: i386 60 | # arch: x86_64 61 | # 62 | # Extra directories used by stack for building 63 | # extra-include-dirs: [/path/to/dir] 64 | # extra-lib-dirs: [/path/to/dir] 65 | # 66 | # Allow a newer minor version of GHC than the snapshot specifies 67 | # compiler-check: newer-minor 68 | -------------------------------------------------------------------------------- /echo-bot-template/stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | size: 590100 10 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/28.yaml 11 | sha256: 428ec8d5ce932190d3cbe266b9eb3c175cd81e984babf876b64019e2cbe4ea68 12 | original: 13 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/28.yaml 14 | -------------------------------------------------------------------------------- /echo-bot-template/test/EchoBotSpec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module EchoBotSpec 4 | ( spec, 5 | ) 6 | where 7 | 8 | import Control.Monad (when) 9 | import qualified Control.Monad.State as S 10 | import Control.Monad.Writer (WriterT, runWriterT, tell) 11 | import qualified Data.Text as T 12 | import EchoBot (Config (..), Event (..), Handle (..), Response (..), State, makeState, respond) 13 | import qualified Logger 14 | import Test.Hspec (Expectation, Spec, describe, it, shouldBe, shouldNotBe, shouldSatisfy) 15 | import Test.QuickCheck (NonNegative (..), property, (==>)) 16 | 17 | type Interp = WriterT [(Logger.Level, T.Text)] (S.StateT State IO) 18 | 19 | spec :: Spec 20 | spec = 21 | {- HLINT ignore spec "Reduce duplication" -} 22 | describe "respond" $ do 23 | it "should echo any non-command input back" $ 24 | property $ \str -> 25 | not (hasCommandPrefix str) ==> do 26 | let comment = T.pack str 27 | let config = stubConfig 28 | let h = handleWith config 29 | responses <- 30 | runBotWithConfig config $ respond h (MessageEvent comment) 31 | responses `shouldBe` [MessageResponse comment] 32 | 33 | it "should echo a simple message for any specified amount of times specified in the config" $ 34 | property $ \(NonNegative count) -> do 35 | let comment = "comment" 36 | let config = stubConfig {confRepetitionCount = count} 37 | let h = handleWith config 38 | responses <- runBotWithConfig config $ respond h (MessageEvent comment) 39 | responses `shouldBe` replicate count (MessageResponse comment) 40 | 41 | it "should output menu for /repeat command" $ do 42 | let config = stubConfig 43 | let h = handleWith config 44 | responses <- runBotWithConfig config $ respond h (MessageEvent "/repeat") 45 | responses `shouldSatisfy` any isMenuResponse 46 | 47 | it "should keep the repetition count set by the user" $ do 48 | let comment = "comment" 49 | let config = stubConfig {confRepetitionCount = 1} 50 | let newRepCount = 3 51 | let h = handleWith config 52 | responses <- runBotWithConfig config $ do 53 | [MenuResponse _ opts] <- respond h $ MessageEvent "/repeat" 54 | Just request <- pure $ lookup newRepCount opts 55 | _ <- respond h request 56 | respond h $ MessageEvent comment 57 | responses `shouldBe` replicate newRepCount (MessageResponse comment) 58 | 59 | it "should output the help text for /help command" $ do 60 | let helpText = "My help text" 61 | let config = stubConfig {confHelpReply = helpText} 62 | let h = handleWith config 63 | responses <- runBotWithConfig config $ respond h $ MessageEvent "/help" 64 | responses `shouldBe` [MessageResponse helpText] 65 | 66 | it "should output the predefined menu title for /repeat command" $ do 67 | let title = "My title" 68 | let config = stubConfig {confRepeatReply = title} 69 | let h = handleWith config 70 | responses <- runBotWithConfig config $ respond h $ MessageEvent "/repeat" 71 | responses `shouldSatisfy` any (isMenuResponseWithTitle title) 72 | 73 | it "should substitute {count} with repetition count in the menu title" $ do 74 | let config = 75 | stubConfig 76 | { confRepeatReply = "My count is {count}, {right}.", 77 | confRepetitionCount = 3 78 | } 79 | let h = handleWith config 80 | responses <- runBotWithConfig config $ respond h $ MessageEvent "/repeat" 81 | responses `shouldSatisfy` any (isMenuResponseWithTitle "My count is 3, {right}.") 82 | 83 | it "should not recognize an unknown command" $ do 84 | shouldNotRecognizeHelpCommand "/xhelp" 85 | shouldNotRecognizeHelpCommand "/ help" 86 | shouldNotRecognizeHelpCommand "/helpx" 87 | shouldNotRecognizeHelpCommand "/he lp" 88 | shouldNotRecognizeHelpCommand "x/help" 89 | shouldNotRecognizeHelpCommand "x /help" 90 | 91 | isMenuResponse :: Response T.Text -> Bool 92 | isMenuResponse (MenuResponse _ _) = True 93 | isMenuResponse _ = False 94 | 95 | isMenuResponseWithTitle :: T.Text -> Response T.Text -> Bool 96 | isMenuResponseWithTitle title (MenuResponse t _) = title == t 97 | isMenuResponseWithTitle _ _ = False 98 | 99 | shouldNotRecognizeHelpCommand :: T.Text -> Expectation 100 | shouldNotRecognizeHelpCommand = shouldRecognizeHelpCommandOrNot False 101 | 102 | shouldRecognizeHelpCommandOrNot :: Bool -> T.Text -> Expectation 103 | shouldRecognizeHelpCommandOrNot matchOrNot input = do 104 | let helpText = "Help text, not " <> input 105 | let config = stubConfig {confHelpReply = helpText} 106 | let h = handleWith config 107 | response <- runBotWithConfig config $ respond h $ MessageEvent input 108 | let expected = [MessageResponse helpText] 109 | if matchOrNot 110 | then response `shouldBe` expected 111 | else response `shouldNotBe` expected 112 | 113 | hasCommandPrefix :: String -> Bool 114 | hasCommandPrefix (' ' : xs) = hasCommandPrefix xs 115 | hasCommandPrefix ('/' : _) = True 116 | hasCommandPrefix _ = False 117 | 118 | runBotWithConfig :: Config -> Interp a -> IO a 119 | runBotWithConfig config = runBot (stateWith config) 120 | 121 | runBot :: State -> Interp a -> IO a 122 | runBot s0 m = do 123 | (a, logMessages) <- S.evalStateT (runWriterT m) s0 124 | logMessages `shouldSatisfy` null 125 | pure a 126 | 127 | handleWith :: Config -> Handle Interp T.Text 128 | handleWith config = 129 | Handle 130 | { hGetState = S.get, 131 | hModifyState' = S.modify', 132 | hLogHandle = logHandle, 133 | hConfig = config, 134 | hTextFromMessage = Just, 135 | hMessageFromText = id 136 | } 137 | 138 | logHandle :: Logger.Handle Interp 139 | logHandle = 140 | Logger.Handle 141 | { Logger.hLowLevelLog = 142 | \level text -> when (level >= Logger.Warning) $ tell [(level, text)] 143 | } 144 | 145 | stubConfig :: Config 146 | stubConfig = 147 | Config 148 | { confRepeatReply = T.empty, 149 | confHelpReply = T.empty, 150 | confRepetitionCount = 1 151 | } 152 | 153 | stateWith :: Config -> State 154 | stateWith = either (error . T.unpack) id . makeState 155 | -------------------------------------------------------------------------------- /echo-bot-template/test/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 2 | -------------------------------------------------------------------------------- /employment-faq.md: -------------------------------------------------------------------------------- 1 | # FAQ по программе обучения. Backend. 2 | 3 | 4 | 1. Так а сколько всё же стоит обучение? 5 | 6 | Нисколько. Абсолютно. Полностью. Всё обучение для вас совершенно бесплатно. 7 | 8 | 2. Оплачивается ли стажировка/есть ли стипендия и т.п.? 9 | 10 | Стажировка не оплачиваемая. И на данный момент компания не рассматривает 11 | внедрение стипендиальных выплат. 12 | 13 | 3. А сколько в среднем уходит на прохождение программы с нуля? 14 | 15 | Сильно зависит от самого обучающегося. Но, в среднем, выпускники затрачивали 16 | около 1 года на прохождение программы. 17 | 18 | 4. Какая зарплата после прохождения? 19 | 20 | Для Junior-1, только закончивших программу обучения, ставка 200р. (+50р для 21 | Haskell разработчиков) в час на руки (после вычета всех налогов). В команде 22 | есть внутренняя карта развития разработчика, по которой можно прокачивать 23 | уровень знаний и зарплаты соответственно. Выпускник программы проходит 24 | собеседование на Junior-1. Сама карта [вот 25 | здесь](https://github.com/fullstack-development/developers-roadmap). 26 | 27 | 5. Гарантируете ли вы трудоустройство? 28 | 29 | Гарантий нет, но мы всячески содействуем трудоустройству: проводим ревью, выдаём сертификат, проводим карьерные консультации, предлагаем вакансии партнёров и конечно же включаем в свой кадровый резерв. 30 | 31 | 6. А если у меня нет гражданства, но есть РВП/регистрация/разрешение на работу и 32 | пр. 33 | 34 | Найм без гражданства в РФ влечет для нас и для вас большое количество 35 | бюрократических препятствий, поэтому к сожалению, в этом случае мы не можем 36 | обеспечить трудоустройство. Но материалы программы полностью открыты и 37 | бесплатны для всех. 38 | 39 | 7. Для трудоустройства можно выбрать только одно из двух направлений? Или же 40 | надо закончить и фронтенд, и бэкенд? 41 | 42 | Достаточно закончить ОДНО из предложенных нами направлений. Либо фронт, либо 43 | бэк. 44 | 45 | 8. А могу я перейти на другое направление обучения? Например, бэка на фронт, 46 | если совсем не пойдет или выбирать можно один раз? 47 | 48 | Конечно! Вы можете перейти на frontend из backenda, или из backenda на 49 | frontend в любой момент. 50 | 51 | 9. А когда можно приступать к обучению, и где фиксируется то, что я начал 52 | обучаться и обучаюсь вообще? 53 | 54 | Вы можете приступать к обучению сразу после заполнения формы и получению 55 | доступа к материалам программы обучения. А по отчётам и активности в чате 56 | (исключая флуд) мы понимаем что вы проходите программу. 57 | 58 | 10. А где посмотреть задание? 59 | 60 | Дублируем ссылки здесь. Задания одинаковые, они просто ведут на разные 61 | ресурсы: 62 | 63 | [GitHub](README.md) 64 | 65 | Rizzoma: 66 | https://rizzoma.com/topic/c27faf1cfa188c1120f59af4c35e6099/0_b_9n8n_8jl2l/ 67 | 68 | Также, чтобы найти задания, вы можете воспользоваться: 69 | 1) Описанием телеграмм чата, там указаны ссылки, которые ведут на задания. Они также дублируют друг друга, просто ведут на разные ресурсы. 70 | 2) Закрепленными сообщениями в чате. 71 | 3) Ссылкой на задания в форме, которую вы заполняли. 72 | 73 | 11. Обязательны ли отчёты? 74 | 75 | Отчёты носят рекомендательный характер, но они помогают нам понять, не 76 | застрял ли человек где-то и не забросил ли программу вовсе; сам ли выполнял 77 | задания (бывали и случаи плагиата работ). Поэтому мы просим стажеров писать 78 | отчёты. Наш заочный способ познакомиться с будущими коллегами :) 79 | 80 | 12. А как фиксируется прохождение мной заданий обучения? 81 | 82 | Как таковой чёткой фиксации выполнения заданий у нас нет. Никто ни за кем 83 | пристально не следит и мы рассчитываем на вашу самостоятельность. :) 84 | 85 | Наша система построена на ежедневных отчётах и уже перед процессом код-ревью 86 | (последние шаги программы), мы будем смотреть на то, как у вас проходило 87 | обучение. 88 | 89 | 13. Куда отправлять отчёты? 90 | 91 | В телеграмм канал согласно вашему текущему заданию (Для фронта). Для бэкенда 92 | все отчёты отправляйте в один телеграмм канал ([канал по 93 | Haskell](https://t.me/learn_haskell_with_fsd)). 94 | 95 | 14. А если у меня возникнут вопросы по материалу, к кому мне можно будет 96 | обратиться? 97 | 98 | Можете смело писать интересующие вас вопросы в телеграмм чат и спрашивать у 99 | комьюнити. Также вы можете задать свой вопрос менеджеру программы обучения 100 | (администратор чата в телеграме) написав ему в личку или в телеграмм чате 101 | (поставив @ и ник менеджера.) 102 | 103 | Не забудьте проверить через поиск по чату свой вопрос. Вдруг его уже 104 | задавали до вас и вы можете легко найти ответ. 105 | 106 | 15. Хочу начать обучение, нужно ли мне что-то скачивать/устанавливать для этого? 107 | 108 | Нет, на начальном этапе, когда идёт знакомстве с теорией, вам ничего 109 | устанавливать не нужно. 110 | 111 | 16. А можете скинуть какой-нибудь проект, который бы включал выполнение всех 112 | задач, требований, которые нам надо выполнить? 113 | 114 | Мы не скрываем проекты других обучающихся, но и идеальный вариант тоже 115 | скинуть не можем. Слишком велик соблазн скопировать. И некоторые так и 116 | делали, но потом у них на реальных проектах были большие сложности с 117 | самостоятельным решением задач. 118 | -------------------------------------------------------------------------------- /exercises-task.md: -------------------------------------------------------------------------------- 1 | # Второе задание: задачки по языку 2 | 3 | 4 | https://www.schoolofhaskell.com/user/DanBurton/20-intermediate-exercises 5 | 6 | Рекомендуемые каты: 7 | 8 | 1. [Бесконечные структуры](https://www.codewars.com/kata/functional-streams) 9 | 10 | 2. [Создание своих инстансов для 5 базовых монад](https://www.codewars.com/kata/five-fundamental-monads) 11 | 12 | 3. [Монадный стек Maybe + List + State](https://www.codewars.com/kata/escape-the-mines-or-die) 13 | 14 | 4. [Представление структур данных из функций](https://www.codewars.com/kata/scott-encoding/train/haskell) 15 | 16 | Практически любой тип данных можно представить даже в языке без специальных 17 | синтаксических конструкций, если этот язык поддерживает функции высшего 18 | порядка и замыкания. Haskell как раз имеет специальные синтаксические 19 | конструкции для создания алгебраических типов данных, однако на замену им 20 | могут прийти простые функции. Как с их помощью сэмулировать пары, Maybe и 21 | даже списки как раз продемонстрирует эта ката. 22 | 23 | Также для закрепления концепции алгебраических типов данных рекомендую 24 | подглаву 2.1 книги SICP, там этот подход отлично описан. Саму книгу в целом 25 | тоже рекомендую в дальнейшем прочесть, хотя многие темы там уже по 26 | продвинутым темам. 27 | 28 | 5. [Изоморфизм (на самом деле довольно простая и интересная ката)](https://www.codewars.com/kata/isomorphism) 29 | 30 | Необязательные каты: 31 | 32 | 1. [Алгебраические изоморфизмы (после каты Изоморфизм)](https://www.codewars.com/kata/algebraic-isomorphism/haskell) 33 | 34 | 2. [Синглтоны](https://www.codewars.com/kata/54750ed320c64c64e20002e2) 35 | 36 | Несложная ката, решив которую можно познакомиться с зависимыми типами. 37 | Несмотря на то, что в Хаскеле на данный момент зависимых типов нет, 38 | последние можно сымитировать при помощи некоторых расширений `ghci` и 39 | типов-синглтонов - типов, имеющих только одно значение. 40 | 41 | 3. [Корутины](https://www.codewars.com/kata/547a77a6b84a1fb8bf000211) 42 | 43 | 4. [Lens](https://www.codewars.com/kata/54258ffb430ca2e4b5000239) 44 | 45 | 5. [Простой компилятор](https://www.codewars.com/kata/5265b0885fda8eac5900093b) 46 | 47 | Обязательно попробуйте найти и пройти еще от 3-х кат (1, 2 или 3 kyu) 48 | самостоятельно. 49 | 50 | -------------------------------------------------------------------------------- /haskell-platform-package-list.md: -------------------------------------------------------------------------------- 1 | # Список библиотек Haskell Platform 2 | 3 | 4 | Haskell Platform считается устаревшей и ее больше нельзя скачать, поэтому здесь 5 | мы приводим список ее пакетов. Все они достаточно важные и часто используемые, 6 | их можно использовать в тестовых проектах, если в задании не сказано обратное. 7 | 8 | - [Cabal](https://hackage.haskell.org/package/Cabal) 9 | - [GLURaw](https://hackage.haskell.org/package/GLURaw) 10 | - [GLUT](https://hackage.haskell.org/package/GLUT) 11 | - [HTTP](https://hackage.haskell.org/package/HTTP) 12 | - [HUnit](https://hackage.haskell.org/package/HUnit) 13 | - [OpenGL](https://hackage.haskell.org/package/OpenGL) 14 | - [OpenGLRaw](https://hackage.haskell.org/package/OpenGLRaw) 15 | - [QuickCheck](https://hackage.haskell.org/package/QuickCheck) 16 | - [StateVar](https://hackage.haskell.org/package/StateVar) 17 | - [Win32](https://hackage.haskell.org/package/Win32) 18 | - [alex](https://hackage.haskell.org/package/alex) 19 | - [array](https://hackage.haskell.org/package/array) 20 | - [async](https://hackage.haskell.org/package/async) 21 | - [attoparsec](https://hackage.haskell.org/package/attoparsec) 22 | - [base](https://hackage.haskell.org/package/base) 23 | - [bytestring](https://hackage.haskell.org/package/bytestring) 24 | - [cabal-install](https://hackage.haskell.org/package/cabal-install) 25 | - [call-stack](https://hackage.haskell.org/package/call-stack) 26 | - [case-insensitive](https://hackage.haskell.org/package/case-insensitive) 27 | - [cgi](https://hackage.haskell.org/package/cgi) 28 | - [containers](https://hackage.haskell.org/package/containers) 29 | - [deepseq](https://hackage.haskell.org/package/deepseq) 30 | - [directory](https://hackage.haskell.org/package/directory) 31 | - [exceptions](https://hackage.haskell.org/package/exceptions) 32 | - [extensible-exceptions](https://hackage.haskell.org/package/extensible-exceptions) 33 | - [fgl](https://hackage.haskell.org/package/fgl) 34 | - [filepath](https://hackage.haskell.org/package/filepath) 35 | - [fixed](https://hackage.haskell.org/package/fixed) 36 | - [haddock](https://hackage.haskell.org/package/haddock) 37 | - [half](https://hackage.haskell.org/package/half) 38 | - [happy](https://hackage.haskell.org/package/happy) 39 | - [hashable](https://hackage.haskell.org/package/hashable) 40 | - [haskell-src](https://hackage.haskell.org/package/haskell-src) 41 | - [haskell2010](https://hackage.haskell.org/package/haskell2010) 42 | - [haskell98](https://hackage.haskell.org/package/haskell98) 43 | - [hpc](https://hackage.haskell.org/package/hpc) 44 | - [hscolour](https://hackage.haskell.org/package/hscolour) 45 | - [html](https://hackage.haskell.org/package/html) 46 | - [integer-logarithms](https://hackage.haskell.org/package/integer-logarithms) 47 | - [mtl](https://hackage.haskell.org/package/mtl) 48 | - [multipart](https://hackage.haskell.org/package/multipart) 49 | - [network](https://hackage.haskell.org/package/network) 50 | - [network-uri](https://hackage.haskell.org/package/network-uri) 51 | - [old-locale](https://hackage.haskell.org/package/old-locale) 52 | - [old-time](https://hackage.haskell.org/package/old-time) 53 | - [parallel](https://hackage.haskell.org/package/parallel) 54 | - [parsec](https://hackage.haskell.org/package/parsec) 55 | - [pretty](https://hackage.haskell.org/package/pretty) 56 | - [primitive](https://hackage.haskell.org/package/primitive) 57 | - [process](https://hackage.haskell.org/package/process) 58 | - [random](https://hackage.haskell.org/package/random) 59 | - [regex-base](https://hackage.haskell.org/package/regex-base) 60 | - [regex-compat](https://hackage.haskell.org/package/regex-compat) 61 | - [regex-posix](https://hackage.haskell.org/package/regex-posix) 62 | - [scientific](https://hackage.haskell.org/package/scientific) 63 | - [split](https://hackage.haskell.org/package/split) 64 | - [stack](https://hackage.haskell.org/package/stack) 65 | - [stm](https://hackage.haskell.org/package/stm) 66 | - [syb](https://hackage.haskell.org/package/syb) 67 | - [template-haskell](https://hackage.haskell.org/package/template-haskell) 68 | - [text](https://hackage.haskell.org/package/text) 69 | - [tf-random](https://hackage.haskell.org/package/tf-random) 70 | - [time](https://hackage.haskell.org/package/time) 71 | - [transformers](https://hackage.haskell.org/package/transformers) 72 | - [transformers-compat](https://hackage.haskell.org/package/transformers-compat) 73 | - [unix](https://hackage.haskell.org/package/unix) 74 | - [unordered-containers](https://hackage.haskell.org/package/unordered-containers) 75 | - [vector](https://hackage.haskell.org/package/vector) 76 | - [xhtml](https://hackage.haskell.org/package/xhtml) 77 | - [zlib](https://hackage.haskell.org/package/zlib) 78 | -------------------------------------------------------------------------------- /how-to-learn.md: -------------------------------------------------------------------------------- 1 | # Важное требование: отчеты 2 | 3 | При прохождении этого и следующих заданий мы будем ждать от вас отчетов с текущим прогрессом и вашим личным отношением к изученному/сделанному. Отчеты лучше писать под конец дня, когда что-то делали (ну или в начале следующего), настолько часто, насколько вы будете работать. График у нас гибкий, так что можно спокойно делать любые перерывы в отправке отчетов и в любой момент возвращаться и писать их снова. 4 | 5 | Отчеты шлем в телеграм в [общую группу][students-chat]. 6 | **ВАЖНО!** После входа в группу нужно нажать на кнопку “Я не бот”, иначе вас выкинет из группы. 7 | 8 | Там же можно спрашивать совета, самим помогать и узнавать о новостях нашей программы обучения. Формат неформальный, пишите, как душа пожелает, чем больше личного фидбека, тем лучше! 9 | 10 | 11 | [students-chat]: https://t.me/learn_haskell_with_fsd 12 | -------------------------------------------------------------------------------- /interview.md: -------------------------------------------------------------------------------- 1 | # Интервью по теоретическим вопросам 2 | 3 | 4 | В [нашем репозитории с картой 5 | развития](https://github.com/fullstack-development/developers-roadmap/tree/master/backend/junior-1) 6 | у нас подготовлены около 80 вопросов по материалам из пройденных вами заданий. 7 | Там мы проверяем, что вы действительно изучили весь тот материал, помогаем вам 8 | побороть [неосознанную некомпетентность](http://sergeykorol.ru/blog/competence/) 9 | через получение обратной связи от нас. 10 | 11 | Сами вопросы вы можете посмотреть в [нашем репозитории на Github (папка 12 | junior-1)](https://github.com/fullstack-development/developers-roadmap/tree/master/backend/junior-1). 13 | 14 | Количество попыток на сдачу не ограничено, обычно уходит неделя-две, чтобы 15 | повторить изученное и ещё неделя на две-три попытки сдачи, после чего задание 16 | успешно завершается. В рамках этого шага вы действительно систематизируете все 17 | полученные знания, закрываются дыры в знаниях, которые почти наверняка у вас 18 | будут, обычно это один из самых эффективных этапов в обучении, когда каждый день 19 | на уже крепкую основу ваших знаний укладывается огромное количество нового и 20 | полезного материала. 21 | -------------------------------------------------------------------------------- /optional-katas.md: -------------------------------------------------------------------------------- 1 | # Дополнительные каты 2 | 3 | 4 | Публикуем список дополнительных кат от тех, кто проходил обучение до вас) Все 5 | это не обязательно к выполнению, это просто для вашего собственного развития. 6 | Список будет дополняться. :) 7 | 8 | 2. [Valid braces](https://www.codewars.com/kata/5277c8a221e209d3f6000b56) 9 | 10 | Нужно проверить все ли скобки в выражении закрываются. Можно решать разными 11 | способами. 12 | 13 | Она заставляет задуматься, что является аналогом стека в хаскеле, и 14 | тренирует навыки обращения со свертками. 15 | 16 | 3. [Product of consecutive Fib numbers](https://www.codewars.com/kata/5541f58a944b85ce6d00006a) 17 | 18 | Нужно найти является ли переданное число произведением соседних чисел в 19 | последовательности Фибоначчи. 20 | 21 | Задача тренирует навыки создания рекурсии, или обращения с функциями из 22 | `Data.List`. 23 | 24 | 4. [Reverse words](https://www.codewars.com/kata/reverse-words) 25 | 26 | Перевернуть все слова по отдельности. Пробелы оставить как есть. У этой 27 | каты есть красивое решение с использованием комбинатора из Data.Function. 28 | Будет полезно познакомиться с этим модулем. 29 | 30 | 5. [Snail](https://www.codewars.com/kata/snail/haskell) 31 | 32 | Интересная с задача с простым условием, для которой сразу понятен 33 | императивный алгоритм с вложенными циклами, но не сразу — функциональный. 34 | 35 | Помечена как 4-ый кью, но на самом деле достаточно первых глав LYAH и 36 | умения работать со списками. 37 | 38 | 6. [Equal sides of array](https://www.codewars.com/kata/equal-sides-of-an-array/train/haskell) 39 | 40 | В задаче нужно найти индекс элемента в списке, где сумма элементов списка 41 | слева будет равна сумме элементов списка справа от найденного элемента. 42 | Если же такого элемента нет, то вывести -1. 43 | 44 | В этой кате можно отработать работу со списками, поискать вспомогательные 45 | функции в модуле Data.List или же просто посмотреть интересные решение 46 | других людей. 47 | 48 | 7. [Go so far around to the right that you end up left](https://www.codewars.com/kata/5424e78460d77749f2000279) 49 | 50 | Необходимо реализовать левую свертку через правую. Для решения нужно 51 | ознакомиться с реализацией обеих сверток и хорошо понимать, как работает 52 | каждая из них. 53 | 54 | Для решения нужно хорошо разобраться в принципе работы обеих сверток, так 55 | как использовать reverse = читерить. 56 | 57 | 8. [Take a Ten Minute Walk](https://www.codewars.com/kata/54da539698b8a2ad76000228) 58 | 59 | Нужно проверить предложенный маршрут (движение по сторонам света) на два 60 | условия: длительность (10 минут при минуте на одно перемещение) и 61 | совпадение начальной и конечной точки (вернуться туда, откуда пришёл). 62 | 63 | Тренирует работу со списками или использование `Data.List`. 64 | 65 | 9. [Highest Rank Number in an Array](https://www.codewars.com/kata/5420fc9bb5b2c7fd57000004/haskell) 66 | 67 | Небольшая ката для практики бесточечного стиля и работы со стандартными 68 | модулями типа `Data.List`, `Data.Ord` и т.д. на ваш выбор. 69 | 70 | 10. [Duplicate Encoder](https://www.codewars.com/kata/54b42f9314d9229fd6000d9c) 71 | 72 | Простая ката, нацеленная на поиск дубликатов в массиве, что часто 73 | встречается в реальных задачах. 74 | 75 | 11. [Next bigger number with the same digits](https://www.codewars.com/kata/55983863da40caa2c900004e) 76 | 77 | Ката с очевидным брутфорс-решением. Попытайтесь найти наиболее оптимальный 78 | алгоритм, потому как если написать слишком просто, то все тесты пройти не 79 | успеет. 80 | 81 | 12. [Find The Parity Outlier](https://www.codewars.com/kata/5526fc09a1bbd946250002dc/haskell) 82 | 83 | У FindOutlier множестово разных решений функциями из Data.List, или можно 84 | в лоб, сверткой, тренирует паттерн матчинг и функции как объекты первого 85 | класса. 86 | 87 | 13. [Most frequently used words in a text](https://www.codewars.com/kata/51e056fe544cf36c410000fb/haskell) 88 | 89 | Достаточно простая ката для 4 кью. Тренирует обработку строки с 90 | использованием `Data.Char` и `Data.List`. 91 | 92 | 14. [Fibonacci, Tribonacci and friends](https://www.codewars.com/kata/556e0fccc392c527f20000c5/haskell) 93 | 94 | Логическое продолжение каты Tribonacci, достаточно интересное и 95 | неординарное решение, тренирует мозги. 96 | 97 | 15. [Sortable Shapes](https://www.codewars.com/kata/586669a8442e3fc307000048) 98 | 99 | Это простая ката 6 кью (30 минут на решение достаточно) позволит Вам 100 | потренироваться в создании пользовательского типа данных. Основной 101 | результат - это понимание реализации каких классов нужно предусмотреть для 102 | созданного типа, чтобы данные могли отправляться в функцию `sort`. 103 | 104 | 16. [Recurrence relations](https://www.codewars.com/kata/recurrence-relations/haskell) 105 | 106 | 17. [Simple Fun #74: Growing Plant](https://www.codewars.com/kata/58941fec8afa3618c9000184) 107 | 108 | 18. [Coloured Triangles](https://www.codewars.com/kata/coloured-triangles) 109 | 110 | 19. [Digital Root](https://www.codewars.com/kata/541c8630095125aba6000c00) 111 | 112 | 20. [Twice linear](https://www.codewars.com/kata/5672682212c8ecf83e000050/train/haskell) 113 | 114 | 21. [Playing with laziness](https://www.codewars.com/kata/5516b80d891547c9b50007fd/haskell) 115 | 116 | 22. [Break camelCase](https://www.codewars.com/kata/5208f99aee097e6552000148) 117 | 118 | 23. [CamelCase Method](https://www.codewars.com/kata/587731fda577b3d1b0001196) 119 | 120 | 24. [Speed Control](https://www.codewars.com/kata/speed-control/haskell) 121 | 122 | -------------------------------------------------------------------------------- /review-task.md: -------------------------------------------------------------------------------- 1 | # Пятое задание: ревью 2 | 3 | 4 | В рамках этого этапа наши сотрудники проведут строгое ревью github-репозиториев 5 | для предыдущих двух проектов: бота и веб-сервера. 6 | 7 | 1. Прежде чем приступить к этому этапу, отправьте свои репозитории нам для 8 | проверки через вот эту 9 | [форму](https://fullstack-dev.typeform.com/to/Q4FDE0ge) заявки на ревью 10 | проектов. Только получив эту форму, мы начнем проверку :) 11 | 12 | 2. После прохождения вами этапа код-ревью напишите менеджеру программы обучения 13 | в личные сообщения в телеграмме. 14 | 15 | Проверка кода в рамках ревью будет не только на предмет формальных требований 16 | ниже, но и на предмет продуманной и ясной архитектуры, читаемого и понятного 17 | кода, удобного интерфейса работы с вашими функциями. Код пройдет проверку только 18 | тогда, когда будет настолько хорош, чтобы мы сами не боялись брать его на 19 | поддержку :) Конечно, ровно эти же правила затем будут применяться и при всех 20 | ревью уже на реальных проектах после приема на работу. 21 | 22 | 23 | ## Требования к оформлению проекта 24 | 25 | 26 | 1. Весь проект скомпилирован с флагами `-Wall` и `-Werror` и нет ни одной ошибки и ни одного варнинга от компилятора. 27 | 2. Весь код проверен через `hlint` и не вызывает ни одного варнинга. 28 | 3. Весь код отформатирован при помощи форматтера `ormolu`. 29 | 4. Все импорты либо `qualified`, либо содержат явный import list. 30 | 5. Не используются нетотальные функции (Partial functions). 31 | 6. Вложенность условных операторов и операторов выбора не превышает 2 уровней. 32 | 7. Не используются длинные кортежи, когда удобнее использовать ADT. 33 | 8. Ошибки внимательно обрабатываются и не глушатся: 34 | * https://wiki.haskell.org/Handling_errors_in_Haskell 35 | * https://wiki.haskell.org/Error_reporting_strategies 36 | * https://www.fpcomplete.com/blog/2016/11/exceptions-best-practices-haskell 37 | 9. Не используются `error` и `undefined`. 38 | 10. Названия функций выбраны в соответствии с проблемой, которую эти функции решают. 39 | 11. Каждая функция решает только одну проблему. 40 | 12. Использованы паттерны проектирования: Service/Handle Pattern или Tagless Final/ReaderT. 41 | 13. Основной функционал покрыт чистыми юнит-тестами. E2E тесты в рамках программы обучения писать не требуется. 42 | 14. Проект запускается и работает с конфигом проверяющего (например, чтобы можно было проверить работу бота с другим токеном). 43 | 15. Все изменения в проект в процессе код-ревью вносятся через пулл-реквесты. В ПР указывается ссылка на ишью. 44 | 16. По максимуму исключено использование синонимов типов `type` в пользу `newtype`. 45 | 17. Не запрещается использовать [RecordWildCards](https://ruhaskell.org/posts/extensions/2021/04/06/RecordWildCards.html) и [OverloadedRecordDot](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_record_dot.html) в случаях, когда это улучшает читаемость кода. 46 | 47 | ## Рекомендуемые источники по оформлению кода 48 | 49 | 50 | * https://wiki.haskell.org/Programming_guidelines (правила про Haddock можно 51 | игнорировать) 52 | 53 | * https://wiki.haskell.org/Things_to_avoid 54 | 55 | * https://wiki.haskell.org/Avoiding_IO 56 | 57 | * HaskellerZ - Feb 2018 - Getting things done in Haskell and Zurich Friends of 58 | Haskell 59 | 60 | https://www.youtube.com/watch?v=-X1vrxQUETM — смотреть первую часть до 51 61 | минуты, там идет набор разных правил по разработке на Хаскеле. Паттерн Handle 62 | стоит рассмотреть, но необязательно применять. 63 | 64 | Слайды: 65 | https://github.com/jaspervdj/talks/blob/master/2018-haskellerz-getting-things-done/slides.md 66 | 67 | Обязательно изучить репозиторий, который там есть в ссылке, там то приложение, 68 | о котором рассказывал спикер: Fugacious. 69 | 70 | 71 | -------------------------------------------------------------------------------- /server-task.md: -------------------------------------------------------------------------------- 1 | # Четвертое задание: веб-сервер 2 | 3 | 4 | Нужно создать новостной веб-сервер с REST API, который принимает HTTP-запросы и 5 | отдает ответы в формате JSON. Можно представить, что это сервер для мобильного 6 | приложения. 7 | 8 | 9 | ## Функциональные требования 10 | 11 | ### Сущности 12 | 13 | 14 | 1. Пользователи 15 | 16 | * Имя 17 | * Логин 18 | * Пароль 19 | * Дата создания 20 | * Админ (булевая переменная) 21 | * Может ли создавать новости (булева переменная) 22 | 23 | 1. Категории (могут быть вложенными, то есть одна категория является 24 | подкатегорией для другой). 25 | 26 | Допустим, есть категория "Языки программирования", и у нее может быть 27 | подкатегория "Динамически Типизированные ЯП", и далее, соответственно, 28 | подкатегория подкатегории "Python" — и таких уровней вложенности может быть 29 | произвольное количество. 30 | 31 | 1. Новости 32 | 33 | * Краткое название 34 | * Дата создания 35 | * Один пользователь (создатель) 36 | * Одна категория 37 | * Текстовый контент 38 | * Любое количество фотографий, от нуля 39 | * Опубликована ли новость (булева переменная). 40 | 41 | 42 | ### API 43 | 44 | 45 | 1. Когда по API запрашиваем новости, в каждой новости должны быть вложены все 46 | сущности (автор, категория (рекурсивно) и т.д.), за исключением 47 | картинок: в ответе нужно вернуть их URI. 48 | 49 | 1. API новостей должно поддерживать фильтрацию. Фильтрация определяется 50 | параметрами в URI query (см. примеры ниже). Клиент может использовать сразу 51 | несколько фильтров по разным полям, и тогда они объединяются при помощи 52 | логического "И". Например, фильтр `/news?created_at=2011-01-01&author=Ivan` 53 | выдает новости, созданные 1 января 2011 г. пользователем по имени `Ivan`. 54 | Поддерживаются фильтры для полей: 55 | 56 | * День создания (созданные до даты, созданные с даты, созданные в указанный день) 57 | 58 | Примеры (можно выбрать другие названия): 59 | 60 | - `/news?created_at=2018-05-21` 61 | - `/news?created_until=2018-05-21` 62 | - `/news?created_since=2018-05-21` 63 | 64 | * Имя пользователя-автора 65 | * Категория по айди 66 | * Название (вхождение подстроки) 67 | * Контент (вхождение подстроки) 68 | 69 | 1. API новостей должно поддерживать поиск по строке, которая может быть найдена 70 | либо в текстовом контенте, либо в имени автора, либо в названии 71 | категории. 72 | 73 | 1. API новостей должно поддерживать сортировку отдельным параметром URI query. 74 | Клиент может использовать сортировку вместе с любым набором фильтров. 75 | Критерии сортировки: 76 | 77 | * Дата, 78 | * Автор (имя по алфавиту), 79 | * Категория (название по алфавиту), 80 | * Количество фотографий 81 | 82 | Пример: `/news?sort_by=category`. 83 | 84 | 1. Должны быть отдельные эндпойнты для сущностей: 85 | 86 | * Категории: 87 | - создание только для админов 88 | - получение списка для всех 89 | - редактирование только для админов (редактирование названия и смена 90 | родительской категории) 91 | * Новости: 92 | - создание для юзеров, имеющих право создания новостей 93 | - получение списка всем. Опубликованные новости видят все, а неопубликованные - 94 | только их автор. 95 | - редактирование только автору новости. Редактироваться могут и 96 | опубликованные, и неопубликованные новости. 97 | * Пользователи: 98 | - создание только для админов 99 | - получение списка всем 100 | * Картинки: 101 | - получение одной картинки. 102 | 103 | 1. Запросы на редактирование должны поддерживать редактирование любого 104 | количества полей сущности, т.е. одного поля, двух полей и т.д., вплоть до 105 | сразу всех. Например, при редактировании новости у нас должна быть 106 | возможность изменить только текст, не трогая остальные поля, или категорию 107 | вместе с заголовком, или любые другие поля в любых сочетаниях. 108 | 109 | 1. Что доступно только админам, при запросе не-админам возвращать 404, а не 403, 110 | чтобы не показать само наличие такого API. 111 | 112 | 1. Запросы по всем остальным URL должны возвращать 404. 113 | 114 | 1. Для аутентификации рекомендуем использовать [basic 115 | auth](https://en.wikipedia.org/wiki/Basic_access_authentication) как самый 116 | простой вариант. Аутентификацию не надо проверять в эндпойнтах, которые 117 | доступны всем. 118 | 119 | 1. Все API, возвращающие список данных, должны быть пагинированы, т.е. выдавать 120 | ограниченное количество элементов. Пагинация нужна для сокращения нагрузки на 121 | сервер: представьте, как она вырастет, если юзеры начнут массово запрашивать 122 | полный список из тысяч новостей, накопленных за годы работы сервера. 123 | 124 | - сервер никогда не выдает в ответе больше `N` элементов (т.е. новостей в 125 | запросе новостей, юзеров в запросе юзеров и т.п.). **Значение N должно быть 126 | задано в одном месте (в коде или в конфиге), а не скопировано в разные места кода, чтобы ее 127 | можно было изменить правкой единственной строчки**. 128 | 129 | - клиент может указать, сколько элементов он хочет получить и начиная с 130 | какого по счету элемента. Пример для новостей: `/news?offset=15&limit=20` - 131 | вернуть 20 новостей, пропустив 15 штук от начала списка (названия 132 | параметров даны только для примера). Клиент может не указывать любой 133 | параметр или даже оба: 134 | 135 | - если `limit` опущен, подразумевается `N` 136 | - а если `offset` опущен, подразумевается `0`. 137 | 138 | - если клиент запросил значение `limit` большее, чем `N`, он все равно 139 | получает не более `N` элементов. 140 | 141 | - пагинацию реализуйте ключевыми словами `LIMIT` и `OFFSET` в соответствующих 142 | SQL-запросах. 143 | 144 | 1. Сервер никогда не возвращает пароль пользователя. 145 | 146 | 1. В БД нужно хранить [хеши][hash-function] паролей, а не сами пароли. Можно (но 147 | совсем необязательно) сделать сложнее и правильнее: использовать хеширование 148 | c [солью][salt], например, при помощи случайно сгенерированной соли и 149 | [PBKDF2][PBKDF2] (см. модуль [`Crypto.KDF.PBKDF2`][pbkdf2-module] в 150 | библиотеке `cryptonite`). 151 | 152 | 1. В ответе на запрос картинки должен быть правильный заголовок `Content-Type`, 153 | например, `Content-Type: image/png`. Как его узнать? Можно его передавать в 154 | запросе, который создает картинку. 155 | 156 | 1. Для создания картинки достаточно передать ее содержимое, закодированное в 157 | [base64](https://ru.wikipedia.org/wiki/Base64), в запросе на создание или 158 | редактирование новости. Но при желании вы можете передать картинку в запросе 159 | типа 160 | [`multipart/form-data`](https://ru.wikipedia.org/wiki/Multipart/form-data). 161 | 162 | 163 | [hash-function]: https://ru.wikipedia.org/wiki/%D0%A5%D0%B5%D1%88-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F 164 | [salt]: https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D0%BB%D1%8C_(%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F) 165 | [PBKDF2]: https://ru.wikipedia.org/wiki/PBKDF2 166 | [pbkdf2-module]: https://hackage.haskell.org/package/cryptonite-0.30/docs/Crypto-KDF-PBKDF2.html 167 | 168 | ## Технические требования 169 | 170 | 171 | 1. В качестве веб-сервера под капотом использовать Warp 172 | 173 | Обязательно прочесть описание, как он работает: 174 | . 175 | 176 | В целом эту книгу (AOSA Book) очень рекомендуем, там огромное количество 177 | инсайтов по многим крутым опенсорсным проектам. 178 | 179 | Чтобы использовать Warp, необходимо понимать WAI, так что еще по нему 180 | прочитать — . 181 | 182 | 1. В качестве базы использовать PostgreSQL 183 | 184 | 1. Рекомендуемые библиотеки: 185 | 186 | 1. `cryptonite` для хеширования паролей 187 | 2. `warp`, `wai` и `servant` как основа для веб-сервера 188 | 3. библиотека для работы с PostgreSQL, любая, на выбор: 189 | - попроще: `postgresql-simple`, `hasql` 190 | - дольше изучать, но более безопасные в плане типов и чаще применяемые на 191 | практике: `beam`, `pesistent` + `esqueleto` 192 | 4. библиотека для чтения конфига: любая, например, `aeson`, `yaml`, 193 | `configurator`, `dhall` 194 | 5. вероятно, вам понадобится `text`, `bytestring`, `http-types`, 195 | `network-uri`, `containers`, `unordeded-containers`, `base64`, 196 | `base64-bytestring`, `vector`, `array` и другие библиотеки из 197 | [Haskell Platform](haskell-platform-package-list.md). В целом можно использовать 198 | почти все подобные простые библиотеки. Библиотека должна решать одну 199 | задачу, а не вести себя как фреймворк, т.е. не работать одновременно с 200 | базой и вебом. Если у вас есть сомнения, можно ли использовать библиотеку, 201 | спросите в чате. 202 | 203 | 1. Для работы с базой можно использовать любые библиотеки, главное — самим 204 | создавать и поддерживать миграции. В идеале, в готовом проекте по миграциям 205 | должна прослеживаться история изменения таблиц в процессе разработки - то 206 | есть дожна быть не только инициализирующая миграция (разве что вы сразу всё 207 | красиво спроектировали:)), но и миграции для добавления новых таблиц, 208 | изменения названий и типов полей и т.д. 209 | 210 | Миграции — код, который задает структуру базе (то есть создание таблиц, 211 | изменение, переименование, удаление полей и тд). 212 | 213 | Обязательно держите в голове, что после того, как проект выпускается в 214 | продакшен, база на проде заполняется данными, которые ни в коем случае нельзя 215 | потерять. Но требование менять структуру базы регулярно появляется за время 216 | развития проекта, и надо уметь развивать базу без потери данных. Для этого 217 | нужно как можно тщательней формализовать все изменения, которые вы проводите 218 | над базой от версии к версии. 219 | 220 | 1. Должна быть предоставлена отдельная команда, которая позволит применить все 221 | миграции к локальной базе данных и создать всю нужную структуру для бд. 222 | 223 | 1. В отдельной папке проекта сделать sh-скрипты со всеми возможными 224 | CURL-запросами, чтобы мы могли быстро склонировать проект и потестить его. 225 | Сделать по sh-файлу на каждый CURL-запрос. 226 | 227 | 1. Также следовать общим правилам, описанным в прошлом задании. 228 | 229 | 230 | ## Источники 231 | 232 | 233 | [Lessons learned while writing a Haskell 234 | application](https://gvolpe.github.io/blog/lessons-learned-while-writing-a-haskell-app/) 235 | — не всем рекомендациям отсюда мы бы советовали следовать, но почитать про 236 | релевантный опыт автора статьи будет однозначно полезно. 237 | -------------------------------------------------------------------------------- /theoretical-task.md: -------------------------------------------------------------------------------- 1 | # Первое задание: теория 2 | 3 | ## Установка ПО 4 | 5 | 6 | Рекомендуем установить [`Haskell Stack`][haskell-stack] и все упражнения 7 | запускать в нем. 8 | 9 | - установка для Windows: 10 | 11 | - имя вашего пользователя Windows должно содержать _только латинские буквы и 12 | цифры, никаких пробелов_. В противном случае создайте нового 13 | пользователя-администратора с подходящим именем, например, `admin`, и 14 | продолжайте установку из него. Переименование юзера не годится, нужно 15 | создать нового. 16 | 17 | - используйте 18 | [установщик](https://get.haskellstack.org/stable/windows-x86_64-installer.exe), 19 | минимальная версия системы - Windows 7 64-разрядная. 20 | 21 | - установка для Linux & Mac OS X: установите по [инструкции][haskell-stack] или 22 | поставьте нужный пакет (`stack` или `haskell-stack`) через пакетный менеджер 23 | вашей ОС (`apt`, `yum`, `brew` и т.п.). Версия на сайте более свежая. 24 | 25 | Больше ничего устанавливать не требуется, никаких Haskell Platform, 26 | ghcup, ghc, cabal. Stack самостоятельно скачает и установит компилятор и библиотеки. 27 | 28 | Как запустить: откройте консоль (командную строку) и наберите команду `stack repl` - 29 | запустится интерпретатор выражений Haskell. В первый раз придется подождать, пока 30 | загрузится и установится компилятор. Если установка выполнена правильно, 31 | отобразится подсказка ко вводу: 32 | 33 | ``` 34 | Prelude> _ 35 | ``` 36 | 37 | [haskell-stack]: https://docs.haskellstack.org/en/stable/README/ 38 | 39 | 40 | ## Базовая подготовка 41 | 42 | 43 | - Минимум, необходимый для написания самых простых проектов: 44 | 45 | - [Learn You a Haskell for Great Good!](http://learnyouahaskell.com/chapters). 46 | - [О Haskell по-человечески](https://www.ohaskell.guide/) - можно использовать в дополнение. 47 | Это простой учебник на русском, но он не закончен. 48 | 49 | - Параллельно выполняйте [каты из Codewars](basic-katas.md) 50 | 51 | - Обязательно [попробуйте](https://www.codewars.com/kata/search/haskell) найти и 52 | пройти от 3-х кат самостоятельно! Можете взглянуть на [список дополнительных 53 | кат](optional-katas.md) от тех, кто проходил обучение до вас. 54 | 55 | 56 | ## Более сложная теория 57 | 58 | 59 | После того, как изучены вышеназванные источники, можно одновременно выполнять 60 | дальнейшие задания и понемногу изучать следующие ресурсы. 61 | 62 | - Обязательно: системное описание почти всех самых популярных тайпклассов — 63 | [Typeclassopedia](https://wiki.haskell.org/Typeclassopedia). 64 | 65 | - Обязательно: учебник чуть более глубоко описывающий Haskell — 66 | . 67 | 68 | Позволяет понять многие теоретические моменты. Обязательны к прочтению 69 | главы 1-9, остальные по желанию. 70 | 71 | - Обязательно: видео-курс на степик от Дениса Москвина. 72 | 73 | https://stepik.org/course/75/ — первая часть курса, очень системный и 74 | развернутый материал по Haskell. Она обязательна к изучению. 75 | 76 | https://stepik.org/course/693/ - 2-я часть курса, она уже необязательна. 77 | Материал тут достаточно сложный, его рекомендуем изучать после того, как 78 | сделаны первые версии бота и сервера, чтобы закрепить полученные на 79 | практике знания и расширить/углубить их. 80 | 81 | - Очень рекомендуется: книга для начинающих с хорошим практическим уклоном 82 | Get Programming with Haskell (W. Kurt). 83 | 84 | Т.к. эта книга не доступна бесплатно, мы не включили её в обязательную 85 | часть, но очень рекомендуем. Здесь есть очень приближенные к реальным 86 | задачи, с помощью которых вы можете быстро и эффективно начать писать свои 87 | приложения, вроде бота. Книга максимально свежая и актуальная, хорошо 88 | написана. Практика показала, что изучающие её в рамках нашей программы 89 | потом её активно хвалили :) 90 | 91 | - По желанию: сделать упражнения из глав [Real World 92 | Haskell](http://book.realworldhaskell.org/), которые помогут освоиться с 93 | приближенными к реальным задачам. 94 | 95 | - По желанию: часто рекомендуемая многими новичками книга 96 | . 97 | 98 | - По желанию: [видео-курс от Николая 99 | Кудасова](https://www.youtube.com/watch?v=jNQVa5INdDk&list=PLov3NSwpY86cfkfXyVroSZkHemxoAdnrd&index=1). 100 | 101 | - По желанию: Write Yourself a Scheme in 48 Hours. 102 | 103 | Упражнение по написанию простого интерпретатора для языка Scheme (язык, с которого был слизан JavaScript). 104 | . 105 | 106 | Есть так же перевод на русский (там могут быть не все части или 107 | неактуальные данные): 108 | . 109 | 110 | Очень рекомендую пройти, на самом деле туториал довольно небольшой, но 111 | дает хороший пример написания средних по размеру програм, в то время как 112 | почти все остальные туториалы нацелены на примеры совсем тривиальных вещей 113 | в один-два файла. 114 | 115 | - [Выводы и ссылки на рецензии о современных книгах по Haskell](https://medium.com/@_bravit/книги-по-программированию-на-haskell-выводы-712c1f5b7749). 116 | 117 | - Статьи по различным темам, которые могут оказаться полезными для понимания 118 | того или иного материала 119 | 120 | - [Pitfalls in Haskell](http://web.archive.org/web/20150505013645/http://users.jyu.fi:80/~sapekiis/haskell-pitfalls/) 121 | - [Thinking Functionally with Haskell](https://pragprog.com/magazines/2012-09/thinking-functionally-with-haskell) 122 | - [Функторы, монады и аппликативы в картинках](https://habr.com/post/183150/) 123 | - [Три полезные монады (в картинках)](https://habr.com/post/184722/) 124 | - [«Страшные» абстракции Haskell без математики и без кода (почти). Часть I](https://habr.com/post/272115/) 125 | - [Моноиды, полугруппы и все-все-все](https://habr.com/company/jugru/blog/340178/). Оригинал [тут](http://blog.ploeh.dk/2017/10/04/from-design-patterns-to-category-theory/) 126 | -------------------------------------------------------------------------------- /why-haskell.md: -------------------------------------------------------------------------------- 1 | # Почему именно Haskell 2 | 3 | 4 | Предыстория. До этого мы довольно много разрабатывали на JavaScript (фронт с 5 | React и бек на Node.JS, +Typescript), Python (плюс Django), Elixir 6 | (присахаренный Erlang), у многих ребят из команды есть опыт с C/C++/C#. Однако в 7 | каждом языке есть свои недостатки, а у нас довольно высокие требования к тому, 8 | чтобы язык позволял быстро написать бизнес-логику, которую было бы легко понять 9 | коллегам и поддерживать. Именно требование легкой поддержки и надежности у нас 10 | всегда были в приоритете. 11 | 12 | Haskell — чистый функциональный язык с ленивой моделью вычислений и статической 13 | типизацией. Причины, почему мы выбрали именно этот язык: 14 | 15 | Haskell имеет, пожалуй, самую мощную систему типов среди промышленных языков на 16 | данный момент Haskell — единственный язык, на котором можно спокойно писать 17 | коммерческий софт, и который при этом позволяет четко отделять [чистые части 18 | программы](https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D1%82%D0%BE%D1%82%D0%B0_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8) 19 | от [частей с 20 | сайд-эффектами](https://en.wikipedia.org/wiki/Side_effect_(computer_science)). У 21 | Haskell уникальная экосистема, предлагающая не просто паттерны, а формальные 22 | абстракции (такие, как функторы и монады), которые на данный момент хорошо 23 | изучены в научной сфере, которые легко комбинировать, и которые позволяют 24 | описать огромное количество разного рода взаимосвязей между частями программы. 25 | 26 | Haskell, несмотря на свои академические зачатки, очень хорош в продакшене (и 27 | куча тому примеров дана ниже), он быстр, у него отличный механизм для 28 | concurrency, его поддерживают почти все популярные редакторы кода и для него 29 | есть огромное количество программ, помогающих нам: repl, линтеры, форматтеры и 30 | тд. Также в плюсы можно добавить колоссальную выразительность языка и его 31 | гибкость (например, операторы можно использовать, как в инфиксной, так и в 32 | префиксной записи, а также определять свои: 33 | https://wiki.haskell.org/Infix_operator). И один из главных бонусов, которые 34 | вытекают из всего вышеперечисленного, и который нас очень сильно приманил — 35 | легкость рефакторинга. Коммерческая разработка лишь на 10-15% состоит из 36 | написания абсолютно нового функционала, чаще же это либо добавление чего-то к 37 | уже существующим частям, либо рефакторинг после того, как опыт показал, где были 38 | ошибки в проектировании. На рефакторинг всегда нужно делать ставку, если 39 | планируется долгое развитие проекта (а нашему проекту уже почти два года, и он 40 | продолжает активно разрастаться), с Haskell же мы имеем кодовую базу, которая 41 | если скомпилировалась, то с 90-95% вероятностью она работает корректно. А значит 42 | во время рефакторинга компилятор сам будет за нас делать самую грязную работу — 43 | находить регресионные ошибки, замечать упущенные по невнимательности детали, 44 | помогать проектировать стройную систему (так как закрыть костылем, как можно 45 | было бы это сделать в Python, тут уже нельзя). 46 | 47 | Куча примеров использования [Haskell в продакшене](https://haskellcosm.com/). 48 | 49 | Два списка компаний, использующих Haskell в продакшене: 50 | и 51 | . 52 | 53 | Плюс еще более конкретные описания (это только быстро вспомнившиеся, еще много 54 | примеров можно нагуглить): 55 | - Facebook борется со спамом с помощью Хаскеля и напилили опенсорсную либу: 56 | https://code.facebook.com/posts/302060973291128/open-sourcing-haxl-a-library-for-haskell/ 57 | - Github написали Semantic на Haskell, [читать подробности о выборе 58 | языка](https://github.com/github/semantic/blob/7364e164947116406850ff6f298d80ba77105229/docs/why-haskell.md). 59 | - [Haskell в большом трейдинговом 60 | проекте](https://skillsmatter.com/skillscasts/9098-haskell-in-the-large-the-day-to-day-practice-of-using-haskell-to-write-large-systems). 61 | 62 | Конечно, есть и свои недостатки, которые мы отлично понимаем, и с которыми 63 | готовы были смириться: высокий порог входа (хотя, возможно, и не сильно выше тех 64 | же C++ или C#, если изучать их с нуля), отсутствие единого гайдлайна с best 65 | practices, отсутствие мощных IDE (хотя они и не особо нужны при таком выводе 66 | типов и таком компиляторе, который и так все подскажет, если правильно 67 | спросить). 68 | 69 | Еще недостатки, на которые нам пофиг: экосистема не изобилует монструозными 70 | фреймворками и разного рода "энтерпрайзными" библиотеками, хотя нам конкретно 71 | это совсем не мешает, так как все равно фреймворки блистают лишь в задачах 72 | уровня "сходить в базу, вытащить из базы, показать в html/json/xml", а у нас же 73 | много бизнес-логики, которую никакая библиотека за нас не напишет. 74 | 75 | Все это можно подытожить простым выводом — будь у нас простенький проект с адски 76 | малыми сроками, то Haskell был бы не лучшим выбором. Но наш проект долгоживущий 77 | и активно разрастающийся, а значит нам нужен язык, который бы позволял писать 78 | максимально прозрачный и понятный код, который можно легко поддерживать и 79 | рефакторить. Функциональный, чистый, статически типизированный Haskell как 80 | нельзя лучше подходит на эту роль, поэтому мы его и выбрали. 81 | 82 | Опционально: [выводы других опытных разработчиков после нескольких лет работы на Haskell](https://ru.hexlet.io/blog/posts/haskell-yazyk-pozvolyayuschiy-glubzhe-ponyat-programmirovanie-kak-on-ustroen-i-pochemu-ego-vybirayut-razrabotchiki?utm_source=telegram&utm_medium=social&utm_campaign=mdtruehaskell--funktsionalnyy-yazyk). 83 | --------------------------------------------------------------------------------