haskell Types 和 Typeclasses

Algebraic Data Types 入门

在前面的章节中,我们谈了一些 Haskell 内置的类型和 Typeclass。而在本章中,我们将学习构造类型和 Typeclass 的方法。

我们已经见识过许多类型,如 BoolIntCharMaybe 等等,不过在 Haskell 中该如何构造自己的类型呢?好问题,一种方法是使用 data 关键字。首先我们来看看 Bool 在标准函式库中的定义:

data Bool = False | True

data 表示我们要定义一个新的类型。= 的左端标明类型的名称即 Bool= 的右端就是值构造子 (Value Constructor),它们明确了该类型可能的值。| 读作"或",所以可以这样阅读该声明Bool 类型的值可以是 True 或False。类型名和值构造子的首字母必大写。

相似,我们可以假想 Int 类型的声明:

data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647

头尾两个值构造子分别表示了 Int 类型的最小值和最大值,注意到真正的类型宣告不是长这个样子的,这样写只是为了便于理解。我们用省略号表示中间省略的一大段数字。

我们想想 Haskell 中图形的表示方法。表示圆可以用一个 Tuple,如(43.1,55.0,10.4),前两项表示圆心的位置,末项表示半径。听着不错,不过三维矢量或其它什么东西也可能是这种形式!更好的方法就是自己构造一个表示图形的类型。假定图形可以是圆 (Circle) 或长方形 (Rectangle):

data Shape = Circle Float Float Float | Rectangle Float Float Float Float

这是啥,想想?Circle 的值构造子有三个项,都是 Float。可见我们在定义值构造子时,可以在后面跟几个类型表示它包含值的类型。在这里,前两项表示圆心的坐标,尾项表示半径。Rectangle 的值构造子取四个 Float 项,前两项表示其左上角的坐标,后两项表示右下角的坐标。

谈到「项」 (field),其实应为「参数」 (parameters)。值构造子的本质是个函数,可以返回一个类型的值。我们看下这两个值构造子的类型声明:

ghci> :t Circle Circle :: Float -> Float -> Float -> Shape ghci> :t Rectangle Rectangle :: Float -> Float -> Float -> Float -> Shape

Cool,这么说值构造子就跟普通函数并无二致啰,谁想得到?我们写个函数计算图形面积:

surface :: Shape -> Float surface (Circle _ _ r) = pi * r ^ 2 surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)

值得一提的是,它的类型声明表示了该函数取一个 Shape 值并返回一个 Float 值。写 Circle -> Float 是不可以的,因为 Circle 并非类型,真正的类型应该是 Shape。这与不能写True->False 的道理是一样的。再就是,我们使用的模式匹配针对的都是值构造子。之前我们匹配过 []False 或 5,它们都是不包含参数的值构造子。

我们只关心圆的半径,因此不需理会表示坐标的前两项:

ghci> surface $ Circle 10 20 10 314.15927 ghci> surface $ Rectangle 0 0 100 100 10000.0

Yay,it works!不过我们若尝试输出 Circle 10 20 到控制台,就会得到一个错误。这是因为 Haskell 还不知道该类型的字符串表示方法。想想,当我们往控制台输出值的时候,Haskell 会先调用 show 函数得到这个值的字符串表示才会输出。因此要让我们的 Shape 类型成为 Show 类型类的成员。可以这样修改:

data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)

先不去深究 deriving(派生),可以先这样理解:若在 data 声明的后面加上 deriving (Show),那 Haskell 就会自动将该类型至于 Show 类型类之中。好了,由于值构造子是个函数,因此我们可以拿它交给 map,拿它不全调用,以及普通函数能做的一切。

ghci> Circle 10 20 5 Circle 10.0 20.0 5.0 ghci> Rectangle 50 230 60 90 Rectangle 50.0 230.0 60.0 90.0

我们若要取一组不同半径的同心圆,可以这样:

ghci> map (Circle 10 20) [4,5,6,6] [Circle 10.0 20.0 4.0,Circle 10.0 20.0 5.0,Circle 10.0 20.0 6.0,Circle 10.0 20.0 6.0]

我们的类型还可以更好。增加加一个表示二维空间中点的类型,可以让我们的 Shape 更加容易理解:

data Point = Point Float Float deriving (Show) data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

注意下 Point 的定义,它的类型与值构造子用了相同的名字。没啥特殊含义,实际上,在一个类型含有唯一值构造子时这种重名是很常见的。好的,如今我们的 Circle 含有两个项,一个是 Point 类型,一个是 Float 类型,好作区分。Rectangle 也是同样,我们得修改 surface 函数以适应类型定义的变动。

surface :: Shape -> Float surface (Circle _ r) = pi * r ^ 2 surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)

唯一需要修改的地方就是模式。在 Circle 的模式中,我们无视了整个 Point。而在 Rectangle 的模式中,我们用了一个嵌套的模式来取得 Point 中的项。若出于某原因而需要整个 Point,那么直接匹配就是了。

ghci> surface (Rectangle (Point 0 0) (Point 100 100)) 10000.0 ghci> surface (Circle (Point 0 0) 24) 1809.5574

表示移动一个图形的函数该怎么写?它应当取一个 Shape 和表示位移的两个数,返回一个位于新位置的图形。

nudge :: Shape -> Float -> Float -> Shape nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1+a) (y1+b)) (Point (x2+a) (y2+b))

简洁明了。我们再给这一 Shape 的点加上位移的量。

ghci> nudge (Circle (Point 34 34) 10) 5 10 Circle (Point 39.0 44.0) 10.0

如果不想直接处理 Point,我们可以搞个辅助函数 (auxilliary function),初始从原点创建图形,再移动它们。

