我们可以自己定义自己的类型来建模.
data 类型名称 = 构造子 参数 ... | ...
例如
data A1 = G1
表示定义一个叫A1
的类型, 其中G1称为构造子, 就是一个构造它的函函数, 这里G1不需要参数.
所以可以这样构造一个A1
类型的值
x1 :: A1
x1 = G1
例如
data A2 = G2 Int String
表示定义一个叫A2
的类型, 构造它的函数是G2
, 同时G2
需要输入两个参数, 一个是Int, 一个是String.
可以这样构造一个A2
类型的值
x2 :: A2
x2 = G2 1 "a"
例如
data A3
= G3_1 Int String
| G3_2 Boolean
这表示定义一个叫A3
的类型, 构造它的函数有两个, 分别是G3_1和G3_2, 两个函数的参数也不同.
可以这样构造A3
类型的值
x3_1 :: A3
x3_1 = G3_1 1 "a"
x3_2 :: A3
x3_2 = G3_2 true
类型是用来对现实建模的, 我们通过类型来描述问题, 例如, 对于温度这个概念而言, 它是表达冷热的量度.
这个量度有多种表达方法, 比如摄氏度和华氏度.
我们可以这样建模
data WD
= CC Number
| FF Number
这表示, 定义一个叫温度的类型, 有两个构造子, 分别是摄氏度和华氏度, 他们都需要输入一个数字.
我们可以写一个, 判断温度是否大于0摄氏度的函数(华氏度32度是摄氏度0度):
isAboveZero :: WD -> Boolean
isAboveZero (CC a) = if a > 0.0 then true else false
isAboveZero (FF a) = if a > 32.0 then true else false
这样调用
xx1 :: Boolean
xx1 = isAboveZero (CC 10.0)
xx2 :: Boolean
xx2 = isAboveZero (FF 10.0)
* 这也是编程常用的模式, 即先通过定义类型来对事物建模, 然后写函数处理这种类型的值.
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
data WD
= CC Number
| FF Number
isAboveZero :: WD -> Boolean
isAboveZero (CC a) = if a > 0.0 then true else false
isAboveZero (FF a) = if a > 32.0 then true else false
xx1 :: Boolean
xx1 = isAboveZero (CC 10.0)
xx2 :: Boolean
xx2 = isAboveZero (FF 10.0)
main :: Effect Unit
main = log (show xx1)
有时候我们需要无限长度的类型, 例如数组, 里面输多少个数都是可以的, 普通的类型定义无法做到.
这里自己实现一个例子List
data List
= Nil
| Cons Int List
这是说, 定义一个叫List
的类型, 它有两种构造方法, Nil
和Cons
.
前者不需要参数, 后者需要输入一个Int
类型的值和一个List
类型的值.
注意到, 在定义List
的时候直接使用了List
(在Cons
构造子里). 这称为递归类型.
可以这样构造List
类型的值:
list1 :: List
list1 = Nil
list2 :: List
list2 = Cons 1 Nil
list3 :: List
list3 = Cons 1 (Cons 2 Nil)
注意到这种模式是可以无限继续下去的.
Nil
类似空数组[]
Cons 1 Nil
类似[1]
Cons 1 (Cons 2 Nil)
类似[1,2]
- ...
这样就完成了一个类似数组的结构.
同时可以写一个函数将List
转换为字符串
list2Str :: List -> String
list2Str Nil = ""
list2Str (Cons a as) = (show a) <> list2Str as
只需要用模式匹配将头数据和剩余数据分开, 将头数据转换为字符串, 同时递归调用剩余数据, 拼接两者.
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
data List
= Nil
| Cons Int List
list1 :: List
list1 = Nil
list2 :: List
list2 = Cons 1 Nil
list3 :: List
list3 = Cons 1 (Cons 2 Nil)
list2Str :: List -> String
list2Str Nil = ""
list2Str (Cons a as) = (show a) <> list2Str as
main :: Effect Unit
main = log (list2Str list3)
上面的例子里, List
里只能输入Int
类型的值.
我们可以构造一个允许输入任何类型值的List_G
.
data List_G a
= Nil_G
| Cons_G a (List_G a)
这表示定义一个叫List_G a
的类型, 其中a
是一个泛型, 会随着上下文确定.
而构造子Cons_G
则变成了, 输入一个a
类型的值, 和一个List_G a
类型的值.
可以通过类型签名指定泛型
list_g1 :: List_G String
list_g1 = Nil_G
这里, 指定了list_g1
的类型是List_G String
, 对比List_G a
可以发现, a的地方对应的是String
, 也就是说a
被指定为了String
.
因此, 如果要构造list_g1
的值的话, 可以使用Nil_G
或Cons_G String (List_G String)
.
这里我们先使用Nil_G.
在单纯的
Nil_G
中, 没有出现泛型a
, 那么它的类型依然是List_G a
,a
会在使用时被确定. 而在这里, 因为类型签名已经说明了,list_g1
的类型是List_G String
. 这里的值又是Nil_G
, 所以在这里Nil_G
类型中的a
被确定为了String
. 所以这里Nil_G
可以作为List_G String
类型的值.
而如果使用Cons_G
list_g2 :: List_G String
list_g2 = Cons_G "a" Nil_G
因为a
被指定为了String
, 因此输入的值就必须是String
和List String
.
这里输入了"a"
和Nil_G
. 上面说过, Nil_G
可以作为List_G String
类型的值.
于是可以顺着这个模式写下去
list_g3 :: List_G String
list_g3 = Cons_G "a" (Cons_G "b" Nil_G)
同时也可以构造其他类型的List_G
list_g4 :: List_G Int
list_g4 = Cons_G 1 (Cons_G 2 Nil_G)
同样的, 可以写转换为字符串的函数
list_g2Str :: List_G String -> String
list_g2Str Nil_G = ""
list_g2Str (Cons_G a as) = (show a) <> list_g2Str as
listTest1 :: String
listTest1 = list_g2Str list_g3
不过这个函数只能处理List_G String
, 能不能处理任意的List_G a
类型的值呢?
list_g2Str2 :: forall a. Show a => List_G a -> String
list_g2Str2 Nil_G = ""
list_g2Str2 (Cons_G a as) = (show a) <> list_g2Str2 as
listTest3 :: String
listTest3 = list_g2Str2 list_g3
listTest4 :: String
listTest4 = list_g2Str2 list_g4
这里使用了Show
类型类约束来约束a
必须能被转换为字符串.
关于类型类的问题之后会说到.
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
data List_G a
= Nil_G
| Cons_G a (List_G a)
list_g1 :: List_G String
list_g1 = Nil_G
list_g2 :: List_G String
list_g2 = Cons_G "a" Nil_G
list_g3 :: List_G String
list_g3 = Cons_G "a" (Cons_G "b" Nil_G)
list_g4 :: List_G Int
list_g4 = Cons_G 1 (Cons_G 2 Nil_G)
list_g2Str :: List_G String -> String
list_g2Str Nil_G = ""
list_g2Str (Cons_G a as) = (show a) <> list_g2Str as
listTest1 :: String
listTest1 = list_g2Str list_g3
list_g2Str2 :: forall a. Show a => List_G a -> String
list_g2Str2 Nil_G = ""
list_g2Str2 (Cons_G a as) = (show a) <> list_g2Str2 as
listTest3 :: String
listTest3 = list_g2Str2 list_g3
listTest4 :: String
listTest4 = list_g2Str2 list_g4
main :: Effect Unit
main = log listTest3