Первая программа на Haskell
Play ITЗагрузка интерактивного демо…
Первая программа на Haskell
Где применяют Haskell
Haskell — чисто функциональный язык — неизменяемые данные, ленивые вычисления, строгая типизация с выводом типов. Учит мыслить выражениями, а не последовательностью присваиваний; применяют в финтехе, компиляторах, исследованиях.
Старт — GHC + Cabal/stack, REPL ghci, main = putStrLn "Hello".
Как подойти к первой программе без перегруза
Чтобы старт не выглядел "слишком академично", держите простой план:
- Установить инструменты и проверить
ghc --version. - Запустить самый короткий
Hello, World!. - Сразу попробовать интерактивный ввод через
getLine. - Только потом идти в тему типов, монад и архитектуры.
Так вы сначала получаете работающий результат, а затем наращиваете теорию поверх практики.
Установка необходимого программного обеспечения на 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 (систему сборки) и другие компоненты одной командой.
- Откройте PowerShell от имени администратора.
- Установка 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.
- Следуйте инструкциям установщика. По умолчанию GHCup предложит установить GHC, Cabal и HLS (Haskell Language Server).
- После завершения перезапустите терминал или 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 стремится сохранять чистоту функций — любые побочные эффекты, такие как чтение с клавиатуры или запись на диск, должны быть явно обозначены в типе.
Сохранение и запуск программы
- Создайте новый файл с именем
hello.hs. Имя файла может быть произвольным, но рекомендуется использовать осмысленные названия. - Вставьте в него приведенный выше код.
- Сборка и запуск в каталоге с
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 или корректность структуры проекта. В большинстве случаев проблем не возникает.
Типичные ошибки новичков и как их избежать
-
Отсутствие отступов или неправильная их глубина
Haskell чувствителен к отступам. Все строки внутри одного блока должны начинаться на одном уровне. Используйте пробелы, а не табуляцию. -
Пропущенная сигнатура типа
Хотя Haskell выводит типы автоматически, явное указание сигнатур повышает надежность и упрощает отладку. -
Попытка использовать переменные вне
IO
Значения, полученные через<-, доступны только внутри блокаdo. Нельзя использовать их в чистых функциях напрямую. -
Неправильное имя файла или модуля
Имя файла должно соответствовать имени модуля. Например, модульMainдолжен находиться в файлеMain.hs. -
Забытая компиляция перед запуском
После изменения кода необходимо пересобрать программу. При использованииcabal runэто происходит автоматически.
Если "не запускается": быстрый чек
- Проверьте, что
mainимеет типIO (). - Убедитесь, что файл сохранён в UTF-8 и расширение
.hs. - В GHCi выполните
:load hello.hsи посмотрите первую ошибку (обычно она ключевая). - Если проблема в зависимостях проекта — выполните
cabal updateи повторитеcabal build.
Не пытайтесь исправить "всё сразу": идите от первой ошибки компилятора к следующей.
Что попробовать
ghci→:type 2 + 2— посмотрите вывод типов.- Функция с pattern matching на список
[x]. - Веб позже: 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выводит результат в консоль при запуске скомпилированной программы.
Что читать дальше
- Чтобы понять логику языка после первого запуска: Основы функционального программирования на Haskell.
- Чтобы научиться читать ошибки компилятора по типам: Типы данных и система типов в Haskell.
- Чтобы сразу перейти к практике: Простые приложения на Haskell.
В подборках
Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:
Первые шаги (маршрут подборки) — Первая программа на Groovy, Первая программа на Scala, Первая программа на Smalltalk, Первая программа на Elixir, Первая программа на Lua, Первая программа на Zig.