Перейти к основному содержимому

Первая программа на Haskell

Разработчику Архитектору Инженеру

Play ITЗагрузка интерактивного демо…


Первая программа на Haskell

Где применяют Haskell

Haskell — чисто функциональный язык — неизменяемые данные, ленивые вычисления, строгая типизация с выводом типов. Учит мыслить выражениями, а не последовательностью присваиваний; применяют в финтехе, компиляторах, исследованиях.

Старт — GHC + Cabal/stack, REPL ghci, main = putStrLn "Hello".


Как подойти к первой программе без перегруза

Чтобы старт не выглядел "слишком академично", держите простой план:

  1. Установить инструменты и проверить ghc --version.
  2. Запустить самый короткий Hello, World!.
  3. Сразу попробовать интерактивный ввод через getLine.
  4. Только потом идти в тему типов, монад и архитектуры.

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


Установка необходимого программного обеспечения на Windows

Для написания и запуска программ на Haskell требуется компилятор и набор инструментов. Основной компилятор — GHC (Glasgow Haskell Compiler) — стандарты Haskell 2010, оптимизации, GHCi, поддержка параллелизма. Рекомендуемый способ установки на всех платформах — GHCup (GHC, Cabal, HLS). Дистрибутив Haskell Platform (GHC + Cabal + библиотеки) с 2022 года считается устаревшим; альтернативы — Chocolatey или Scoop на Windows, но GHCup остаётся единой точкой входа.


Установка GHCup

GHCup — это официальный менеджер версий для Haskell-инструментов. Он позволяет устанавливать GHC, Cabal (систему сборки) и другие компоненты одной командой.

  1. Откройте PowerShell от имени администратора.
  2. Установка GHCup:
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://get-ghcup.haskell.org'))

Разбор:

  • Set-ExecutionPolicy ... -Scope Process временно разрешает выполнение скрипта только в текущей сессии PowerShell.
  • Блок с SecurityProtocol -bor 3072 включает TLS 1.2, чтобы загрузка шла по современному защищённому протоколу.
  • New-Object System.Net.WebClient создаёт HTTP-клиент для получения установочного скрипта.
  • DownloadString(...) скачивает текст скрипта, а iex (Invoke-Expression) выполняет его сразу в текущем процессе.
  • Это быстрый bootstrap GHCup, после которого устанавливаются GHC/Cabal/HLS.
  1. Следуйте инструкциям установщика. По умолчанию GHCup предложит установить GHC, Cabal и HLS (Haskell Language Server).
  2. После завершения перезапустите терминал или PowerShell, чтобы изменения в PATH вступили в силу.

Проверка:

ghc --version

Если команда выводит версию компилятора, например The Glorious Glasgow Haskell Compilation System, version 9.6.3, значит, установка прошла успешно.

Разбор:

  • ghc --version проверяет, что компилятор доступен в PATH и корректно запускается.
  • Команда не компилирует код, а только выводит информацию о текущей установленной версии.
  • Этот шаг подтверждает, что установка завершилась и терминал видит бинарник ghc.

Выбор редактора или IDE

Haskell не требует специальной IDE, но наличие поддержки языка значительно ускоряет разработку. Рекомендуемые варианты:

  • Visual Studio Code с расширением Haskell. Это расширение автоматически использует Haskell Language Server (HLS), предоставляет подсветку синтаксиса, навигацию по коду, проверку типов в реальном времени и автодополнение.
  • Vim или Neovim с плагинами, такими как haskell-vim и hls.
  • Emacs с пакетом haskell-mode.
  • JetBrains IntelliJ IDEA с плагином Haskell.

Для новичков оптимальный выбор — Visual Studio Code. Установка расширения занимает несколько секунд, а интерфейс интуитивно понятен. После установки расширения и открытия файла с расширением .hs HLS автоматически запускается в фоне и начинает анализировать код.


Создание первой программы

Программа "Hello, World!" на Haskell выглядит следующим образом:

main :: IO ()
main = putStrLn "Hello, World!"

Разбор:

  • main :: IO () задаёт точку входа и показывает, что функция выполняет действие ввода-вывода.
  • IO фиксирует наличие побочного эффекта, а () означает отсутствие полезного возвращаемого значения.
  • putStrLn печатает строку и добавляет перевод строки в конце.
  • Всё выражение справа — описание действия, которое runtime выполняет при старте программы.

Этот код состоит из двух строк. Первая строка — это сигнатура типа функции main. Она указывает, что main возвращает значение типа IO (), то есть выполняет операцию ввода-вывода и не возвращает полезных данных. Вторая строка — тело функции. Она вызывает функцию putStrLn, которая выводит строку на экран и добавляет символ перевода строки.

