Swift関数式プログラミング実践

13458 ワード

原文接続:a practical introduction to functional programming原文日付:2015/08/10
訳者:shanks校正:numbbbbb定稿:小鍋

紹介する


SwiftはiOSプログラミングの世界に新しいモデルを導入した:関数モデル.多くのiOS開発者は、Objective-Cや他のオブジェクト向けプログラミング言語に慣れており、関数式の符号化や思考が少し頭を焼くようになっています.そこから勉強を始めるべきですか.私はいくつかの非常に理解しやすい例を見つけました-Mary Rose Cookのblogの中で1篇のとても良い文章を見つけました:A practical introduction to functional programming、この文章はとても良くて、私の疑問を解くのに十分で、しかもこの文章は多くの例のコードを含んで、私达はこの基礎の上で関数式の補充を加えることができます.
私たちのこの文章はCookの例を見直し、Swiftで実現します.だから、この文章を読む前に、まず彼女の文章を読んでください.この文章は多くの例を作っただけでなく、初心者にとって、関数式プログラミングとは何かをはっきり説明しています.私はこれらの概念を繰り返しません.
プログラマーたちが関数式のプログラミングについて話しているとき、彼らは多くのくわえ天の「関数式」の特性に言及します.のしかし、これらの点を無視してください.関数コードは、副作用がないことを説明しています.現在の関数は、関数以外のデータに依存せず、現在の関数も関数以外のデータを変更しません.他のすべての関数プロパティは、このプロパティから拡張されます.この特性を関数式プログラミングを学ぶ指導思想としてください.
-Mary Rose Cook関数式プログラミングの経験を学ぶ方法について

ケース#1-Increment

/*  : 。 */

///   ///

var a = 0

func incrementUnfunctional() -> () {
    a += 1
}

incrementUnfunctional()
print(a) // a = 1

///   ///

a = 0

func incrementFunctional(num: Int) -> Int {
    return num + 1
}

a = incrementFunctional(a)
print(a) // a = 1

この2つの関数の違いは、変数a−関数incrementUnfunctionalをどのように増加させるかによってグローバル変数が変更され、関数incrementFunctionalは通常の関数であり、1つの数値を取得し、増加した数値を返す.これがCookが言及した「副作用なし」:関数incrementFunctionalが自身以外の変数に影響を及ぼさない状態である.

cookのレッスン1:リストでループを使用しないでmapとreduceを使用


関数プログラミングを深く研究するプログラマーは、mapreduceおよびfilter関数にすぐに接触します.これらの関数は、コレクションタイプを処理するのに非常に強力です.次に、mapおよびreduce関数を見てみましょう.

例2-Map 1

Map関数は、関数と集合を受信します.Mapは、新しい空の集合を生成し、入力された関数を使用して集合の各要素を処理し、戻り値を新しい集合に挿入し、最後にこの新しい集合を返します.
/*  ,  map   reduce。 */

// Map   #1

let languages = ["Objective-C", "Java", "Smalltalk"]

let languageLengths = languages.map { language in count(language) } 

print(languageLengths) // [11, 4, 9]

let squares = [0, 1, 2, 3, 4].map { x in x * x }

print(squares) // [0, 1, 4, 9, 16]

例が示すように、mapは確かに新しい集合を返します.この例では配列(array)です.元の集合の各要素を匿名関数で処理し、元の集合は変わらないままです.

例3-Map 2

/*  ,  map   reduce。 */

// Map   #2

var languages = ["Objective-C", "Java", "Smalltalk"]
let newLanguages = ["Swift", "Haskell", "Erlang"]

///   ///

for index in 0.. String {
    let randomIndex = randomPositiveNumberUpTo(array.count)
    return array[randomIndex]
}

func randomPositiveNumberUpTo(upperBound: Int) -> Int {
    return Int(arc4random_uniform(UInt32(uppderBound)))
}

ここでは,熟知した方法を用いてランダムなプログラミング言語配列を得る方法,および我々の関数式の補完実装を見ることができる.

