Пишем «Hello, world!» приложение для web на Haskell (Spock)

в 9:26, , рубрики: haskell, helloworld, spock

Мне стало скучно писать на 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 вместе с желаемым портом запуска.

Заключение

Понятно, что все здесь описаное очень просто, и каждый человек, владеющий английским и оборудованный мозгом сможет проделать все то же самое, посмотрев в изачальный туториал по spock. Эта статья была скорее про то, как происходило мое знакомство с языком Haskell. Что дальше? Дальше почти все обучающие ресурсы предлагают использовать state и написать todo приложение, потом подключить базу данных, потом… Может быть, в будущем напишу продолжение.

Ссылки

Автор: Guitar1st

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js