Haskell教程:#6 类型和类

By George Z. August 6, 2021

类型和类(Types and Type Class)

Haskell 是一种函数式语言,它是严格类型化的,这意味着整个应用程序中使用的数据类型将在编译时为编译器所知。

内置类型类

在 Haskell 中,每个语句都被视为一个数学表达式,这个表达式的类别称为类型。 您可以说“类型”是编译时使用的表达式的数据类型。要了解有关类型的更多信息,我们将使用 “:t” 命令。 在泛型中,Type 可以被认为是一个值,而 Type Class 可以被认为是一组相似类型的类型。 在本章中,我们将了解不同的内置类型。

Int

Int 是表示 Integer 类型数据的类型类。 2147483647 到 -2147483647 范围内的每个整数都属于 Int 类型类。 在以下示例中,函数 fType() 将根据其定义的类型运行。

fType :: Int -> Int -> Int
fType x y = x*x + y*y
main = print (fType 2 4)

在这里,我们将函数 fType() 的类型设置为 int。 该函数采用两个 int 值并返回一个 int 值。 如果编译并执行这段代码,它将产生以下输出

sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
20

integer

Integer 可以被认为是 Int 的超集。 这个值不受任何数字的限制,因此整数可以是任何长度而没有任何限制。 为了看看 Int 和 Integer 类型的基本区别,让我们将上面的代码修改如下

fType :: Int -> Int -> Int
fType x y = x*x + y*y
main = print (fType 212124454 44545454454554545445454544545)

如果编译上面这段代码,会抛出下面的错误信息

main.hs:3:31: Warning:            
   Literal 44545454454554545445454544545 is out of the Int range -
   9223372036854775808..9223372036854775807
Linking main ...

发生此错误是因为我们的函数 fType() 需要一个 Int 类型值,而我们正在传递一些真正的大 Int 类型值。 为了避免这个错误,让我们用“Integer”修改类型“Int”并观察差异。

fType :: Integer -> Integer -> Integer
fType x y = x*x + y*y
main = print (fType 212124454 4454545445455454545445445454544545)

现在,它将产生以下输出

sh-4.3$ main
1984297512562793395882644631364297686099210302577374055141

Float

看看下面的一段代码。 它展示了 Float 类型在 Haskell 中是如何工作的

fType :: Float -> Float -> Float
fType x y = x*x + y*y
main = print (fType 2.5 3.8)

该函数将两个浮点值作为输入,并产生另一个浮点值作为输出。 当您编译并执行此代码时,它将产生以下输出

sh-4.3$ main
20.689999

Double

Double 是一个以双精度结尾的浮点数。 看看下面的例子

fType :: Double -> Double -> Double
fType x y = x*x + y*y
main = print (fType 2.56 3.81)

当你执行上面的一段代码时,它会产生下面的输出

sh-4.3$ main
21.0697

Bool

Bool 是布尔类型。 它可以是 True 或 False。 执行以下代码,了解 Bool 类型在 Haskell 中是如何工作的

main = do  
   let x = True

   if x == False
      then putStrLn "X matches with Bool Type"
   else putStrLn "X is not a Bool Type"

在这里,我们将变量“x”定义为 Bool 并将其与另一个布尔值进行比较以检查其原创性。 它将产生以下输出

sh-4.3$ main
X is not a Bool Type

Char

Char 代表字符。 单引号内的任何内容都被视为字符。 在下面的代码中,我们修改了之前的 fType() 函数以接受 Char 值并返回 Char 值作为输出。

fType :: Char-> Char
fType x = 'K'
main = do  
   let x = 'v'
   print (fType x)

上面这段代码将调用 fType() 函数,其 char 值为 ‘v’,但它返回另一个 char 值,即 ‘K’。 这是它的输出 -

sh-4.3$ main
'K'

请注意,我们不会显式使用这些类型,因为 Haskell 足够智能,可以在声明之前捕获类型。 在本教程的后续章节中,我们将看到不同的类型和 Type 类如何使 Haskell 成为一种强类型语言。

EQ Type Class

