Haskell使用数据类型

use*_*416 2 haskell

我正在阅读制作我们自己的类型和类型类来学习你的哈克尔.

代数数据类型介绍中我注意到:

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

surface :: Shape -> Float
surface (Rectangle (Point x1 y1) (Point x2 y2)) = abs (x2 - x1) * abs (y2 - y1) 
Run Code Online (Sandbox Code Playgroud)

surface (Rectangle (Point x1 y1) (Point x2 y2)),我们指出Rectangle的参数是Point类型.

但是,在递归数据结构部分中:

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
singleton :: a -> Tree a  
singleton x = Node x EmptyTree EmptyTree  

treeInsert :: (Ord a) => a -> Tree a -> Tree a  
treeInsert x EmptyTree = singleton x  
treeInsert x (Node a left right)   
    | x == a = Node x left right  
    | x < a  = Node a (treeInsert x left) right  
    | x > a  = Node a left (treeInsert x right)
Run Code Online (Sandbox Code Playgroud)

我们不标明left的和right的数据类型Tree atreeInsert x (Node a left right).编译器如何知道它们的类型?

luq*_*qui 13

我觉得你有一个误解:

surface (Rectangle (Point x1 y1) (Point x2 y2)),我们指出Rectangle的参数类型Point.

这确实表明参数属于point类型,但可能与您的想法不同." Point"in Point x1 y1不是一个类型 - 它是一个构造函数,其命名方式与它构造的类型相同.如果我们宣布Point

data Point = MakePoint Float Float
Run Code Online (Sandbox Code Playgroud)

然后你会说

surface (Rectangle (MakePoint x1 y1) (MakePoint x2 y2)) 
Run Code Online (Sandbox Code Playgroud)

为清楚起见,我将继续使用MakePoint构造函数和Point类型.合法的Haskell将它们命名为相同,因为编译器总是可以从上下文中判断,但人类有时会遇到更多麻烦.

在上下文中

surface (Rectangle (MakePoint x1 y1) (MakePoint x2 y2)) = ...
Run Code Online (Sandbox Code Playgroud)

我们知道子表达式MakePoint x1 y1的类型为Point2个不同的地方.一个是构造函数Rectangle具有类型

Rectangle :: Point -> Point -> Shape
Run Code Online (Sandbox Code Playgroud)

所以我们知道,无论它的参数必须是点(这是由外向内的类型推断,我们得到的来自其中它的使用范围内的事情型); 另一个是构造函数MakePoint有类型

MakePoint :: Float -> Float -> Point
Run Code Online (Sandbox Code Playgroud)

所以我们知道它MakePoint x1 y1代表一个类型的值Point(这是从里到外的类型推断,我们从其组件中获取表达式的类型).在某种程度上,编译器使用这两种方法并确保它们匹配.

但是,有时缺少这些信息中的一种或另一种,例如x1在我们的例子中.我们没有关于内部信息的内部信息x1(好吧,如果我们查看方程式的右侧,编译器也会这样做,但我们现在忽略它),我们所拥有的只是MakePoint构造函数的参数必须是Float的,所以我们知道x1必须是一个Float.这是编译器有效和推断的; 没有必要明确说明.

在这个Tree例子中,有一个令人困惑的命名正在进行(一旦你得到它,不再混淆并开始有用,但在开始时画出一个明确的区别是好的)所以我将重命名第一个参数的Node来自av:

treeInsert :: (Ord a) => a -> Tree a -> Tree a  
treeInsert x EmptyTree = singleton x  
treeInsert x (Node v left right)   
    | x == v = Node x left right  
    | x < v  = Node v (treeInsert x left) right  
    | x > v  = Node v left (treeInsert x right)
Run Code Online (Sandbox Code Playgroud)

同样的事情与发生的事情left,并right因为是与x1上面:没有由内而外的结构使用,但我们知道的是,Node构造函数采用a两个Tree aS,所以v 必须是类型的a,并且leftright 必须是类型Tree a.编译器从上下文中推断出这一点.