Важно понимать, что в Haskell ввод-вывод — это особый вид вычислений, обернутых в тип IO. Это связано с тем, что Haskell стремится сохранять чистоту функций — любые побочные эффекты, такие как чтение с клавиатуры или запись на диск, должны быть явно обозначены в типе.


Сохранение и запуск программы

  1. Создайте новый файл с именем hello.hs. Имя файла может быть произвольным, но рекомендуется использовать осмысленные названия.
  2. Вставьте в него приведенный выше код.
  3. Сборка и запуск в каталоге с hello.hs:
ghc hello.hs
./hello.exe # Windows; на Linux/macOS: ./hello

Разбор:

  • ghc hello.hs компилирует исходник и создаёт исполняемый файл в текущей директории.
  • ./hello.exe запускает скомпилированную программу на Windows.
  • Разделение "сначала компиляция, потом запуск" помогает видеть ошибки сборки до исполнения.
  • Для Linux/macOS бинарник запускается как ./hello, без расширения .exe.

Альтернативный способ — запуск без компиляции через интерпретатор GHCi. Это удобно для быстрого тестирования:

ghci hello.hs

Разбор:

  • ghci hello.hs открывает интерактивную среду и загружает модуль из файла.
  • Изменения можно перезагружать без полной сборки, что ускоряет обучение и эксперименты.
  • После загрузки вызов main выполняет то же действие, что и скомпилированный бинарник.
  • Такой режим полезен для пошаговой проверки выражений и сигнатур.

После загрузки появится приглашение Prelude>. Введите main и нажмите Enter. Программа выполнится, и вы увидите тот же результат. Чтобы выйти из GHCi, введите :quit.


Структура программы и ключевые понятия

Функция main — это точка входа в каждую исполняемую программу на Haskell. Без неё компилятор не сможет создать исполняемый файл. Все действия, связанные с внешним миром — вывод текста, чтение файлов, сетевые запросы — должны происходить внутри main или функций, вызываемых из неё.

Тип IO () указывает, что функция взаимодействует со средой выполнения. Символ () обозначает "единичный тип" — аналог void в других языках, но в Haskell это полноценное значение, называемое "unit".

Функция putStrLn принимает строку и возвращает действие ввода-вывода. Строки в Haskell заключаются в двойные кавычки и представляют собой списки символов. Это отличает их от многих других языков, где строки — это примитивные объекты.


Расширение первой программы

После успешного запуска "Hello, World!" можно добавить простую логику. Например, запросить имя пользователя и поприветствовать его:

main :: IO ()
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ "!")

Разбор:

  • do объединяет несколько IO-действий в последовательность выполнения.
  • putStrLn выводит приглашение пользователю перед чтением ввода.
  • name <- getLine извлекает строку из действия IO String и связывает её с именем name.
  • Выражение "Hello, " ++ name ++ "!" собирает приветствие через конкатенацию строк.
  • Финальный putStrLn печатает персонализированный результат в консоль.

Конструкция do позволяет объединять несколько действий ввода-вывода в последовательность. Оператор <- извлекает значение из действия IO и связывает его с переменной name. Оператор ++ выполняет конкатенацию строк.

Эта программа демонстрирует, как в Haskell работают с пользовательским вводом, не нарушая принципов чистоты: все побочные эффекты остаются внутри блока IO, а остальная часть программы может быть полностью функциональной.


Советы по дальнейшему обучению

После написания первой программы стоит изучить основы системы типов Haskell, работу с функциями, списками и рекурсией. Haskell поощряет декларативный стиль: вместо описания шагов алгоритма вы описываете, что должно быть получено. Это требует переосмысления привычных подходов к программированию, но открывает доступ к мощным абстракциям.

Рекомендуется практиковаться в GHCi, экспериментируя с простыми выражениями. Например, можно проверить, как работает функция map, или написать собственную функцию для вычисления факториала. Каждый эксперимент укрепляет понимание функциональной парадигмы.

Полезная связка внутри раздела:


Быстрый старт через Stack (альтернатива Cabal)

Stack фиксирует совместимый набор пакетов (snapshot) и версию GHC, что упрощает воспроизводимую сборку на новой машине:

stack new hello-haskell
cd hello-haskell
stack run