例#4-Reduce 1


Reduce関数は、1つの関数と1つの集合を受信します.要素を結合して作成した値を返します.
/*  ,  map   reduce。 */

// Reduce   #1

let sum = [0, 1, 2, 3, 4].reduce(0, combine: { $0 + $1 })

print(sum) // 10
Reduce理解するのはmapより難しいです.Reduceは、初期値(上記の例では、初期値は0)から値の蓄積を開始し、各集合要素でcombine閉パケットを呼び出し、最後の結果を返す.
上記の例では、パラメータ名の略語$0$1を使用していますが、これはよくわかりません.以下は別の表現で、機能は同じです.
/*  , map reduce。 */

// Reduce   #1 -  

let numbers = [0, 1, 2, 3, 4]
let startingWith = 0

let sum = numbers.reduce(startingWith) {
    (runningSum, currentNumber) in
    
    runningSum + currentNumber
}

print(sum) // 10

0から、加算接続runningSumと数値セットの現在の値を使用して、最終的に合計を完了します.

例#5-Reduce 2

Reduceは、数値セットだけでなく、文字列セットをどのように処理するかを見てみましょう.次の関数は、「hello」という単語を含むフレーズがいくつあるかを示します.
/*  ,  map   reduce。 */

// Reduce   #2

let greetings = ["Hello, World", "Hello, Swift", "Later, Objective-C"]

///   ///

var helloCount = 0

for greeting in greetings {
    if(string(greeting, contains:"hello")) {
        helloCount += 1
    }
}

print(helloCount) // 2

///   ///

let helloCountFunctional = greetings.reduce(0, combine: { $0 + ((string($1, contains:"hello")) ? 1 : 0) })

print(helloCountFunctional) // 2


//  

func string(str: String, #contains: String) -> Bool {
    return str.lowercaseString.rangeOfString(contains.lowercaseString) != nil
}

Swiftノート:MapとReduce


Swift 1.2では、mapおよびreduceのような関数がSwiftライブラリでグローバル関数であるため、map([0, 1, 2, 3, 4], { x in x * x })を使用する必要があります.Swift 2は、上記の例で見たように、mapをコレクション上で直接呼び出すことができるより直感的な構文を提供します.この2つの方法の機能は同じです!

Cookのレッスン2-宣言式(Imperative)を使用してプログラミングし、コマンド式(Declarative)を使用しない


関数バージョンのコマンドコードは宣言されます.このようなコードは、どのようにするかではなく、何をするかを説明しています.のコードをいくつかの関数にパッケージ化すると、コードの宣言の性質が向上します.
Objective-C開発者はコマンドプログラミングに慣れています.これはプログラミングのモデルで、一連の文がステータスを変更するために使用されています.関数プログラミングは宣言プログラミングの一種であり、関数プログラミングの特徴は関数を使用して何をするかを記述することである.

例#6-Imperative 1


Cookの例を見てみましょう.次のコードは3つの自動車の競争をシミュレートしています.
/***  ,  ***/

// Imperative vs. Declarative -   #1

/// Imperative -   ///

var time = 5
var carPositions = [1, 1, 1]

