Простая и удобная среда разработки Haskell-кода в vim

Текстовый редактор vim/nvim очень хорош! Разумеется, для такого популярного редактора существует множество модулей, существенно помогающих в разработке программного кода на бесчисленных языках программирования. В данной короткой заметке я набросаю простой и быстрый вариант рабочего окружения (нужные модули для vim, их настройка, внешние программы) для комфортного программирования на Haskell в текстовом редакторе vim или nvim (все настройки ниже должны быть полностью совместимы как с классическим vim версии 8 и выше, так и с neovim).
ЧТАТ ПОЛН →

Функциональное реактивное программирование GTK+ и Cairo: программа-рисовалка

В данной заметке мы покажем, как функциональное реактивное программирование (FRP) позволяет нам полностью декларативно описать механику графической программы и чрезвычайно упростить её код. В качестве иллюстрации мы напишем незамысловатую программу-рисовалку, которая использует (обыкновенные) императивные биндинги к GTK и Cairo и пользовательский интерфейс которой создан в UI-редакторе Glade. Для FRP мы будем использовать замечательный пакет reactive-banana. Мостик между GTK и reactive-banana заполнит библиотечка reactive-banana-gi-gtk. Подготовка Зависимости нашей программы: text mtl reactive-banana reactive-banana-gi-gtk gi-gtk gi-gdk gi-gobject gi-cairo cairo Этапы построения программы Мы можем разделить процесс создания программы на следующие этапы:
ЧТАТ ПОЛН →

Процесс создания собственных декларативных GTK+ виджетов