Разбор:

  • stack new hello-haskell создаёт новый проект по шаблону с базовой структурой и конфигами.
  • cd hello-haskell переходит в каталог проекта, где находятся stack.yaml и исходники.
  • stack run автоматически резолвит зависимости, собирает проект и запускает целевое приложение.
  • Такой поток удобен для воспроизводимых сборок на разных машинах команды.

stack run собирает проект и запускает исполняемый файл (см. блок выше). Для учебных и промышленных проектов Stack и Cabal дополняют друг друга: оба публикуют пакеты на Hackage.


Организация проекта с помощью Cabal

После успешного запуска простой программы возникает необходимость в управлении зависимостями, модулями и сборкой более сложных приложений. Для этих целей в экосистеме Haskell используется система сборки Cabal (Common Architecture for Building Applications and Libraries). Cabal позволяет описывать структуру проекта, указывать зависимости от сторонних библиотек, управлять версиями и автоматизировать компиляцию.


Создание нового проекта

Новый проект с Cabal:

cabal init

Разбор:

  • cabal init запускает мастер инициализации нового проекта в текущей папке.
  • Во время диалога формируются базовые метаданные пакета и тип цели (например, executable).
  • По итогам команда создаёт .cabal-файл и стартовую структуру каталогов/исходников.
  • Это отправная точка для управляемой сборки, зависимостей и дальнейшего масштабирования проекта.

Эта команда запустит интерактивный мастер инициализации. Он задаст несколько вопросов — имя проекта, автора, лицензию, тип проекта (библиотека или исполняемое приложение). Для первой программы выберите тип executable. В результате будет создан каталог с файлами:

  • hello.cabal — основной конфигурационный файл проекта.
  • src/Main.hs — исходный файл с точкой входа.
  • .gitignore, CHANGELOG.md, README.md — вспомогательные файлы.

Файл .cabal содержит метаданные проекта и описание компонентов. Пример секции для исполняемого файла:

executable hello
main-is: Main.hs
hs-source-dirs: src
build-depends: base >=4.16 && <5
default-language: Haskell2010

Разбор:

  • executable hello объявляет исполняемую цель сборки с именем hello.
  • main-is указывает входной модуль программы, где ожидается функция main.
  • hs-source-dirs задаёт директорию, откуда Cabal берёт исходники модуля.
  • build-depends фиксирует пакетные зависимости и их допустимый диапазон версий.
  • default-language закрепляет языковой стандарт для предсказуемой компиляции.

Здесь указано, что исполняемый файл hello использует модуль Main.hs из директории src и зависит только от стандартной библиотеки base.


Сборка и запуск через Cabal

Вместо прямого вызова ghc теперь можно использовать команды Cabal:

cabal build
cabal run

Разбор:

  • cabal build выполняет компиляцию проекта и зависимостей без запуска приложения.
  • cabal run после сборки запускает выбранный исполняемый компонент.
  • Такая пара команд удобна в цикле разработки: отдельно проверка сборки и быстрый запуск.
  • Cabal сам управляет кэшем артефактов, поэтому повторные сборки идут быстрее.

Команда cabal run автоматически соберёт проект и запустит исполняемый файл. Это особенно удобно при наличии нескольких исполняемых компонентов или зависимостей.

Если позже потребуется добавить внешнюю библиотеку, например text для работы со строками, достаточно указать её в поле build-depends:

build-depends: base >=4.16 && <5, text

Разбор:

  • В поле build-depends перечисляются все библиотеки, нужные этому компоненту.
  • base >=4.16 && <5 задаёт совместимый диапазон для стандартной библиотеки.
  • Добавление text подключает пакет для более эффективной работы со строковыми данными.
  • После изменения этого поля следующая сборка автоматически подтянет новую зависимость.

Cabal автоматически загрузит нужную версию из центрального репозитория Hackage при следующей сборке.


Модульная структура и организация кода

Haskell поощряет разбиение кода на модули. Каждый файл с расширением .hs представляет собой модуль. По соглашению, имя модуля совпадает с путём к файлу. Например, файл src/Utils/Strings.hs должен начинаться с:

module Utils.Strings where

Разбор:

  • Объявление задаёт модульное имя Utils.Strings, соответствующее структуре каталогов.
  • where открывает тело модуля, где размещаются определения функций и типов.
  • Такая структура помогает разделять код по ответственности и облегчает импорт.

Модуль может экспортировать функции, типы и значения. Если список экспорта не указан, экспортируются все определения. Явное указание экспорта повышает читаемость и инкапсуляцию:

module Utils.Strings (greetUser) where

greetUser :: String -> String
greetUser name = "Hello, " ++ name ++ "!"