while(time > 0) {
    time -= 1
    
    print("
") for index in 0.. 3) { carPositions[index] += 1 } for _ in 0..

例#7-Imperative 2


経験のあるObjective-C開発者は、上のコードを見て、上のコードをより小さなセグメントに分解すべきだとすぐに気づきます.
/***  ,  ***/

// Imperative vs. Declarative -   #2

/// Imperative -   ///

var time = 5
var carPositions = [1, 1, 1]

while(time > 0) {
    runStepOfRace()
    draw()
}

// Helpers

func runStepOfRace() -> () {
    time -= 1
    moveCars()
}

func draw() {
    print("
") for carPosition in carPositions { drawCar(carPosition) } } func moveCars() -> () { for index in 0.. 3) { carPositions[index] += 1 } } } func drawCar(carPosition: Int) -> () { for _ in 0..

コードはより簡潔になりましたが、依然として関数式ではありません.各関数はCookが教えてくれた関数式に従って実現されていません.「[関数は現在の関数以外のデータに依存することができず、現在の関数以外のデータを変更することはできません」.

例#8-Declarative


以下は関数実装バージョンです.
/***  ,  ***/

// Imperative vs. Declarative -   #3

///   ///

typealias Time = Int
typealias Positions = [Int]
typealias State = (time: Time, positions: Positions)

let state: State = (time: 5, positions: [1, 1, 1])
race(state)

//  

func race(state: State) -> () {
    draw(state)
    
    if(state.time > 1) {
        print("

") race(runStepOfRace(state)) } } func draw(state: State) -> () { let outputs = state.positions.map { position in outputCar(position) } print(join("
", outputs)) } func runStepOfRace(state: State) -> State { let newTime = state.time - 1 let newPositions = moveCars(state.positions) return (newTime, newPositions) } func outputCar(carPosition: Int) -> String { let output = (0.. [Int] { return positions.map { position in (randomPositiveNumberUpTo(10) > 3) ? position + 1 : position } }

わあ、コードがたくさんありますね.次の例の各関数をよく検討することをお勧めします.外部データに依存したり、内部データを変更したりしない関数がほしいことに気づきます.
例のもう1つの清潔さを保つ詳細は、typealiasキーワードを介してbandsキーワードを使用することで、コードを書くときにより自然になります.個人的に関数式プログラミングが好きな理由の一つは、コードで使用されるタイプと、関数でこれらのタイプをどのように操作するかをよく考えさせることです.

cookのレッスン3-パイプを使用します。


前の章では、いくつかのコマンド式のループが補助関数を呼び出すループに書き換えられています.この章では、別のコマンド式のループは、パイプという技術を使用して書き換えられます.

例#9-パイプを使用しない


まず、データのセットを変換する典型的な例を示します.この例では、各要素がnameおよびcountryを含むbandsの配列がある.このcountry集合を2回変換したい:1、Canadaname2に設定し、inoutを大文字に変更します.以下は私たちが初めて実現したものです.
/***   ***/

///   ///

var bands: [ [String : String] ] = [
    ["name" : "sunset rubdown", "country" : "UK"],
    ["name" : "women", "country" : "Germany"],
    ["name" : "a silver mt. zion", "country" : "Spain"]
]

func formatBands(inout bands: [ [String : String] ]) -> () {
    var newBands: [ [String : String] ] = []
    
    for band in bands {
        var newBand: [String : String] = band
        newBand["country"] = "Canada"
        newBand["name"] = newBand["name"]!.capitalizedString
        
        newBands.append(newBand)
    }
    
    bands = newBands
}

formatBands(&bands)
print(bands) // [[country: Canada, name: Sunset Rubdown], [country: Canada, name: Women], [country: Canada, name: A Silver Mt. Zion]]
formatBandsキーワードを使用すると、関数プログラミングを使用していないことがわかります.
以下は、データ変換を同様に実現し、すべてのコードをprint(formattedBands(bands, [setCanadaAsCountry, capitalizeName]))関数に詰め込むことなく、より表現力と柔軟性を有する別の実装方法である.canada
私たちはどのように実現すべきですか?

例#10-関数パイプ

/***   ***/

/// Functional - Example #1 ///

let bands: [ [String : String] ] = [
    ["name" : "sunset rubdown", "country" : "UK"],
    ["name" : "women", "country" : "Germany"],
    ["name" : "a silver mt. zion", "country" : "Spain"]
]

typealias BandProperty = String
typealias Band = [String : BandProperty]
typealias BandTransform = Band -> Band
typealias BandPropertyTransform = BandProperty -> BandProperty

let canada: BandPropertyTransform = { _ in return "Canada" }
let capitalize: BandPropertyTransform = { return $0.capitalizedString }

let setCanadaAsCountry: BandTransform = call(function: canada, onValueForKey: "country")
let capitalizeName: BandTransform = call(function: capitalize, onValueForKey: "name")

func formattedBands(bands: [Band], functions: [BandTransform]) -> [Band] {
    return bands.map {
        band in
        
        functions.reduce(band) {
            (currentBand, function) in
            
            function(currentBand)
        }
    }
}

print(formattedBands(bands, [setCanadaAsCountry, capitalizeName])) // [[country: Canada, name: Sunset Rubdown], [country: Canada, name: Women], [country: Canada, name: A Silver Mt. Zion]]

//  

func call(#function: BandPropertyTransform, onValueForKey key: String) -> BandTransform {
    return {
        band in
        
        var newBand = band
        newBand[key] = function(band[key]!)
        return newBand
    }
}
capitalizeおよびBandProperty関数に注意してください.彼らはBandProperty(文字列)を受け入れてsetCountryAsCanada(文字列)を返すだけです.capitalizeNameおよびBandPropertyTransform関数の表示に注意してください.彼らは単純にcanada関数(例えばcapitalizeおよびBand)を受信し、formattedBands(辞書)のキー値(ここではそれぞれ「country」および「name」)に適用する.
上記のすべての関数を使用すると、それらの役割を独立して考えることができます.入力されたBandTransform配列を処理するために、bandsの関数が最後に呼び出される.私たちは任意の数の変換を書くことができて、それからそれらをformattedBandsに伝えて、同時に他の関数を維持します--これはとても強い東です!

課外コンテンツ-関数の組合せ


ここを見たら、別の合併変換の方法を見てみましょう.Cookの文章にはこの方法が紹介されていません.
この方法のインスピレーションは私が一番好きなSwiftに関する図書:Functional Programming in Swiftから来て、作者はobjcから来ました.ioのChris Eidhof、Florian Kugler、Wouter Swierstra.この本の章では、関数の構築方法について説明しています.この概念を前のコードに使用することができます.

例#11-関数の組合せ

/***   ***/

///   -   #2 ///

let canada: BandPropertyTransform = { _ in return "Canada" }
let capitalize: BandPropertyTransform = { return $0.capitalizedString }

let setCanadaAsCountry: BandTransform = call(function: canada, onValueForKey: "country")
let capitalizeName: BandTransform = call(function: capitalize, onValueForKey: "name")

let myBandTransform = composeBandTransforms(setCanadaAsCountry, capitalizeName)
let formattedBands = bands.map { band in myBandTransform(band) }

print(formattedBands) // [[country: Canada, name: Sunset Rubdown], [country: Canada, name: Women], [country: Canada, name: A Silver Mt. Zion]]


//  

func call(#function: BandPropertyTransform, onValueForKey key: String) -> BandTransform {
    return {
        band in
        
        var newBand = band
        newBand[key] = function(band[key]!)
        return newBand
    }
}

func composeBandTransforms(transform1: BandTransform, transform2: BandTransform) -> BandTransform {
    return {
        band in
        
        transform2(transform1(band))
    }
}

これは以前のコードと似ているように見えますが、関数構築の概念を使用して私たちの変換を構築し、Cookのmapreduceスキームを置き換えます.

結論


この文章はCookのすばらしい総括で終わることができます.
関数コードは他のスタイルのコードとよく共存することができます......リストのループをmapreduceに変えます.この点は車両レースの例を参考にしてください.コードを関数に分割し、これらの関数をより関数的にします.一つの過程の循環を再帰に変える.この点はbandsの例を参照してください.一連の操作をパイプに変える.
Swiftは純粋な関数言語ではありません.あなたの関数コードは非関数コードとよく共存することができます.
この文章の重点は、既存のコードを関数スタイルに変換する方法を教え、関数プログラミングの威力を見せることです.
本文のすべての例のコードはGists(原文の例のGistsリンクをクリックしてください)に置かれ、GitHubにも置かれています.https://github.com/hkellaway/swift-functional-intro
コードハッピー!