Процесс создания собственных декларативных виджетов в библиотеке gi-gtk-declarative не так уж и сложен. Его можно разбить на следующие шаги: Измышление механики будущего виджета, понимание нижележащих GTK-виджетов, его составляющих Описание типа меняемых пользователем параметров (properties) виджета - для установления чистой функциональной зависимости от структуры пользователя. Описание типа всех событий (events), которые будет генерировать виджет (например, в ответ на срабатывания сигналов внутренних GTK-виджетов или достижения определённых состояний автомата логики) Описание внутреннего стэйта нашего виджета (например, поддерживание набора составляющих низкоуровневых GTK-виджетов для доступа к их состояниям) Создание пяти функций, необходимых для конструирования значения типа CustomWidget widget customData event (что в свою очередь даст нам искомый Widget event): data CustomWidget widget customData event = CustomWidget { customWidget :: Gtk.
ЧТАТ ПОЛН →

gi-gtk-declarative: Полностью декларативные GTK-программы на Haskell

Императивный GTK Программирование графических (GUI) приложений популярным императивным способом возможно и на Haskell: при таком подходе мы обычно работаем в монаде IO (или в параметризованном IO трансформере, например StateT) и логика программы частенько полностью идентична императивным языкам. Например, программа ниже использует библиотеку GTK3 в императивном стиле (низкоуровневые биндинги к GTK3 пакетом gi-gtk): import qualified GI.Gtk as Gtk import Data.GI.Base main :: IO () main = do Gtk.init Nothing win <- new Gtk.
ЧТАТ ПОЛН →

Монада RWS: Reader + Writer + State в одном интерфейсе

Фундаментальные трансформеры монад ReaderT (read-only контекст: например, настройки программы из конфигурационного файла), WriterT (write-only контекст: например, лог работы программы) и StateT (read/write контекст: например, счётчики и прочее внутреннее состояние логики программы) стандартных библиотек mtl и transformers очень употребительны. Их можно композиционно параметризовать любой “объемлющей” монадой для моделирования требуемого вычислительного контекста (например, WriterT w IO для программы, которая общается по сети и протоколирует (логирует) свою работу моноидом r). Трансформеры легко параметризуются друг-другом; мы работает с вложенными в “стопку” монадами путём лифтинга комбинаторов к нужным монадам с помощью функций семейства lift.
ЧТАТ ПОЛН →

Аппликативный комонадический (анти)контекст: ComonadApply

Типы Предположим, что мы программируем некую компьютерную симуляцию и решили выделить следующие типы в нашей программе: Тип, моделирующий внутреннее состояние сущности (допустим, юнита в компьютерной игрей): data Unit = Unit { _unitName :: String , _unitPosition :: L.V2 Double } Тип, моделирующий более абстрактную, внешнюю (например, административную) информацию, присущую вообще всем объектам симуляции: data Object s = forall d . Object { _objectId :: Int , _objectState :: s , _objectIntelDB :: [d] -- , .
ЧТАТ ПОЛН →

Свободная монада для функтора или изоляция сайд-эффектов в программе

На основе англоязычной статьи Gabriel Gonzalez. Предположим, что мы написали простейшую компьютерную программу, делающую некий ввод-вывод (т.е. имеющую сайд-эффекты): module Main where import Data.Char (toUpper) import System.Exit (exitSuccess) main = do x <- getLine putStrLn (map toUpper x) putStrLn "Finished" exitSuccess Всё бы ничего, но данный код смешивает “чистую” логику (не имеющую сторонних эффектов чистую трансформацию строки в верхний регистр: строка -> СТРОКА) и “грязный” ввод-вывод с терминала (имеющий сторонние эффекты вроде ожидания клавиатурного ввода, разрыв терминальной линии и т.
ЧТАТ ПОЛН →

Эффективный, оптимальный, типизированный diff между данными произвольного типа

Глубокие теоретико-математические основания и лаконичный синтаксис Haskell делают этот язык одним из наиболее совершенных средств осмысления и составления компьютерных программ за всю историю кибернетики. В данной короткой заметке мы воспользуемся пакетами для Обобщённого Программирования (Generic Programming) и покажем, как можно получать разницу (diff) между данными некоторого типа, а также накладывать эту разницу (patch) для получения эндоморфизма типа. Задача Положим, мы имеем следующий тип автомобилей: data Color = White | Black data Car = Car { carMaker :: String , carColor :: Color , carPrice :: Int } Наша задача состоит в получении разницы (диффа, дельты) между двумя автомобилями, белым “Volvo” и чёрным “BMW”:
ЧТАТ ПОЛН →

Haskell: Применение паттерна MVC для программ с GTK+

Введение В данной заметке мы исследуем один из способов структурирования на Haskell графических программ (GTK3) архитектурным паттерном Model-View-Controller. Для этих целей мы будем применять пакет mvc и Haskell-биндинги к GTK3 gi-gtk. Поскольку поток выполнения любой GTK+ программы обычно (после инициализации, настройки коллбэков, и т.д.) рано или поздно входит в бесконечный библиотечный цикл gtk_main(), нам придётся что-то придумать для одновременного применения графической подсистемы вместе с пакетом mvc. Любой GUI является лишь частью некоторой программы, но не наоборот, поэтому мы считаем запуск и выполнение MVC более общим и первичным, а GTK+ – вторичным и потому запускаемым и выполняемым “внутри” нашей архитектуры MVC.
ЧТАТ ПОЛН →

mvc: архитектурный паттерн Model-View-Controller в Haskell

Для Haskell существует замечательная библиотека mvc, реализующая архитектуру Model-View-Controller для проектирования многопоточных программ. Библиотека построена на базе поточных процессоров pipes, async и других известных абстракций. ![/img/500px-MVC-Process.svg.png) В данной заметке мы познакомимся с mvc и спроектируем простенькую графическую программу на SDL. Компоненты mvc Библиотека mvc преднамеренно форсирует нас расщеплять дизайн программы на три фундаментальных компонента: Тип Controller, снабжающий ядро программы некоторыми входными данными (стимулами) из внешнего мира. Моделируется “нечистыми” вычислениями, т.
ЧТАТ ПОЛН →

Почему конструкция if-then-else малополезна в языках программирования

Введение: if-then-else В большинстве языков программирования имеется условная конструкция IF ... THEN ... ELSE .... Она знакома любому школьнику и представляет собой развилку на пути из некоторого абстрактного типа a в тип b. Решение о том, взять первый путь или второй, принимается с помощью предиката, то есть путя из a в булев тип (истина/ложь). Вместе с обоими вариантами b (т.н. ветками условия), предикат также идёт параметром к конструкции IF ... THEN .
ЧТАТ ПОЛН →

Простая интуиция линз, призм, изоморфизмов (lens)

Введение В данной заметке я вкратце и упрощённо поделюсь своим пониманием общей картины (“Big Picture”) библиотеки lens и вытекающей из него интуиции о линзах. Интуиция основных операций с линзовыми объектами Итак, имея линзовый объект (о них - позже), мы можем осуществлять с его помощью четыре фундаментальных действия: Смотреть на фокус линзы, то есть фокусироваться внутрь структуры данных: view Модифицировать данные в фокусе линзы, оставляя остальную структуру без изменений: over Полностью замещать данные в фокусе линзы (может быть, на другой тип), оставляя остальную структуру без изменений: set Комбинировать линзовые объекты друг с другом, производя новый линзовый объект: (.
ЧТАТ ПОЛН →

Траверсабельные и битраверсабельные структуры: Data.Traversable

Интуиция тайпкласса Traversable Тайпкласс Data.Traversable базируется на функториальных, фолдабельных абстракциях и представляет собой такие (часто контейнерные) структуры данных, которые можно последовательно погрузить в некоторый аппликативный контекст с сохранением формы структуры. Проще говоря, траверсабельные контейнеры позволяют замапить все элементы структуры в вычислительный контекст, произвести эти вычисления слева-направо и собрать результат в контексте внутри точно такого-же контейнера. Если Data.Foldable подразумевает полное разрушение формы контейнера в процессе последовательной свёртки, т. е. создания дайджеста, то Data.
ЧТАТ ПОЛН →

Два пути к Data.Foldable: структуры, допускающие свёртку

Интуиция Класс Data.Foldable представляет собой абстракцию над такими (часто контейнерными) типами данных, которые допускают свёртку (fold). Для этих типов осмыслена и определена операция коллапса, то есть разрушения и схлопывания всей структуры в одно единственное итоговое значение (дайджест, резюме). > foldr (+) 100 $ Node 2 [Node 1 [], Node 3 []] 106 > let f = foldr (.) id [(+1), (*2), (+10)] > :t f f :: Num b => b -> b > f 1 23 Если некоторый тип t реализует интерфейс тайпкласса Data.
ЧТАТ ПОЛН →

Функторы как адаптеры между категориями

Функторы в теории категорий Как известно, функтор в теории категорий определён как морфизм между двумя категориями (маппинг всех объектов одной категории в объекты другой, маппинг всех морфизмов одной категории в морфизмы другой), который сохраняет нейтральные элементы (Identity) и композицию в соответствующих категориях. Функтор, таким образом, – более общий случай понятия гомоморфизма (групп, моноидов и т.п.), то есть отображения, сохраняющего (алгебраическую) структуру. Функторы в философии В отличии от Множества, которое по своей природе лишено всякой структуры, любая Категория – это сама структура в чистом виде (и ничего кроме структуры).
ЧТАТ ПОЛН →

intero-mode: быстрая настройка Emacs для комфортной работы с Haskell-кодом

Не секрет, что хакеры предпочитают использовать текстовые редакторы emacs или vim. Индивидуальные настройки редактора, скрипты и т.п. при этом могут жить десятилетиями, поскольку являются делом профессиональной привычки. Для редактирования Haskell-кода в emacs традиционно были пакеты haskell-mode и ghc-mod, однако не так давно появился замечательный проект intero-mode, предлагающий хороший функционал “из коробки” и настраивать среду для комфортной работы стало быстрее и проще. Установка intero-mode После добавления MELPA-репозитория пакетов к emacs, установить intero-mode можно, найдя свежую версию “intero” в M-x package-list-packages.
ЧТАТ ПОЛН →

OpenGL в Haskell: загрузка текстуры в GPU, UV-координаты

На базе нашей программки вращения кубика (из прошлых статей) разберёмся с простейшей загрузкой текстур в аппаратуру и текстурированием четырёхугольника. Забудем пока про куб и цвета и вернёмся к квадрату со стороной 1 и 2D-координатам. UV-координаты В компьютерной графике для “закрепления” текстуры на объекте используют так называемые UV-координаты. Размеры текстуры нормализуются до [0..1] по оси X и Y, и каждая вершина треугольников, из которых состоит объект, мапится к пикселю картинки как показано выше.
ЧТАТ ПОЛН →

Объекты OpenGL в Haskell: VAO и VBO

В прошлый раз мы говорили об инициализации системы OpenGL на Haskell и создали простенький модульный шаблон программы (мы используем биндинги GLUT). В этот раз мы немного дополним наш кубик, раскрасив вершины треугольников, из которых он собран, случайными цветами. Немного о VAO и VBO Спецификация OpenGL определяет два объекта, которые мы уже использовали: VAO (Vertex Array Object): инкапсулирует состояние системы, достаточное для запуска конвейера (то есть для поставки вершинных данных) и полного рендеринга объекта.
ЧТАТ ПОЛН →

Инициализация программируемого графического конвейера OpenGL

Для Haskell есть замечательные биндинги к OpenGL, а также биндинги к популярным “клиентским” вспомогательным библиотекам вроде GLUT, GLFW, SDL и прочим. Мощь и изящество языка Haskell вместе с разнообразием фундаментальных подходов к композиции программ (монады, Arrow, FRP и т.д.) делают его чуть-ли не идеальным инструментом в написании корректных графических программ вроде симуляций или игр. Задачи Попробуем разобраться с минимальными действиями, необходимыми для создания простенькой программки, использующей систему OpenGL современным образом: а именно, выполняя вычисления на высокопроизводительном GPU и управляя программируемым графическим конвейером с помощью вершинных, фрагментных и других шейдеров.
ЧТАТ ПОЛН →

Монада Identity или почему Haskell - самый лучший императивный язык программирования

Введение Предположим, что у нас есть целая библиотека архи-полезных функций: timesTwo :: Int -> Int timesTwo = (* 2) timesTen :: Int -> Int timesTen = (* 5) . timesTwo isBig :: Int -> Bool isBig = (>= 100) countChars :: String -> Int countChars = length Мы составили (популярную) компьютерную программу на базе этой библиотеки (точка . – операция композиции функций): program = isBig . timesTen . countChars Программа принимает на вход строку, а выдаёт значение “истина/ложь”.
ЧТАТ ПОЛН →

Контравариантные функторы, бифункторы, профункторы в Haskell

Введение Предположим, что у нас есть следующая структура данных, моделирующая некоторое абстрактное преобразование сущности i в сущность o: newtype ConversionFromTo i o = ConverterFromTo { convertFromTo :: i -> o } Типы i и o мыслятся входом и выходом соответственно: функция convertFromTo просто запускает описанное в ConversionFromTo i o преобразование над аргументом типа i. Обратим внимание, что последовательность аргументов важна, и первым параметром к конструктору значения типа ConversionFromTo i o идёт тип входного сигнала.
ЧТАТ ПОЛН →

На пути к пониманию стрелок (Arrow): функции с памятью

Стрелки (тайпклассы и комбинаторы из Control.Arrow) являются замечательным обобщением понятия функции. Стрелка y a b представляет собой некоторый процесс, принимающий значения типа a и выдающий значения типа b. Помимо прочих мест, довольно подробная статья про стрелки есть на англоязычном Викиучебнике. Ниже я попробую пересказать её часть своими словами и подробно объяснить некоторые моменты (отсутствующие в Вики), которые сам понял не сразу. Стрелочных комбинаторов >>>, first, &&& и прочих я коснусь в отдельной статье.
ЧТАТ ПОЛН →

Простое объяснение идеи монад за 30 секунд

Композиция Нестрого заметим, что любая компьютерная программа представляет собой некий абстрактный вычислительный блок. Вычислительный блок просто соединяет вход программы с её выходом (результатом). Довольно сложно проектировать и поддерживать большие программы, состоящие из одного громадного вычислительного блока. Искусство програмирования, поэтому, есть искусство разбиения большого вычислительного блока на несколько малых блоков. Каждый блок при этом призван решить свою собственную подпроблему (подзадачу). Так достигается модульность решения задачи. Раньше у нас был один единственный вычислительный блок, а теперь – несколько маленьких блоков.
ЧТАТ ПОЛН →

Линзы в Haskell: эквивалентности (Equality)

В прошлой записи мы в общих чертах ознакомились с замечательным пакетом lens, реализующим технику функционального программирования, известную как линзы (обобщённые геттеры и сеттеры). В пакете определена целая иерархия “разновидностей” линз, откуда и разные названия для их типов: Equality, Iso, Lens, Prism, Getter, Traversal и т.д. Комбинаторы с верхних уровней следующей диаграммы можно применять к типам на нижележащих уровнях: Чтобы лучше овладеть пакетом lens, на протяжении нескольких записей я попробую вкратце сформулировать особенности и цели использования, а также привести содержательные примеры для каждого из типов с диаграммы выше.
ЧТАТ ПОЛН →

Линзы и призмы в Haskell

Введение Линзы – это функциональные композиционабельные проекции типов, сильно облегчающие доступ к многоуровневым, вложенным структурам данных. Под “проекцией типа” я здесь имею в виду некоторый тип T, конструируемый N-арным конструктором значений makeT, вместе с некоторой функцией proj_N, деконструирующей значение T в его N-ый элемент: data T = makeT A B C D proj_A :: T -> A proj_B :: T -> B proj_C :: T -> C proj_D :: T -> D т.
ЧТАТ ПОЛН →

Реализация Random для перечисляемого и ограниченного типа

Предположим, что у нас есть следующий тип, описывающий вид дерева (дуб, сосна, берёза, …): data TreeType = Pine | Oak | Birch | Maple Мы бы хотели уметь строить дерево случайного вида, т.е. использовать интерфейс тайпкласса Random и комбинаторы из System.Random: Тайпкласс Random Тайпкласс случайных величин Random в Haskell имеет следующий интерфейс: randomR :: (RandomGen g, Random a) => (a, a) -> g -> (a, g) random :: (RandomGen g, Random a) => g -> (a, g) Обе функции принимают на вход детерминированный генератор псевдослучайных чисел, а функция randomR также - мнимальное и максимальное значение случайной величины, в интервале между которыми и будет сделан случайный выбор.
ЧТАТ ПОЛН →

Простейший (ан)парсинг типов Haskell в JSON c Data.Aeson

Введение Сериализация и десериализация типов Haskell в популярный формат JSON проста как пробка с пакетом Aeson (Data.Aeson). Положим, что T - тип данных, который мы хотим сохранять в и считывать из формата JSON. Для этого нам нужны стрелки (изоморфизм) между типами T и ByteString, строками, содержащими все корректные JSON-репрезентации значений нашего T. Пакет Data.Aeson содержит высокоэффективные функции работы с форматом JSON. Среди прочих имеем функции кодирования и декодирования между JSON и типами Haskell:
ЧТАТ ПОЛН →

Частичные функции и композиция Клейсли

Частичными называют такие функции, которые не определены для некоторых значений своего аргумента. Например, отображение “рост_в_сантиметрах” из множества людей в множество натуральных чисел определено на всей области определения (каждому человеку поставлено в соответствие число), тогда как отображение “зарплата” определено не для каждого человека (кто-то не работает). Компьютерная программа – это композиция (то есть функция от функции от … от функции аргумента) некоторых “функциональных блоков”. Например, программа-просмотрщик картинок вроде geeqie является удачной композицией функций из графической библиотеки GTK, библиотек декодирования изображений JPEG, PNG, стандартных библиотек языка Си и т.
ЧТАТ ПОЛН →

Десериализация в Haskell: парсинг карт TacOps

Введение Предположим, мы читаем двоичную последовательность из файла (или слушаем на проводе) и хотим преобразовать её в некоторую осмысленную структуру. Для этого нам нужен парсер – вычислительный процесс, который, грубо говоря, разобъёт бездушную последовательность единиц и нулей на несущие смысл компоненты и сконструирует из них нашу структуру. И наоборот, мы хотим сохранить известную структуру данных на диск (например, для последующего архивирования) или передать по сети другому компьютеру. Нам нужно провести обратную операцию – анпарсинг, т.
ЧТАТ ПОЛН →

Монада инвестиции: простой язык описания и симуляции инвестиций

Можно просто посмотреть пример. Краткое описание Накатал забавную Haskell-библиотечку для моделирования финансового поведения: инвестиций, личных трат, выплат, торговли на бирже, и прочих алгоритмов продолжительных и условных накоплений и передвижений средств. Смысл простой: тип Investment является синонимом специализации трансформера монад StateT на монаду IO: -- | The investment monad type type Investment a r = StateT (InvestmentState a) IO r При этом тип InvestmentState, описывающий текущее состояние инвестиции (или чьего-то кошелька и долгов…), определён так:
ЧТАТ ПОЛН →

Моделирование валюты в Haskell

Любопытная задачка – смоделировать валюту, т.е. конвертабельные друг в друга денежные средства. Чтобы уметь делать утверждения о валюте, хранить и конвертировать её, нам нужно определить тип для представления денег в программе. В первом приближении можно рассуждать так: data Symbol = USD | RUB | BTC | LTC | ETH | DASH | ZEC type Money = (Symbol, Double) Но при таком подходе типы выражений (USD, 100) (купюра в сто баксов) и (RUB, 1) будут одинаковы, а значит любые комбинаторы над типом Money каким-то образом должны будут учитывать разные денежные знаки (разные конструкторы типа Symbol).
ЧТАТ ПОЛН →

Haskell: F-алгебры, катаморфизмы и foldr

Мой перевод статьи Bartosz Milewski “Understanding F-Algebras” на schoolofhaskell. Что такое алгебра? Говоря наивно, алебра – это такая штука, которая даёт нам возможность производить вычисления с числами и символами. Абстрактная алгебра представляет символы как элементы векторного пространства: они могут умножаться на скаляры и складываться друг с другом. Но что отделяет алгебры от линейных пространств, так это наличие векторного умножения: билинейного произведения векторов, рузультатом которого является другой вектор (против скалярного произведения, которое даёт скаляр).
ЧТАТ ПОЛН →

Прогулки с тайпклассами Haskell: аппликативные функторы

Как мы уже выяснили, функторы позволяют втягивать “обычную” функцию в некоторый вычислительный контекст, превращая её в функцию над контекстами: > :t (* 3) (* 3) :: Num a => a -> a > :t fmap (* 3) fmap (* 3) :: (Functor f, Num b) => f b -> f b В этом примере (* 3) – это функция “утроения” (функция от одного числа, отсюда и a -> a). Функция fmap (* 3) тогда есть “функция утроения содержимого некоторого контейнера (функтора)”.
ЧТАТ ПОЛН →

Тайпклассы Haskell: функториальные законы

В продолжении разговора о функторах стандартной библиотеки Haskell, подумаем над их законами. Функториальные законы Все реализации Functor должны подчиняться следующим двум законам: fmap id = id fmap (g . h) = (fmap g) . (fmap h) Первый закон очень ёмок, несмотря на кажущуюся простоту. Он говорит нам о том, что втягивание функции тождества (id a = a) в вычислительный контекст не может изменять контекст. Второй закон говорит нам о дистрибутивности fmap относительно композиции: применение одной функции внутри контекста вслед за другой – это то же самое, что применение композиции этих функций внутри контекста.
ЧТАТ ПОЛН →

Прогулки с тайпклассами Haskell: функторы

Приглашаю всех любознательных новичков, как и я, на прогулку по классам^[В данном случае термины класс и экземляр, разумеется, не имеют ничего общего с созвучными терминами из объектно-ориентированного программирования.] типов стандартной библиотеки языка Haskell. На wiki сообщества хакеров Haskell есть страница Typeclassopedia, в которой подробно рассказывается про самые фундаментальные классы языка, приводятся интересные примеры интуиции (метафор, которыми можно понимать для себя тот или иной класс), а также даются полезные упражнения. Я бы хотел пройтись по каждому из описанных в Typeclassopedia классов и понять их все; потратить несколько минут благодарных размышлений над красивейшими абстракциями и глубокими математическими идеями, стоящими за каждым из них, а также выполнить все упражнения из параграфов Exercises.
ЧТАТ ПОЛН →