Разбор:

  • В скобках после имени модуля указан явный экспорт: наружу доступна только greetUser.
  • Сигнатура String -> String документирует чистое преобразование имени в приветствие.
  • ++ склеивает строки, формируя итоговый ответ.
  • Ограничение экспорта поддерживает инкапсуляцию внутренней реализации модуля.

Основной модуль Main может импортировать этот модуль:

module Main where

import Utils.Strings

main :: IO ()
main = do
putStrLn "Enter your name:"
name <- getLine
putStrLn (greetUser name)

Разбор:

  • import Utils.Strings подключает функцию greetUser из вспомогательного модуля.
  • main остаётся IO-точкой входа и оркестрирует взаимодействие с пользователем.
  • name <- getLine получает ввод, затем greetUser name выполняет чистое преобразование.
  • Разделение на IO-слой и чистую функцию повышает тестируемость и читаемость проекта.

Такая структура делает код масштабируемым и легко тестируемым.


Работа с Haskell Language Server (HLS)

HLS — это сервер языка, обеспечивающий интеллектуальную поддержку Haskell в редакторах. Он предоставляет:

  • Проверку типов в реальном времени.
  • Подсказки по сигнатурам функций.
  • Переход к определению.
  • Автоматическое форматирование (через ormolu или fourmolu).
  • Обнаружение неиспользуемого кода.
  • Интеграцию с тестами и документацией.

После установки GHCup HLS устанавливается автоматически. В Visual Studio Code расширение Haskell активирует его без дополнительной настройки. При открытии проекта с файлом .cabal HLS считывает зависимости и начинает анализировать весь код.

Если HLS не запускается, проверьте наличие файла hie.yaml или корректность структуры проекта. В большинстве случаев проблем не возникает.


Типичные ошибки новичков и как их избежать

  1. Отсутствие отступов или неправильная их глубина
    Haskell чувствителен к отступам. Все строки внутри одного блока должны начинаться на одном уровне. Используйте пробелы, а не табуляцию.

  2. Пропущенная сигнатура типа
    Хотя Haskell выводит типы автоматически, явное указание сигнатур повышает надежность и упрощает отладку.

  3. Попытка использовать переменные вне IO
    Значения, полученные через <-, доступны только внутри блока do. Нельзя использовать их в чистых функциях напрямую.

  4. Неправильное имя файла или модуля
    Имя файла должно соответствовать имени модуля. Например, модуль Main должен находиться в файле Main.hs.

  5. Забытая компиляция перед запуском
    После изменения кода необходимо пересобрать программу. При использовании cabal run это происходит автоматически.


Если "не запускается": быстрый чек

  • Проверьте, что main имеет тип IO ().
  • Убедитесь, что файл сохранён в UTF-8 и расширение .hs.
  • В GHCi выполните :load hello.hs и посмотрите первую ошибку (обычно она ключевая).
  • Если проблема в зависимостях проекта — выполните cabal update и повторите cabal build.

Не пытайтесь исправить "всё сразу": идите от первой ошибки компилятора к следующей.


Что попробовать

  1. ghci:type 2 + 2 — посмотрите вывод типов.
  2. Функция с pattern matching на список [x].
  3. Веб позже: Phoenix (отдельные материалы раздела).

Сессия в ghci после первого запуска:

ghci> :type 2 + 2
2 + 2 :: Num a => a
ghci> let square x = x * x
ghci> square 5
25
ghci> :type square
square :: Num a => a -> a
ghci> map square [1,2,3]
[1,4,9]

Разбор:

  • :type показывает выведенную сигнатуру без компиляции отдельного файла.
  • let square x = ... объявляет функцию прямо в REPL.
  • Вызов square 5 сразу демонстрирует поведение.
  • map square [1,2,3] проверяет функцию высшего порядка на коллекции.

Мини-файл для экспериментов с pattern matching:

describeList :: [a] -> String
describeList [] = "пустой список"
describeList [_] = "один элемент"
describeList (_:_) = "два и более элементов"

main :: IO ()
main = putStrLn (describeList [1, 2, 3])

Разбор:

  • Три уравнения функции покрывают разные формы списка.
  • Шаблон (_:_) означает "непустой список", конкретные значения не нужны.
  • describeList [1,2,3] возвращает строку "два и более элементов".
  • main выводит результат в консоль при запуске скомпилированной программы.

Что читать дальше


В подборках

Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:

Первые шаги (маршрут подборки) — Первая программа на Groovy, Первая программа на Scala, Первая программа на Smalltalk, Первая программа на Elixir, Первая программа на Lua, Первая программа на Zig.