baseCircle :: Float -> Shape baseCircle r = Circle (Point 0 0) r  baseRect :: Float -> Float -> Shape baseRect width height = Rectangle (Point 0 0) (Point width height)
ghci> nudge (baseRect 40 100) 60 23 Rectangle (Point 60.0 23.0) (Point 100.0 123.0)

毫无疑问,你可以把你的数据类型导出到模块中。只要把你的类型与要导出的函数写到一起就是了。再在后面跟个括号,列出要导出的值构造子,用逗号隔开。如要导出所有的值构造子,那就写个..。

若要将这里定义的所有函数和类型都导出到一个模块中,可以这样:

module Shapes ( Point(..) , Shape(..) , surface , nudge , baseCircle , baseRect ) where

一个 Shape (..),我们就导出了 Shape 的所有值构造子。这一来无论谁导入我们的模块,都可以用 Rectangle 和Circle 值构造子来构造 Shape 了。这与写 Shape(Rectangle,Circle) 等价。

我们可以选择不导出任何 Shape 的值构造子,这一来使用我们模块的人就只能用辅助函数 baseCircle 和 baseRect来得到 Shape 了。Data.Map 就是这一套,没有 Map.Map [(1,2),(3,4)],因为它没有导出任何一个值构造子。但你可以用,像 Map.fromList 这样的辅助函数得到 map。应该记住,值构造子只是函数而已,如果不导出它们,就拒绝了使用我们模块的人调用它们。但可以使用其他返回该类型的函数,来取得这一类型的值。

不导出数据类型的值构造子隐藏了他们的内部实现,令类型的抽象度更高。同时,我们模块的用户也就无法使用该值构造子进行模式匹配了。

 

haskell Types 和 Typeclasses,布布扣,bubuko.com

时间: 2024-10-09 12:28:19

haskell Types 和 Typeclasses的相关文章

Haskell Types与Typeclasses

可使用 :t 命令检测表达式类型. 明确的类型首字母必大写. 一.Types Char Bool Int(有界,与Integer类型对比效率高) Integer(无界,与Int类型对比效率低) Float Double Tuple的类型取决于长度和其中元素的类型. List的类型只取决于其中元素的类型. 二.Typeclasses “=>”表示约束. Eq:可比较相等性. Ord:可比较大小. compare:取两个Ord中相同类型的值做参数,结果为LT,GT,EQ三种情况(小于/大于/相等).

Haskell手撸Softmax回归实现MNIST手写识别

Haskell手撸Softmax回归实现MNIST手写识别 前言 初学Haskell,看的书是Learn You a Haskell for Great Good, 才刚看到Making Our Own Types and Typeclasses这一章. 为了加深对Haskell的理解,便动手写了个Softmax回归.纯粹造轮子,只用了base. 显示图片虽然用了OpenGL,但是本文不会提到关于OpenGL的内容.虽说是造轮子, 但是这轮子造得还是使我受益匪浅.Softmax回归方面的内容参考

「SF-QC」2 TypeClasses - 黄玄的博客

Considerring printing different types with this common idiom: 1 2 3 4 5 6 showBool : bool → string showNat : nat → string showList : {A : Type} (A → string) → (list A) → string showPair : {A B : Type} (A → string) → (B → string) → A * B → string Defi

浅谈haskell中functor typeclass和普通typeclasses的区别

其实这个区别就好像普通函数和高阶函数的区别一样.这样是不是很好理解了呢,额,如果你说你还不知道啥是高阶函数,那么还是不要看这个文章了.下面来看看我是如何把他们类比起来的. 我们看看haskell中的Eq是如何定义的,这个我把它叫"普通typeclasses"(为了区分functor typeclasses,我就这么叫它了:P),这里定义了一个typeclasses并且在这个typeclasses里面定义了一个行为,普遍的说法就是你可以把这个typeclasses想象成java的inte

From Zero to HIPster (Haskell In Production)

(TL,DR)We're building a micro-service platform christened Hasura.io (alpha release scheduled in summer 2015), and we used Haskell as the core programming language to build it. This is a post for people who're not very sure about using Haskell in prod

haskell目录层次

[email protected] /usr/lib/ghc/haskell2010-1.1.1.0 $ tree . ├── Control │   └── Monad.hi ├── Data │   ├── Array.hi │   ├── Bits.hi │   ├── Char.hi │   ├── Complex.hi │   ├── Int.hi │   ├── Ix.hi │   ├── List.hi │   ├── Maybe.hi │   ├── Ratio.hi │   └

An introduction to parsing text in Haskell with Parsec

Parsec makes parsing text very easy in Haskell. I write this as much for myself as for anyone else to have a tutorial and reference which starts from the ground up and works through how each function can be used with examples all the way. First off,

Haskell学习笔记二:自定义类型

内容提要: 代数数据类型 - Algebraic Data Types: 自定义数据类型 - data关键字:值构造器:类型变量与类型构造器: 记录(Record)语法 - 简化自定义数据类型的一种语法糖: 一个完整的例子 - PurchaseOrder定义和简单计算.单元测试: 代数数据类型(Algebraic Data Types) 为什么Haskell的数据类型会有代数数据类型这个名字?回想我们初中时代,初次学习代数的情况,印象最深刻就是x,y,z代替了具体的数字,引入方程式的概念,对 解

Haskell学习笔记一:类型和类型类相关内容

内容提要: 静态类型系统: 编译时确定类型错误: 类型推导机制: 基础类型:Int,Integer,Float,Double,Bool,Char: 类型变量: 基础类型类:Eq,Ord,Show,Read,Enum,Bounded,Num,Integral,Floating: Haskell是一门函数式编程语言,被称为最为纯粹的函数式编程语言.Haskell的类型系统非常强大,其中包含了很多有趣.抽象.某种程度上充满学术气息的特质. Haskell属于静态类型语言,这意味着: 每个值或者表达式,