函数式Swift1-函数式思想

本文是一个系列,是函数式Swift的读书笔记(其实是为了备忘)

函数在 Swift 中是一等值 (first-class-values),换句话说,函数可以作为参数被传递到其它函数,也可以作为其它函数的返回值

案例:BattleShip

这个例子是 判断一个给定的点是否在射程范围内,并且距离友方船舶和我们自身都不太近

1,非函数式方式:(我之前一直用这种方式编程的)
typealias Distance = Double

struct Position{
    var x: Double
    var y: Double
}

 //1.假设船舶在原点
extension Position{
    func withIn(range:Distance) -> Bool {
        return sqrt(x*x + y*y) <= range
    }
}

// 考虑到船舶有位置, 需要一个ship
struct Ship {
    var position : Position
    var firingRange :Distance
    var unsafeRange : Distance
}

//2.只考虑是否在敌船范围内。
extension Ship{
    func canEngage(ship target:Ship) -> Bool {
        let dx = target.position.x - position.x
        let dy = target.position.y - position.y
        let targetDistance = sqrt(dx*dx + dy*dy)
        return targetDistance <= firingRange
    }
}

//3.避免与过近的敌船交战
extension Ship{
    func canSagelyEngage(ship target:Ship) -> Bool {
        let dx = target.position.x - position.x
        let dy = target.position.y - position.y
        let targetDistance = sqrt(dx * dx + dy * dy)
        return targetDistance <= firingRange && targetDistance > unsafeRange
    }
}

//4.避免目标船舶与友方船舶靠的过近
extension Ship {
    func canSafelyEngage(ship target: Ship, friendly: Ship) -> Bool {
        let dx = target.position.x - position.x
        let dy = target.position.y - position.y
        let targetDistance = sqrt(dx * dx + dy * dy)
        let friendlyDx = friendly.position.x - target.position.x
        let friendlyDy = friendly.position.y - target.position.y
        let friendlyDistance = sqrt(friendlyDx * friendlyDx +
            friendlyDy * friendlyDy)
        return targetDistance <= firingRange
            && targetDistance > unsafeRange
            && (friendlyDistance > unsafeRange)
    }
}

// 写一个辅助方法和一个计算属性负责几何运算,从而让代码清晰一些
extension Position{
    func minus(_ p: Position) -> Position {
        return Position(x: x-p.x,y:y-p.y)
    }
    var length:Double{
        return sqrt(x*x + y*y)
    }
}
//5.添加了辅助方法以后,最终代码变为
extension Ship {
    func canSafelyEngage2(ship target: Ship, friendly: Ship) -> Bool {
        let targetDistance = target.position.minus(position).length
        let friendlyDistance = friendly.position.minus(target.position).length
        return targetDistance <= firingRange
            && targetDistance > unsafeRange
            && (friendlyDistance > unsafeRange)
    }
}
2.函数式思维的方式

在当前 canSafelyEngage(ship:friendly) 的方法中,主要的行为是 为构成返回值的布尔条件 组合 进行编码

原来的问题归根结底是要 定义一个函数 来判断一个点是否在范围内。

func positionInRange(position:Position)-Bool{
// 判断点是否在范围内
}

可以用一个独立的名词来命名该函数类型

typealias Rangin = (Position)->Bool

Region 类型将指代把 Position 转化为 Bool 的函数。它可以让我们更容易理解在接下来即将看到的一些类型

我们有意识地选择了 Region 作为这个类型的名字,而非 CheckInRegion 或 RegionBlock 这种字里行间暗示着它们代表一种函数类型的名字。函数式编程的核心理念就是函数是值,它和结构体、整型或是布尔型没有什么区别

定义一个以原点为圆心的圆。返回一个函数,以半径 r 为参数,调用 circle(radius: r) 返回的是一个函数。这里我们使用了 Swift 的闭包来构造我们期待的返回函数。

func circle(radius:Distance) -> Region {
    return {
        point in
        point.length <= radius
    }
}

要得到一个圆心是任意定点的圆,我们只需要添加另一个代表圆心的参数,并确保在计算新区域时将这个参数考虑进去

func circle2(radius:Distance,center:Position) -> Region {
    return {
        point in
        point.minus(center).length <= radius
    }
}

