getter和setter并不是Haskell中出现的概念,而是很多面向对象语言中用来操作对象实例的一个语法。下面以JavaScript为例来介绍:

position = {x: 1, y: 2}
position.x
// 1

position.y = 3
// position == {x: 1, y: 3} 

简单地说,在大部分面向对象语言中,如果想取出一个对象x的某个属性p,只需要使用x.p即可,而如果希望更改某个对象的属性p,则直接对x.p赋值即可。有时候,你不希望直接暴露属性p,那么可以给对象添加两个方法x.getP和x.setP。通过调用这两个方法,就可以完成读取和更改的操作,这两个方法很多时候被称为getter和setter。

这条不成文的规定到了Haskell这里,就成了另外一个故事。我们在定义数据类型时,有下面这样的记录语法:

data Position = Position { positionX :: Double, positionY :: Double }

p = Position 1 2

positionX p
-- 1

p2 = p { positionY = 3 } 

-- Position 1 3

看起来还不错嘛,只是因为Haskell中绑定是不会改变的,所以p2创建了一个新的Position记录,这个记录的值是我们希望更新后的值。而在内存中,情况大约如下:

enter image description here

也就是说,因为所有绑定都不会改变,所以并不会浪费内存去复制p1和p2中相同的元素,p1和p2共享了相同的数据1 :: Double。刚刚记录的更新表达式是下面表达式的语法糖:

p2 = Position {
    positionX = positionX p
,   positionY = 3
}

这里我们新建了一个Position盒子,把盒子的内容指向对应的位置,这些都是模式匹配和构造函数的语义。我们可以把下面两个函数当作getter和setter:

positionX :: Position -> Double
positionX (Position x _) = x

setPositionX :: Double -> Position -> Position
setPositionX x' p = p { positionX = x' }

其中,positionX是记录语法提供的提取数据的方法。而setPositionX简单地把记录语法中更新数据的部分{ positionX = x' }做了一个绑定,这个函数接收一个Double类型的值x'作为新的横坐标,一个Position类型的值p作为原始坐标,然后返回一个新的Position类型的值,这是横坐标更新之后的值。这两个方法提供了操作横坐标的能力。

记录语法提供的getter和setter的解决方案在大部分情况下都可以满足要求,但是当数据结构变得愈加复杂时,问题就出现了。假定现在定义了一个新的数据类型Line,它用来表示直角坐标系上的一条线段,这条线段由起点lineStart和终点lineEnd共同确定:

data Line = Line { lineStart :: Position, lineEnd :: Position }

line1 = Line (Position 0 0) (Position 3 4)

现在希望将line1的终点纵坐标从4变到5,应该如何做呢?我们有下面几种选择:

-- 模式匹配
line2 = case line1 of Line p1 (Position x _) -> Line p1 (Position x 5)

-- 记录语法
line2 = line1 { lineEnd = (lineEnd line1) { positionY = 5 } }

-- getter和setter
line2 = setLineEnd (setPositionY 5 (lineEnd line1)) line1

在模式匹配中,我们需要手动匹配线段的起点p1并在重建线段时使用它。而在记录语法里,我们需要手动提取line1的终点lineEnd line1,才能让接下来的更新操作得以继续。显然,不管是模式匹配还是记录语法,都需要写出一堆辅助的表达式。而实际上,这些额外的绑定都不需要,这样添加绑定然后扔掉的行为既浪费时间,写起来又很麻烦。假如数据的层级变得更深,可以想象每次操作一个深层次数据时需要添加多少层级的辅助操作,我们必须有一个高效、优雅的解决方案来解决函数式编程中嵌套数据结构操作的问题。

评论

本文目前还没有评论……

我要评论

需要登录后才能发言
登录未成功,请修改提交。

× 451
× 1756
× 2435
× 933
× 1
× 1
× 1190
× 0
× 1
× 0
× 2
× 1
× 3
× 3
× 2750
× 817
× 1104