LoginSignup
5
6

More than 5 years have passed since last update.

Swiftエンジニアの高階関数実践

Last updated at Posted at 2017-02-26

はじめに

純粋関数型プログラミング言語であるHaskellを参考に、以下の3つの構成で高階関数についてまとめていきます。

今回は、標準ライブラリで扱うことができる代表的な高階関数をいくつかまとめています。

高階関数

まずは、関数を引数で受け取る簡単な例を示します。以下のapplyTwiceは、受け取った関数を2回適用する関数です

applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

関数を引数にする場合はかっこで囲む必要があります。上の例では一つの引数を取り、同じ型の値を返す関数を引数として受け取っています。
引数である関数をxに適用し、再びfを適用しています。動作例を以下に示します。

Prelude> applyTwice (+3) 10
16
Prelude> applyTwice (3:) [2]
[3, 3, 2]

セクション1を使った部分適用を使っています。とても単純な例ですが、汎用性が高い関数ができてるのがわかるかと思います。

1 中間関数に関しても部分適用することができ、これを中間関数をセクションするといいます。中間関数をセクションするには(+3)のように片側だけに値を置いて括弧で囲みます。

map

map関数はリストに対して、引数の関数をすべての要素に適用し、新しい関数を返す関数です。実例を見るとわかりやすいので、定義を以下に示します。

map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs

第一引数は、a型の値を受け取りb型の値を返す関数となっています。そのため、第二引数のリストはa型の要素の必要があります。二行目のパターンマッチは、リストが空だった場合は空のリストを返すという意味です。_はこの値を使用しない時に使う予約語で、使い捨ての変数を表しています。
(x:xs)はリストの最初の要素とその他の要素に分けるという意味で、最初の要素に関数を適用しその他の要素に対して再帰的にmapを適用しています。

Prelude> map (+2) [1, 2, 3, 4]
[3, 4, 5, 6]
Prelude> map (replicate 3) [1..3]
[[1,1,1],[2,2,2][3,3,3]]

このように、すべての要素に関数が適用されています。

zipWith

zipWithは、二つのリストの各要素に関数を適用し1つのリストに結合する関数です。
こちらも実例を見るとわかりやすいので定義を以下に示します。

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys

第一引数の関数はa型とb型の値からc型の値を返す関数となっています。そのため、第2引数のリストはa型の要素、第3引数のリストはb型の要素となっています。zipWith'2の使用例は以下のようになります。

Prelude> zipWith' (+3) 10
16
Prelude> zipWith' (3:) [2]
[3, 3, 2]

2 関数名の最後についてるアポストロフィ(')は、特別な意味を持たない関数名の一部として有効な文字です。慣習的に正格版の関数を表したり、少し変更したバージョンの関数に似た名前をつけるために利用されます。今回は標準ライブラリのzipWith関数を自作で定義しているために使用しています。

Swiftで高階プログラミング

Haskellでいくつか高階関数の例を示しましたが、今度はSwiftで高階関数を扱っていきたいと思います。
高階関数の良いところの一つとして、汎用性のある関数が定義できるところです。
まず高階関数をつかっていない例として、各要素を二倍にして返す関数を定義してみます。

func double(_ array: [Int]) -> [Int] { 
    var result: [Int] = [] 
    for x in array { 
        result.append( x * 2) 
    } 
    return result 
}

double([3, 4]) // [6, 8]

この関数はあまり良いものとは言えません。Intを3倍にしたい時や、Floatを扱いたい時などに変更が必要なためです。

これは高階関数とジェネリクスを使用することで解決できます。
標準ライブラリのmap関数を自作で定義してみます。

func map<Element, T>(_ array: [Element], transform: (Element) -> T) -> [T] {
    var result: [T] = []
    for x in array {
        result.append(transform(x))
    }
    return result
}

map([2, 3]) { $0 * 2 } // [4, 6]
map([8, 4]) { $0 / 2 } // [4, 2]
map(["Swit", "swif"]) { $0 + "t" } // ["Swift", "swift"]

標準ライブラリのmapと同じような挙動になっていますね。このように、高階関数を扱うことで汎用性がある関数を定義できます。

同様にfilterも高階関数をつかってこのような定義で実現できます。

func filter<Element>(_ array: [Element], includeElement: (Element) -> Bool) -> [Element] {
    var result: [Element] = []
    for x in array where includeElement(x) {
        result.append( x)
    }
    return result
}

filter([3, 4, 5]) { $0 > 3 } //[4, 5]

カリー関数

今度は少し難易度を上げて、引数と返り値に関数を扱うカリー関数を定義してみます。
ジェネリクスを用いて以下のように定義します。

func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
    return { x in
        { y in f(x, y) }
    }
}

ぱっと見、何ができるかわからないような関数ですね。
このカリー関数を使用して、要素をカウント分繰り返した配列を返してみます。

let repeateString = curry { (count: Int, element: String) -> [String] in
    Array(repeating: element, count: count)
}

let threeArray = repeateString(3)

threeArray("Swift") // ["Swift", "Swift", "Swift"]
threeArray("Haskell") // ["Haskell", "Haskell", "Haskell"]

Swiftでも関数の部分適用ができていますね。部分適用された関数を使い回すことができます。

参考文献

5
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6