// 如果我么你想要更多的图形组件,我们于鏊重复这些代码,更加函数式的方式是写一个区域变换函数,这个函数按一定的偏移量移动一个区域:

func  shift(_ region: @escaping Region,by offSet:Position)->Region{
    return {
        point in
        region(point.minus(offSet))
    }
}

// 调用 shift(region, by: offset) 函数会将区域向右上方移动,偏移量分别是 offset.x 和 offset.y。我们需要的是一个传入 Position 并返回 Bool 的函数 Region。为此,我们需要另写一个闭包,它接受我们要检验的点,这个点减去偏移量之后我们得到一个新的点。最后,为了检验新点是否在原来的区域内,我们将它作为参数传递给 region 函数。

//这是函数式编程的核心概念之一:为了避免创建像 circle2 这样越来越复杂的函数,我们编写了一个 shift(_:by:) 函数来改变另一个函数。例如,写一个圆心为(5,5),半径为10的圆, 可以用下边的方式

let shifted = shift(circle(radius:10),by:Position(x:5,y:5))

//还有很多类似的方法,比如反转一个区域以定义另外一个区域

func invert(_ region:@escaping Region)->Region{
    return{!region($0)}
}

// 一个区域的交集和并集

func intersect(_ region:@escaping Region,with other:@escaping Region) -> Region {
    return{
        region($0)&&other($0)
    }
}
func union(_ region:@escaping Region,with other:@escaping Region)->Region{
    return {
        region($0)||other($0)
    }
}

// 然后,可以利用上边的函数,生成新的region函数

func subtract(_ region:@escaping Region,from original:@escaping Region)->Region{
    return  intersect(original, with: invert(region))
}

//解析:这里其实就是,传入某个region,按照某种规则,生成另外一个region。

3. 函数式的实现方式

函数库已经写完了,可以重写那个复杂的方法了:

extension Ship{
    func  canSafelyEngage(ship target: Ship, friendlyShip:Ship) -> Bool {
     // 我们与敌人的范围交集
        let rangeRegion = subtract(circle(radius: unsafeRange), from:circle(radius: firingRange))
      //根据我们的位置,偏移一下。
        let firingRegion = shift(rangeRegion, by: position)
       // 友船的范围
        let friendlyRengion = shift(circle(radius: unsafeRange), by: friendlyShip.position)
        
      //最终的交集范围 
        let resultRegion = subtract(friendlyRengion, from: firingRegion)
    // 判断
        return resultRegion(target.position)
    }
}

//我想吐槽,有点麻烦。
//与原来的 canSafelyEngage(ship:friendly:) 方法相比,使用 Region 方法重构后的版本是更加声明式的解决方案。

4. 优化

Region 类型的方法有它自身的缺点。我们选择了将 Region 类型定义为简单类型,并作为 (Position) -> Bool 函数的别名。其实,我们也可以选择将其定义为一个包含单一函数的结构体”

struct Region{
let lookup: Position->Bool
}

//接下来我们可以用 extensions 的方式为结构体定义一些类似的方法,来代替对原来的 Region 类型进行操作的自由函数,“这可以让我们能够通过对区域进行反复的函数调用来变换这个区域,直至得到需要的复杂区域,而不用像以前那样将区域作为参数传递给其他函数

//这种方法有一个优点,它需要的括号更少。再者,这种方式下 Xcode 的自动补全在装配复杂的区域时会十分有用
rangeRegion.shift(ownPosition).difference(friendlyRegion)
4.类型驱动开发。

我们定义了一系列函数来描述区域。每一个函数单打独斗的话都并不强大。然而装配到一起时,却可以描述你绝不会想要从零开始编写的复杂区域。

这与单纯地将 canSafelyEngage(ship:friendly:) 方法拆分为几个独立的方法那种重构方式是完全不同的。 我们确定了 如何来定义区域 ,这是至关重要的设计决策。当我们选择了 Region 类型之后,其它所有的定义就都自然而然,水到渠成了。

这个例子给我们的启示是,我们应该 谨慎地选择类型 。这比其他任何事都重要,因为类型将左右开发流程。

//概念性的东西比较多,所以文字比较多,感觉不是总结,而是复制粘贴书。但是为了易懂,还是觉得这样。

你可能感兴趣的:(函数式Swift1-函数式思想)