Skip to content

Latest commit

 

History

History
343 lines (231 loc) · 7.32 KB

03 类型.md

File metadata and controls

343 lines (231 loc) · 7.32 KB

类型

我们可以自己定义自己的类型来建模.

形式

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的类型, 它有两种构造方法, NilCons.

前者不需要参数, 后者需要输入一个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_GCons_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, 因此输入的值就必须是StringList 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