Yesod - Haskell (1)

Haskell

Haskell 是一门强大,快速,类型安全的函数式编程语言。本书假定你已经熟悉了Haskell大部分基础知识。这里有两本学Haskell非常棒的书,而且两本都是网上免费阅读的:

另外,这里有许多非常好的文章Scholl of Haskell

为了使用Yesod,你应该至少了解Haskell的基础知识。另外,Yesod使用一些Haskell特性,这些特性在大多入门书籍中没有介绍。所以本书假定读者已经熟悉Haskell的基本语法,本章打算填补这些空缺。

如果你已经能够熟练的使用Haskell,可以轻松的跳过这章。而且如果你在使用Yesod中遇到困难,你仍然可以返回这一章作一个参考。

术语

甚至对于那些已经很熟悉Haskell的程序员来说,有时候仍然会对一些术语感到困惑。这里介绍一些本书要用到的基本术语。

数据类型

数据类型是Haskell这类强类型语言的核心模块之一。 Int之类的数据类型可以被看做基本数据类型。其他数据类型建立在基本数据类型之上,从而创建出更复杂的数据类型。 举个例子,你可能使用以下代码表示一个person:

data Person = Person Text Int

Text代表person的名字, Int 代表person的年龄,因为这个例子比较简单,所以会在本书中经常出现,实际上有三种方式来创建一个新的数据类型:

  • type 声明,例如type GearCount = Int 仅仅是为一个现存的类型创建了一个别名。类型系统允许你在需要GearCount的地方使用 Int 替换。 使用type 能让你的代码更加可读。
  • newtype 声明,例如 newtype Make = Make Text。 这种情况下,编译器不允许你使用 Text 替代 Make;newtype包装在编译过程中消失,并不引入额外的开销。
  • data 声明,例如 上面的Person ,你可以创建代数数据类型(ADTs),例如 data Vehicle = Bicycle GearCount | Car Make Model

数据构造器

在我们上面的例子中, PersonMakeBicycleCar 都是数据构造器。

类型构造器

在我们上面的例子中, PersonMakeVehicle 都是类型构造器。

类型值

思考一下数据类型data Maybe a = Just a | Nothing。 在这种情况下,a 就是一个类型变量。

PersonMake 数据类型中,我们的类型构造器和数据构造器使用了同样的名字。在处理单数据构造器的数据类型时,这是一中常见的做法。然而,这样并没有什么硬性的要求;你完全可以给数据构造器和类型构造器取不同的名字。

工具