EQ 类型类是一个提供测试表达式相等性功能的接口。 任何想要检查表达式相等性的 Type 类都应该是这个 EQ Type Class 的一部分。上面提到的所有标准 Type 类都是这个 EQ 类的一部分。 每当我们使用上述任何类型检查任何相等性时,我们实际上是在调用 EQ 类型类。在以下示例中,我们使用“==”或“/=”操作在内部使用 EQ 类型。

main = do
   if 8 /= 8
      then putStrLn "The values are Equal"
   else putStrLn "The values are not Equal"

它将产生以下输出

sh-4.3$ main
The values are not Equal

Ord Type Class

Ord 是另一个接口类,它为我们提供了排序功能。 到目前为止我们使用的所有类型都是这个 Ord 接口的一部分。 与 EQ 接口一样,Ord 接口可以使用“>”、“<”、“<=”、“>=”、“比较”来调用。请在下面找到我们使用此类型类的“比较”功能的示例。

main = print (4 <= 2)

这里,Haskell 编译器会检查 4 是否小于或等于 2。如果不是,代码将产生以下输出

sh-4.3$ main
False

Show

Show 具有将其参数打印为字符串的功能。 无论它的参数是什么,它总是将结果打印为字符串。 在以下示例中,我们将使用此接口打印整个列表。 “show”可以用来调用这个接口。

main = 打印(显示 [1..10])

它将在控制台上产生以下输出。 这里,双引号表示它是一个 String 类型的值。

sh-4.3$ 主
“[1,2,3,4,5,6,7,8,9,10]”

Read

Read 接口和 Show 做同样的事情,但它不会以 String 格式打印结果。 在以下代码中,我们使用 read 接口读取字符串值并将其转换为 Int 值。

main = print (readInt "12")
readInt :: String -> Int
readInt = read

在这里,我们将一个字符串变量(“12”)传递给 readInt 方法,该方法在转换后返回 12(一个 Int 值)。 这是它的输出 -

sh-4.3$ main
12

Enum

Enum 是另一种类型的 Type 类,它启用 Haskell 中的顺序或有序功能。 这个 Type 类可以通过 Succ、Pred、Bool、Char 等命令访问。 以下代码显示了如何找到 12 的后继值。

main = print (succ 12)

它将产生以下输出

sh-4.3$ main
13

Bounded

所有有上限和下限的类型都属于这个类型类。 例如,Int 类型数据的最大边界为“9223372036854775807”,最小边界为“-9223372036854775808”。下面的代码展示了 Haskell 如何确定 Int 类型的最大和最小界限。

main = do
   print (maxBound :: Int)
   print (minBound :: Int)

它将产生以下输出

sh-4.3$ main
9223372036854775807
-9223372036854775808

Num

该类型类用于数值运算。 Int、Integer、Float 和 Double 等类型都属于这个 Type 类。 看看下面的代码

main = do
   print(2 :: Int)  
   print(2 :: Float)

它将产生以下输出

sh-4.3$ main
2
2.0

Integral

Integral 可以被认为是 Num Type Class 的一个子类。 Num Type 类包含所有类型的数字,而 Integral 类型类仅用于整数。 Int 和 Integer 是这个 Type 类下的类型。

Floating

与 Integral 一样,Floating 也是 Num Type 类的一部分,但它只保存浮点数。 因此, Float 和 Double 属于这个类型类。

Custom Type Class

与任何其他编程语言一样,Haskell 允许开发人员定义用户定义的类型。 在下面的示例中,我们将创建一个用户定义的类型并使用它。

data Area = Circle Float Float Float  
surface :: Area -> Float   
surface (Circle _ _ r) = pi * r ^ 2   
main = print (surface $ Circle 10 20 10 )

在这里,我们创建了一个名为 Area 的新类型。 接下来,我们使用这种类型来计算圆的面积。 在上面的例子中,“surface”是一个以 Area 作为输入并产生 Float 作为输出的函数。请记住,“数据”在这里是一个关键字,Haskell 中所有用户定义的类型总是以大写字母开头。它将产生以下输出

sh-4.3$ main
314.15927
comments powered by Disqus