Swift関数式プログラミング一(関数式思想)


コードアドレス
前言
Swift関数式プログラムの特性:
  • モジュール化:関数プログラミングは、プログラムをより小さなモジュールユニットに繰り返し分解する傾向があり、これらのブロックは、関数によって組み立てられ、完全なプログラムを定義することができる.
  • 可変状態に対する慎重な処理:オブジェクト向けプログラミングはクラスとオブジェクトの設計に専念し、各クラスとオブジェクトには独自の組立状態がある.関数式プログラミングは値ベースプログラミングの重要性を強調し、可変状態や他の副作用の干渉を免れることができます.可変状態を避けることで,関数式プログラムは対応する命令式やオブジェクト向けプログラムよりも組み合わせやすい.
  • タイプ:適切なデータと関数のタイプは、他のものよりも重要なコードの構築に役立ちます.Swiftには強力なタイプのシステムがあり、コードをより安全で丈夫に使用することができます.

  • 関数式プログラミングの基本思想
    プログラム状態や可変オブジェクトの使用を避けることは,プログラムの複雑さを低減する有効な方法の一つであり,これも関数式プログラミングの真髄である.関数プログラミングは、実行のプロセスではなく、実行の結果を強調します.単純で一定の機能を持つ一連の小さな関数を構築してから、これらの関数を組み立てて、完全な論理と複雑な演算を実現します.
    かんすうしき思想
    関数はSwiftでは等値(first-class-values)であり、つまり関数はパラメータとして他の関数に渡すこともできるし、他の関数の戻り値として与えることもできる.
    ケース:Battleship
    この例は一等関数を引き出した:戦艦類ゲームを記述するために実現しなければならない核心関数である.問題は,与えられた点が射程範囲内であるか否かを判断することに帰結する.
    まず2種類を定義し、Distance(距離を表す)、Position(位置を表す)
    typealias Distance = Double
    struct Position {
        var x: Distance
        var y: Distance
    }
    

    次にShip構造体を導入し、戦船を表す
    struct Ship {
        ///   
        var position: Position
        
        ///     
        var firingRange: Distance
        
        ///      
        var unsafeRange: Distance
    }
    

    次にShipタイプを拡張し、canEnageShip関数を追加して、別の船が射程範囲内にあるかどうかを確認します.
    extension Ship {
        func canEnageShip(target: Ship) -> Bool {
            let dX: Distance = target.position.x - position.x
            let dY: Distance = target.position.y - position.y
            return sqrt(dX*dX + dY*dY) <= firingRange
        }
    }
    

    さらにinUnsafeRange関数を拡張して、別の船が安全ではないかどうかを判断します.
        func inUnsafeRange(target: Ship) -> Bool {
            let dX: Distance = target.position.x - position.x
            let dY: Distance = target.position.y - position.y
            return sqrt(dX*dX + dY*dY) <= unsafeRange
            
            return position.minus(tagert: target.position).length() <= unsafeRange
        }
    

    ゲームでは安全な出力が必要なので、canSafelyEngageShip関数を拡張して、別の船が射程範囲内で安全でない範囲外であるかどうかを判断します.
        func canSafelyEngageShip(target: Ship) -> Bool {
            return canEnageShip(target: target) && (!inUnsafeRange(target: target))
        }
    

    コードの発行に伴い、canEnageShip、inUnsafeRangeには複雑な計算コードが含まれており、Positionでいくつかの補助関数を拡張してジオメトリ演算を専門に担当することができ、これらのコードをより明確に理解することができます.再修正後のコードは次のとおりです.
    extension Position {
        func minus(tagert: Position) -> Position {
            return Position(x: x - tagert.x, y: y - tagert.y)
        }
        func length() -> Distance {
            return sqrt(x*x + y*y)
        }
    }
    
    extension Ship {
        func canEnageShip(target: Ship) -> Bool {
            return position.minus(tagert: target.position).length() <= firingRange
        }
        
        func inUnsafeRange(target: Ship) -> Bool {
            return position.minus(tagert: target.position).length() <= unsafeRange
        }
        
        func canSafelyEngageShip(target: Ship) -> Bool {
            return canEnageShip(target: target) && (!inUnsafeRange(target: target))
        }
    }
    

    いっとうかんすう
    現在の一連の関数では,主な挙動は戻り値を構成するブール条件の組合せを符号化することである.この例では関数が何をしたかは複雑ではないが,よりモジュール化された解決策がある.
    問題は、ポイントが特定の範囲内にあるかどうかを判定する関数を定義することです.このような関数タイプは、function pointInRegion(point:Poosition)->Boolです.この関数のタイプは非常に重要で、独立した名前を与えます.
    typealias Regin = (Position) -> Bool
    

    今からRegionはPositionをBoolに変換する関数を指し、言い換えれば与えられた点が範囲内であるか否かを判定できる関数で一つの領域を表す.
    構造体やオブジェクトではなく関数で領域を表すと、関数式プログラミングの核心理念関数は値であり、構造体、Bool値と変わらない.
    以上、Regionを使用して領域を定義できます.
    ///         radius  
    ///
    /// - Parameter radius:   
    /// - Returns:     
    func circle(radius:Distance) -> Region {
        return { point in point.length() <= radius }
    }
    

    しかし、すべての円の中心が原点にあるわけではありません.パラメータを増やすことで解決できます.
    ///    center   radius  
    ///
    /// - Parameters:
    ///   - center:   
    ///   - radius:   
    /// - Returns:     
    func circle1(center: Position, radius: Distance) -> Region {
        return { point in center.minus(tagert: point).length() <= radius }
    }
    

    しかし、円だけでなく、矩形や他の図形も同じように、より多くのグラフィックコンポーネントを変更したいと思っています.この場合、より関数的な領域変換関数が必要です.この関数は、一定のオフセット量で領域を移動します.
    ///     
    ///
    /// - Parameters:
    ///   - region:   
    ///   - offset:    
    /// - Returns:       
    func shift(region: @escaping Region, offset: Position) -> Region {
        return { point in region(point.plus(tagert: offset)) }
    }
    

    ここでは、circle 1のような複雑な関数を作成することを避け、円などの基礎的なグラフィックコンポーネントを作成し、これらのコンポーネントを基礎として一連の関数(shiftのような変換関数を構築して別の関数を変更する)を構築することで、小型関数を組み立てることができます.いろいろな問題を幅広く解決する.たとえば、次のように円を表します.
    let circle2 = shift(region: circle(radius: 5), offset: Position(x: 5, y: 5))
    

    次に、領域を制御、変換、結合するための関数をさらに記述することもできます.
    ///     
    ///
    /// - Parameter region:   
    /// - Returns:       
    func invert(region: @escaping Region) -> Region {
        return { point in !region(point) }
    }
    
    ///     
    ///
    /// - Parameters:
    ///   - region1:   1
    ///   - region2:   2
    /// - Returns:     
    func intersection(region1: @escaping Region, region2: @escaping Region) -> Region {
        return { point in region1(point) && region2(point) }
    }
    
    ///     
    ///
    /// - Parameters:
    ///   - region1:   1
    ///   - region2:   2
    /// - Returns:     
    func union(region1: @escaping Region, region2: @escaping Region) -> Region {
        return { point in region1(point) || region2(point) }
    }
    
    ///    (                )
    ///
    /// - Parameters:
    ///   - region:    
    ///   - mimus:     
    /// - Returns:    
    func difference(region: @escaping Region, mimus: @escaping Region) -> Region {
        return { point in region(point) && (!mimus(point)) }
    }
    

    エリアに関する小型関数ライブラリの準備が完了し、戦船に戻る例は以下のように再構築されています.
    extension Ship {
        func canEnageShip(target: Ship) -> Bool {
            return shift(region: circle(radius: firingRange), offset: position)(target.position)
        }
        
        func inUnsafeRange(target: Ship) -> Bool {
            return shift(region: circle(radius: unsafeRange), offset: position)(target.position)
        }
        
        func canSafelyEngageShip(target: Ship) -> Bool {
            return difference(region: shift(region: circle(radius: firingRange), offset: position), mimus: shift(region: circle(radius: unsafeRange), offset: position))(target.position)
        }
    }
    

    さらに、別の船がある範囲内にあるかどうかを判断する関数を、一連の領域を表す関数に置き換えることができ、組み立てがより便利だと思います.
        ///     
        ///
        /// - Returns:   
        func enageRegion() -> Region { return shift(region: circle(radius: firingRange), offset: position) }
        
        ///      
        ///
        /// - Returns:   
        func unsafeRegion() -> Region { return shift(region: circle(radius: unsafeRange), offset: position) }
        
        ///       
        ///
        /// - Returns:   
        func safelyEngageReion() -> Region { return difference(region: enageRegion(), mimus: unsafeRegion()) }
    

    同意の問題に対してRegion関数を用いて再構成されたバージョンは,より明確な解決策である.このスキームはアセンブリ式であるため、後述のバージョンはより理解しやすい.
    しかしRegionタイプを単純タイプとして定義し,Position->Bool関数の別名としてこの方法にはそれ自体の欠点がある.単一の関数を含む構造体を定義できます.
    struct Region1 {
        let lookup: (Position) -> Bool
    }
    

    次に、Regionタイプを操作していた関数の代わりに、extensionsを使用して構造体の類似関数を定義します.これは、領域を反復する関数変換によって、以前のように領域をパラメータとして他の関数に渡すのではなく、必要な複雑な領域を得ることができる.
    struct Region1 {
        let lookup: (Position) -> Bool
    }
    extension Region1 {
        func shift(offset: Position) -> Region1 { return Region1(lookup: { point in self.lookup(point.plus(tagert: offset)) }) }
        
        func invert() -> Region1 { return Region1(lookup: { point in !self.lookup(point) }) }
        
        func intersection(other: Region1) -> Region1 { return Region1(lookup: { point in self.lookup(point) && other.lookup(point) }) }
        
        func union(other: Region1) -> Region1 { return Region1(lookup: { point in self.lookup(point) || other.lookup(point) }) }
        
        func difference(other: Region1) -> Region1 { return Region1(lookup: { point in self.lookup(point) && (!other.lookup(point)) }) }
    }
    
    func circle3(radius: Distance) -> Region1 { return Region1(lookup: { point in point.length() <= radius }) }
    
    extension Ship {
        func enageRegion1() -> Region1 { return circle3(radius: firingRange) }
        func unsafeRegion1() -> Region1 { return circle3(radius: unsafeRange) }
        func safelyEngageReion1() -> Region1 { return enageRegion1().difference(other: unsafeRegion1()) }
    }
    

    この方法には2つの利点があります.
  • より少ない括弧
  • この方式では、Xcodeの自動補完は、組立が複雑な領域に非常に有用である
  • .
    もっと複雑な問題を増やして、もしこの時私が持っていたら戦艦ではなく、艦隊だったら.では、問題の処理は次のようになります.
    extension Ship {
        func allEnageRegion1(friends: Ship ...) -> Region1 { return friends.reduce(enageRegion1(), { (region, friend) in region.union(other: friend.enageRegion1()) }) }
        func allUnsafeRegion1(friends: Ship ...) -> Region1 { return friends.reduce(unsafeRegion1(), { (region, friend) in region.union(other: friend.unsafeRegion1()) }) }
        func allSafelyEnageRegion1(friends: Ship ...) -> Region1 { return friends.reduce(safelyEnageReion1(), { (region, friend) in region.union(other: friend.safelyEnageReion1()) }) }
    }
    

    注意事項
    説明に値するのは、前にどのように構築されたのかということです.より小さな領域で構成されているわけでも単純な図形でもないが、一つの点が領域内にあるかどうかを検証することしかできない.これらの領域をイメージ化するには、十分な点をサンプリングしてビットマップを生成するしかない.
    まとめ
    関数式プログラミングは、関数をパラメータとしてより大きなプログラムに正規化することができる.前の例から見ると,各関数は単独では強くないが,組み立てたときに非常に複雑な領域を記述することができ,解決策は簡単で優雅である.これは単純に関数を分割する方法とは全く異なる.ここで領域をどのように定義するかが重要であり、他のすべての定義は自然で、水が水路に届く.
    ヒント
    慎重にタイプを選ぶことが大切で、開発の流れを左右します.