自从2015年7月,Yesod工具推荐变得非常简单:就是使用stack, stack是一个完整的Haskell构建工具, 它可以处理你的编译器(Glasgow Haskell Compiler, 同 GHC),库(包括Yesod),额外的构建工具(比如alexhappy)等等。这里还有其他Haskell可用的构建工具,其中大多数都对Yesod支持很好。但是作为最简单的方式,强烈建议使用stack。Yesod网页上已经更新到最新了(快速入门指南)[http://www.yesodweb.com/page/quickstart],提供了stack的安装说明和快速开始。

一旦你已经设置好你的工具链,你需要安装许多Haskell库。使用一下命令可以安装本书需要的大部分库:

stack build yesod persistent-sqlite yesod-static esqueleto

运行本书中的例子,你需要把代码保存到一个文件,比如,yesod-example.hs, 然后使用一下命令运行:

stack runghc yesod-example.hs

语言编译指示

GHC运行默认情况下在一些情况非常接近Haskell98。而且附带大量的语言扩展,允许更加强大的类型类,语法变化等等。这里有多种方式告诉GHC打开这些扩展。对于本书中的大部分代码片段,你会看到语言编译指示,就像下方所示:

{-# LAUGUAGE MyLanguageExtension #-}

这些应该出现源文件的开头。另外,这里还有两个其他途径:

  • 在GHC命令行中,传入额外的参数-XMyLanguageExtension
  • 在你的cabal文件,添加一个default-extension块。

我个人意见是不要使用GHC命令行参数方式。这是个人爱好,但是我比较喜欢把我的设置清晰的写在一个文件中。通常情况下,不建议将扩展写在cabal文件中; 在写公用的库的时候,这个规则大多数情况下是奏效的。当你和你的团队合作开发的应用时,把所有语言扩展指定到一个特定的位置会有很大的意义。 Yesod网页使用这种途径,来避免每个文件中相同的语言扩展样板代码。

我们会在本书中使用相当多的语言扩展。我们不会覆盖到它们的所有含义,如果需要,请阅读GHC文档

重载字符串

"hello"是什么类型呢?传统意义上,它是个String,定义是type String = [Char]。 不幸的是,这样有一些限制:

  • 这是一个很低效的文本数据实现。我们需要为连接每个单元分配额外的内存,加上每个字符本身都会消耗一个完整机器字(machine word)。
  • 有时候我们有一些长得像string但不是文本的数据,比如ByteString和HTML。

要解决这些限制,GHC有一个语言扩展叫做OverloadedStrings。当打开这个选项的时候,字面字符串将不再有单一类型String;替代的,它们将会有这种类型IsString a ⇒ a,定义如下:

class IsString a where
    fromString :: String -> a

在Haskell中,有许多IsString的可用实例,例如,Text(一个更加高效的压缩String类型),ByteString,和Html。 实际上,本书的每一个例子都会假定此语言扩展是打开的。

不幸的是,对于这个扩展,这里有一个弊端:有时候会迷惑GHC的类型检查系统,想想一下我们有如下代码:

{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances #-}
import Data.Text (Text)

class DoSomething a where
    something :: a -> IO ()

instance DoSomething String where
    something _ = putStrLn "String"

instance DoSomething Text where
    something _ = putStrLn "Text"

myFunc :: IO ()
myFunc = something "hello"

程序会打印出什么呢? String或者Text?这不清楚。所以,你需要给一个明确的注解,这个"hello"应该被看做String或者Text

在一些情况中,你可以通过使用ExtendDefaultRules语言扩展来解决这些问题,但是我们尝试在书中保持明确,而并不依赖于默认( though we’ll instead try to be explicit in the book and not rely on defaulting.)。

Type Families

Type family的基本概念是说明两种不同类型之间的关联。假定我们想要写一个函数,这个函数安全地取出一个列表中的第一个元素。但是我们不想让它在仅仅在列表中工作;我们可能会把ByteString看成一个Word8列表。为了实现这个功能,我们需要引进一些关联类型,来指定一个类型的内容是什么。T

{-# LANGUAGE TypeFamilies, OverloadedStrings #-}
import Data.Word (Word8)
import qualified Data.ByteString as S
import Data.ByteString.Char8 () -- get an orphan IsString instance

class SafeHead a where
    type Content a
    safeHead :: a -> Maybe (Content a)

instance SafeHead [a] where
    type Content [a] = a
    safeHead [] = Nothing
    safeHead (x:_) = Just x

instance SafeHead S.ByteString where
    type Content S.ByteString = Word8
    safeHead bs
        | S.null bs = Nothing
        | otherwise = Just $ S.head bs

main :: IO ()
main = do
    print $ safeHead ("" :: String)
    print $ safeHead ("hello" :: String)

    print $ safeHead ("" :: S.ByteString)
    print $ safeHead ("hello" :: S.ByteString)

新语法提供了一种能力,我们可以在classinstance中放置一个type。我们也可以使用 data 替代, 当然,这样会创建一个新类型,而不是为一个存在的类型创建一个别名。

这里有其他方式在类型类上下文之外使用关联类型。其他关于type families,看这里Haskell wiki page.

Template Haskell

Template Haskell(TH)是一种代码生成方法。我们在Yesod中大量使用使用,来减少样板代码,和保证生成代码的正确性。Template Haskell本质上是Haskell生成Haskell抽象语法树(AST)。

写 TH 代码可能是很棘手的,而且不幸的是没有很多的类型安全参与其中。 你可以简单地写 TH 生成的代码将无法编译。这是Yesod开发者唯一存在的问题,不是针对Yesod的使用者。整个开发过程, 我们使用大量的单元测试来保证生成的代码的正确性。作为一个使用者,所有你需要做的就是调用这些已经存在的函数。举个例子,包含一个外部定义的Hamlet模板,你可以这样写:

$(hamletFile "myfile.hamlet")

(Hamlet 在Shakespeare章节讨论)。挨着括号的$符号告诉GHC接下来是Haskell模板函数。函数内部的代码会被编译器运行,生成一个Haskell AST,然后再进行编译。 (go meta with this)[http://www.yesodweb.com/blog/2010/09/yo-dawg-template-haskell]。

一个非常好的技巧是 TH 代码允许被任意IO动作执行,因此,我们可以放置一些外部的文件输入然后在编译时候进行解析。一个使用例子,我们在编译时来检查HTML,CSS,JavaScript模板。

如果你的Template Haskell代码是用来生成声明的,那么它应该被放置在文件的上方,我们可以省去$符号和括号,换句话来说:

{-# LANGUAGE TemplateHaskell #-}

-- Normal function declaration, nothing special
myFunction = ...

-- Include some TH code
$(myThCode)

-- Or equivalently
myThCode

你可以使用-ddump-splices GHC选项,来观察Template Haskell为你生成的代码,这是非常有用的。

这里有很多其他Template Haskell特性没有涉及到,获得更多信息,请看Haskell wiki page

Template Haskell引进了阶段限制(stage restriction),其本质上意味着Template Haskell代码拼接之前不能引用到Template Haskell代码。这样有时会要求你重新调整一下你的代码位置。QuasiQuotes也有同样的限制。

Yesod 真正面向使用生成代码来避免模板代码,在Yesod中,广泛使用Template Haskell是可以接受的,更多信息请看"Yesod for Haskellers"章节。

QuasiQuotes

QuasiQuotes(QQ)是Template Haskell的一个辅助扩展,它使得我们可以在Haskell源文件中嵌入任意的内容。举个例子,我们先前提到的hamletFile TH 函数,这个函数功能是从外部文件中读取模板内容。我们也可以使用quasi-quoter来写hamlet

{-# LANGUAGE QuasiQuotes #-}

[hamlet|<p>This is quasi-quoted Hamlet.|]

这种语法使用了方括号和管道'|'。quasi-quoter的名字在'['和'|'之间,内容在'|'和']'之间。

在本书中,我们会多次在一个Template Haskell外部文件上,使用QuasiQuotes,因为前者更加易于拷贝粘贴。但是在生产环境中,除了特别短的输入,都建议使用外部文件的方式,因为这样能够很好的隔离那些不是Haskell语法的代码和Haskell代码。

API文档

Haskell 的标准API文档叫做 Haddock 。标准搜索工具叫做 Hoogle。 我得建议是使用FP Complete's Hoogle search和它自带的Haddocks来搜索和浏览文档。原因是,FP Complete's Hoogle search的数据库覆盖了大量的开源Haskell包, 而且文档较全,还能链接到其他Haddocks。

更加常用的资源是Hackage本身,和haskell.org’s Hoogle instance。缺点是在服务器上的构建问题会使得文档不能产生,Hoogle搜索默认只是搜索可用包的子集。最重要的是,Yesod索引是由FP Complete's Hoogle,而不是haskell.org官网。

如果你在读本书的时候,有任何不理解的类型或者函数,尝试使用FP complete's Hoogle进行搜索来获取更多信息。

总结

如果你只是使用Yesod,你不需要成为Haskell专家,对基本语法熟悉就已经足够了。 希望本章能够给你带来足够的额外信息,让你能够更为舒服的读完本书。

转载于:https://my.oschina.net/mzui/blog/1932318

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值