Мне стало скучно писать на Python, захотелось чего-нибудь необычного. Решил попробовать Haskell. Языка я не знаю, однако просто писать консольные учебные программы, типа вычисления факториала, не хотелось. После изучения довольно большого числа постов про Haskell и его применения в реальной жизни, я понял, что одна из потенциальных точек роста популярности языка — написание web-приложений. Как ни странно, под Haskell есть довольно много web-фреймворков. Мой выбор пал на Spock, потому что, судя по описанию, это простой и быстрый в разработке фреймворк. У меня есть некоторый опыт написания web-приложений на Flask, поэтому я подумал, что будет интересно сравнить такие непохожие подходы в решении похожих задач. В статье я постараюсь максимально подробно изложить свой путь проб и ошибок в изучении Haskell на примере написания простейшего web-приложения на Spock. Возможно, это будет полезно для тех, кто сомневается, пробовать ли изучать Haskell и будет ли он полезен в реальной жизни.
Немного про Haskell и как его готовить
Первое, с чем сталкивается каждый разработчик при изучении нового языка — выбор и настройка среды разработки. Конечно, писать можно и в блокноте, но если вы имеете хотя бы незначительный опыт разработки продакшен проектов, такой способ вызовет у вас боль. К стастью, Haskell достаточно старый и распространенный язык и имеет поддержку для большинства известных редакторов и ide. Мой знакомый хаскелист использует emacs. Я привык к нормальным IDE, поэтому поставил плагин для IntelliJ.
Также, для разработки понадобится stack, который сейчас является стандартом и объединяет в себе компилятор, систему управления пакетами, систему сборки и тестирования.
Все выглядит довольно дружелюбно, с установкой проблем не возникло. Для разработки я использую Mac OS, на других системах не проверял, но подозреваю, что под Linux тоже все заведется без проблем.
Hello, world!
Подготовка
Идем в туториал и пробуем делать все по инструкции. Там предлагают сначала создать стандартный проект через stack: stack new MyLovelyProlect
. Стандартный проект представляет из себя папку с тремя подпапками: app
, src
, test
. Это выглядит довольно логично: одна папка под основное приложение, одна под вспомогательные функции, третья для тестирования. Так как мы пишем "Hello, world!", папки src
и test
нам не понадобится, но удалять их не нужно, так как иначе придется аккуратно подчищать другие файлы, например HelloWorld.cabal
.
Собственно, код
Далее в туториале предлагается скопировать в Main.hs
некоторый код. Мы еще немного его упростим, чтобы сравнить с тем, что предлагает flask.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Web.Spock
import Web.Spock.Config
app :: SpockM () () () ()
app = get root $ text "Hello World!"
main :: IO ()
main =
do cfg <- defaultSpockCfg () PCNoDatabase ()
let mw = spock cfg app
runSpock 8080 mw
Для сравнения, приведу такой же код на flask:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
app.run()
По количеству строк flask все же выигрывает: 8 против 13. Но учитывая, что Haskell — язык статически типизированный и 2 строчки занимает определение типов, разница, на мой взгляд, невелика. По крайней мере, приведенный выше код не отпугнул меня от дальнейшего изучения языка.
Сборка и запуск
Далее идем в HelloWorld.cabal
и дописываем в executable HelloWorld-exe
в секцию build-depends:
строчку Spock >=0.13
. В туториале на сайте предлагается включить еще 2 зависимости, но для моих целей они пока не нужны. Если сейчас попробовать собрать приложение в помощью stack build --fast --pedantic
, получим следующую ошибку:
Error: While constructing the build plan, the following exceptions were encountered:
In the dependencies for HelloWorld-0.1.0.0:
Spock must match >=0.13, but the stack configuration has no specified version (latest matching version is 0.13.0.0)
needed since HelloWorld is a build target.
Some different approaches to resolving this:
* Consider trying 'stack solver', which uses the cabal-install solver to attempt to find some working build configuration. This can be
convenient when dealing with many complicated constraint errors, but results may be unpredictable.
* Recommended action: try adding the following to your extra-deps in /Users/dkvasov/Documents/Haskell/Spock/HelloWorld/stack.yaml:
Spock-0.13.0.0@sha256:8115862eb4fb84a26fb7bcd34f30acf036bd2e7c4eaf813c185c5562d138bba2
Plan construction failed.
Довольно понятно: stack не знает какую версию Spock надо установить, поэтому ее надо прописать в файле stack.yaml
. Дописываем туда extra-deps
и повторяем попытку сборки. Там еще выскочит несколько похожих ошибок, и в итоге в файле stack.yaml
у меня появилось следующее:
extra-deps:
- Spock-0.13.0.0
- Spock-core-0.13.0.0
- reroute-0.5.0.0
- stm-containers-0.2.16
- focus-0.1.5.2
После этого все собралось. Артефакты собрки у меня оказались в папке .stack-work/dist/x86_64-osx/Cabal-2.4.0.1/build
.
У меня все запустилось по команде stack exec HelloWorld-exe
, и на localhost:8080
я увидел желанный "Hello, world!". Никаких плясок с бубном не понадобилось.
Пробуем разобраться что вообще происходит
Пока что мы не использовали никаких специфических знаний о функциональном программировании (ФП) и языке Haskell. Мы использовали здравый смысл и знание основ разработки. Так дальше нельзя. Для дальнейшего понимания нам надо знать об ФП некоторые вещи. ФП не является антиподом ООП. Те, кто знаком с языком Scala, знают, что в нем обе эти концепции легко уживаются. Антиподом ФП является императивное программирование. В то время, как функциональная модель вычислений опирается на композицию функций, императивная модель опирается на процесс последовательного изменения состояний системы. Из этого следует, что в чисто функциональных языках, таких как Haskell, предполагается, что функции являются "чистыми", то есть, не содержат изменяемого состояния и "побочных эффектов", помимо возвращаемого значения. Это позволяет легко строить композиции функций. Собственно, требование "чистоты" накладывает множество ограничений на использование функциональных языков в реальном мире. Однако, раз существуют продакшен-приложения на Haskell, значит все-таки можно как-то использовать чистые функции в реальном мире. Рассмотрим повнимательнее наш Main.hs
.
Насколько я понял, приложение app
является переменной типа SpockM
, который является монадой. Скорее всего, если вы не знакомы с функциональным стилем программирования и теорией категорий, вы не поймете с первого раза что это такое и зачем нужно. Однако, разбраться с этим хотя бы на базовом уровне необходимо, так как именно монады являются основой прикладного использования языка Haskell. Есть довольно много статей разной степени подробности на эту тему, в том числе и на Хабре. Приводить их здесь я, конечно, не буду. Пока что предлагаю считать, что монады — это такая магия, которая позволяет производить, так называемые, побочные эффекты. В нашем приложении есть еще одна монада: IO
. Ее побочным эффектом является ввод-вывод данных.
SpockM параметризуется четырьмя другими типами. Они соответствуют подключению к базе данных, сессии, состоянию и возвращаемому значению. Для пустого приложения ничего этого не нужно, поэтому мы везде будем использовать тип ()
, который называется Unit. Внутри app
мы привязываем пути к действиям. В данном случае мы определили базовый путь /
и действие верни мне текст "Hello, world! по get-запросу
.
Далее мы создаем дефолтный конфиг и присваиваем его в cfg
. Далее с помощью функции spock
создаем middleware для app и cfg и передаем его в runSpock
вместе с желаемым портом запуска.
Заключение
Понятно, что все здесь описаное очень просто, и каждый человек, владеющий английским и оборудованный
Ссылки
- Your First Web Application with Spock — довольно подробное видео на английском с разбором похожего Hello World.
Автор: Guitar1st