如何用标准化的,通用的纯函数编程语言Haskell来构建Web?Haskell构建的Web拥有着什么样的优点呢?让我们来看看构建的过程吧。

haskell语言入门(快速用Haskell构建超级简单的)(1)

作者 | William Yao

译者 |弯月,责编 | maozz

出品 | CSDN(haskell语言入门(快速用Haskell构建超级简单的)(2)ID:CSDNnews)

以下为译文:

Haskell有大量的库可用于满足基本的后台需求,从日志输出到数据库访问,再到Web服务器的定义和路由,应有尽有。

拥有选择的自由固然很好,但如果你刚刚接触这个领域,那么大量的选择可能会让你目不暇接。也许你没法自信地判断出这些选择之间的区别。例如,你需要查询数据库。

那么,你需要Squeal提供的列名强保证,以及深度SQL嵌入功能吗?还是青睐Opaleye在保证类型安全的前提下的简单性?或者只是使用简单的PostgreSQL-simple就好?还是使用Selda?或者……

我用最简单的库写了一个Web应用程序,一方面是想告诉你,你不需要担心技术栈是否足够先进,另一方面也是我自己的学习过程。

如果你不知道怎样用Haskell构建真正的应用程序,为什么不去学习呢?我会尽可能保持代码的简单性。

点击此处可查看源代码(https://gitlabhaskell语言入门(快速用Haskell构建超级简单的)(3).com/williamyaoh/haskell-web-stack)。

下面我们来看一看我选择的这些库,以及它们的功能,还有这个应用程序的功能。

haskell语言入门(快速用Haskell构建超级简单的)(4)

这个Web应用程序究竟是什么呢?

它是一个网站,用户可以在这个网站上创建定时器和做笔记。

我举个例子说明该网站的用途:某人想设置多个定时器来跟踪多个反应物的过程,同时用笔记来记录他们需要关心的事情、下次制作配方时需要改进的事情,等等。

再举一个例子,某个人在玩MOBA游戏,比如英雄联盟或者刀塔2,他们可以在另一台显示器上打开一个页面来跟踪关键技能的冷却时间,同时用笔记记录宏操作、对手的组合,以及团战时需要haskell语言入门(快速用Haskell构建超级简单的)(5)关注技的冷却等。

haskell语言入门(快速用Haskell构建超级简单的)(6)

在这个应用程序中我将演示:

haskell语言入门(快速用Haskell构建超级简单的)(8)

我用到了哪些库?

路由和Web服务器:Spock

最终我选择了Spock,因为它易于使用。如果你用过Ruby的Sinatra,那么Spock应该不会陌生。Spock也自带了会话管理,这一点非常好。

例如,定义一个拥有几条路由的服务器,返回Html和json,代码大致如下:

{-# LANGUAGE OverloadedStrings #-}

import Web.Spock as Spock

import Web.Spock.Config as Spock

import Data.Aeson as A

main :: IO

main = do

spockCfg <- defaultSpockCfg PCNoDatabase

runSpock haskell语言入门(快速用Haskell构建超级简单的)(9)3000 $ spock spockCfg $ do

get root $ do

Spock.html "<div>Hello world!</div>"

get "users" $ do

Spock.json (A.object [ "users" .= users ])

get ("users" <//> var <//> "friends") $ \userID -> do

Spock.json (A.object [ "userID" .= (userID :: Int), "friends" .= A. ])

where users :: [String]

users = ["bob", "alice"]

数据库访问:postgresql-simple

postgresql-simple基本上只允许您对数据库运行原始SQL查询,而没有多余的装饰,例如防止注入攻击。它可以实现您的期望,仅此而已。

{-# LANGUAGE OverloadedStrings #-}

import Database.PostgreSQL.Simple

userLoginsQuery :: Query

userLoginsQuery =

"SELECT l.user_id, COUNT(1) FROM logins l GROUP BY l.user_id;"

getUserLogins :: Connection -> IO [(Int, Int)]

getUserLogins conn = query_ conn userLoginsQuery

数据库访问:postgresql-simple

postgresql-simple可以让你在数据库上运行原始的SQL查询,它只提供最基本的额外处理,比如防止注入攻击等。它仅完成你需要的东西,没有任何额外的功能。

{-# LANGUAGE OverloadedStrings #-}

import Database.PostgreSQL.Simple

userLoginsQuery :: Query

userLoginsQuery =

"SELECT l.user_id, COUNT(1) FROM logins l GROUP BY l.user_id;"

getUserLogins :: Connection -> IO [(Int, Int)]

getUserLogins conn = query_ conn userLoginsQuery

配置:configurator

configurator能够从文件中读取配置,并解析成Haskell数据类型。与普通的配置文件读取器相比它的功能要多一些。

如果你习惯了直接读取配置文件,那么configurator还有一些额外功能。例如,配置项可以嵌套分组,configurator还提供了热重载来监视配置文件变化。

# An example config file.

haskell语言入门(快速用Haskell构建超级简单的)(10)app_name = "The Whispering Fog"

db {

pool {

stripes = 4

resource_ttl = haskell语言入门(快速用Haskell构建超级简单的)(11)300

}

username = "pallas"

password = "thefalloflatinium"

dbname = "italy"

}

{-# LANGUAGE OverloadedStrings #-}

import Data.Configurator as Cfg

import Database.PostgreSQL.Simple

data MyAppConfig = MyAppConfig

{ haskell语言入门(快速用Haskell构建超级简单的)(12)appName :: String

, haskell语言入门(快速用Haskell构建超级简单的)(13)appDBConnection :: Connection

}

getAppConfig :: IO MyAppConfig

getAppConfig = do

cfgFile <- Cfg.load ["haskell语言入门(快速用Haskell构建超级简单的)(14)app-configuration.cfg"]

name <- Cfg.require cfgFile "haskell语言入门(快速用Haskell构建超级简单的)(15)app_name"

conn <- do

username <- Cfg.require cfgFile "db.username"

password <- Cfg.require cfgFile "db.password"

dbname <- Cfg.require cfgFile "db.dbname"

connect $ defaultConnectInfo

{ connectUser = username

, connectPassword = password

, connectDatabase = dbname

}

pure $ MyAppConfig

{ haskell语言入门(快速用Haskell构建超级简单的)(16)appName = name

, haskell语言入门(快速用Haskell构建超级简单的)(17)appDBConnection = conn

}

日志:fast-logger

fast-logger提供了一个相对简单易用的日志解决方案。在Web应用程序的示例中,我只输出到了stderr,但它还可以支持输出日志到文件。虽然它支持许多类型,但绝大多数情况下,你需要定义一个辅助函数,接收一个LoggerSet,以及需要记录的信息。

import System.Log.FastLogger as Log

logMsg :: Log.LoggerSet -> String -> IO

logMsg logSet msg =

Log.pushLogStrLn logSet (Log.toLogStr msg)

doSomething :: IO

doSomething = do

logSet <- Log.newStderrLoggerSet Log.defaultBufSize

logMsg logSet "message 1"

logMsg logSet "message 2"

HTML生成:blaze-html

尽管本项目的后台并不需要生成太多HTML,但值得一提的是,blaze-html正是我需要的。

基本上它就是将HTML浅层嵌入到了Haskell DSL中。如果你会编写HTML,那你就会使用这个库。

{-# LANGUAGE OverloadedStrings #-}

import Data.ByteString.Lazy

import Text.Blaze.Html5 as HTML

import Text.Blaze.Html5.Attributes as HTML hiding ( title )

import Text.Blaze.Html.Renderer.Utf8 as HTML

dashboardHTML :: HTML.Html

dashboardHTML = HTML.html $

HTML.docTypeHtml $ do

HTML.head $ do

HTML.title "Timers and Notes"

HTML.meta ! HTML.charset "utf-8"

HTML.script ! HTML.src "/js/bundle.js" $ ""

HTML.body $ do

HTML.div ! HTML.id "content" $ ""

dashboardBytes :: ByteString

dashboardBytes = HTML.renderHtml dashboardHTML

构建前端:make npm

没错,它们并不是库。但我们依然需要某种JavaScript前端,因为定时器需要实时更新。Webpack能够生成JS包,而Make能够组装最终的应用程序。

这些东西无需我多说,网上有很多相关的资源。

haskell语言入门(快速用Haskell构建超级简单的)(18)

我必须要用这些库吗?

当然不是。如果你第一次接触Haskell,那么有这些疑问是很自然的。不要让这篇文章限制了你的思路。尽管这个应用程序可以运行,但许多部分用于生产环境下的Haskell时并不理想。

例如,许多Haskell程序员很可能会使用Servant而不是Spock来定义API端点。如果你想了解其他库,那当然应该跟随你的直觉。

你可以把这些库和这个应用程序作为起点。我haskell语言入门(快速用Haskell构建超级简单的)(19)建议你用这些代码作为学习的机会,理解它的原理,然后自己试着修改。Haskell很好的一点就是它非常易于重构或者升级,而不会破坏已有的功能。

一旦掌握了这个应用程序,就可以用更高级的库来替换它们,来获得更多的保证。同时,这也是一个增量学习的过程。

将数据库访问的库从postgresql-simple升级到支持类型安全的库。我推荐Opaleye!

将API定义的库从Spock升级到Servant

利用QuickCheck或hedgehog增加自动测试。例如,你可以测试服务器的每个错误响应都返回了JSON格式的错误信息。

你还可以尝试替换前端和构建系统。

升级前端,使用PureScript或Elm来替换原始的JavaScript

升级构建系统,利用Shake替换make构建更健壮的系统

原文:https://williamyaohhaskell语言入门(快速用Haskell构建超级简单的)(20).com/posts/2019-11-16-a-dead-simple-web-stack.html

本文为 CSDN 翻译,转载请注明来源出处。

【End】

haskell语言入门(快速用Haskell构建超级简单的)(21)

,