Swiftで学ぶ関数合成の基本:カスタム演算子で作る読みやすいコード
はじめに:関数とは何か
プログラマーは毎日のように関数と向き合っています。関数とは、ある入力値に対して必ず何らかの出力値が対応する、特別な種類の関係を表すものです。より一般的に言えば、関数とは「入力値を出力値へ対応付けるルール」だと言えます。
関数合成(Function Composition)とは
関数合成の基本的な考え方は、「ある関数を、別の関数の結果に対して適用する」というものです。つまり、複数の関数を1つの関数へと組み合わせる数学的な概念です。
数学的な基礎から理解する
まずは数学の概念とともに見ていきましょう。上の図では「f」と「g」という2つの関数が登場します。これらの関数は次のように表現できます。
f: A -> B
g: B -> C
この2つの関数を合成すると、「g ∘ f」(g アフター f と読みます)として表せます。
(g ∘ f): A -> C
(g ∘ f)(a) = g(f(a)) ※a は A の任意の要素
簡単な例で具体的に確認してみましょう。
f(a) = 2a + 3
g(a) = 3a + 5
(g ∘ f)(a) = g(f(a))
= 3(f(a)) + 5
= 3(2a + 3) + 5
= 6a + 14
この概念は数学だけのものではありません。プログラミング言語にも応用でき、そのような考え方を中心とする言語は「関数型プログラミング言語」と呼ばれます。この概念を理解すると、コードの可読性が向上し、他のプログラマーにとっても理解しやすいコードになります。
関数型プログラミング言語としてのSwift
嬉しいことに、Swiftも関数型プログラミングの特徴を備えた言語です。Swiftにおいて関数は非常に重要な役割を担っており、日々の開発で必ずと言っていいほど扱うことになります。Swiftの関数は値を返すことができ、その戻り値を別の関数の入力として利用できます。これはごく一般的なプログラミング手法です。
Swiftで関数合成を実装する
ここで、整数の配列があるとしましょう。そこから「重複のない偶数のみを取り出し、それぞれを2乗した配列」を得たいとします。通常は次のようなコードを書くことになるでしょう。
let numbers = [1, 2, 3, 4, 5, 6, 7, 8]
let result = Array(Set(numbers.filter { $0 % 2 == 0 }).map { $0 * $0 })
// [4, 16, 36, 64]
このコードは正しい結果を返しますが、処理が一行に詰め込まれており可読性は決して高くありません。また、呼び出し順序が直感と逆に感じられ、経験の浅いプログラマーには混乱を招く可能性があります。分析しづらいコードになってしまうのです。
そこで登場するのが関数合成です。ジェネリクス、クロージャ、中置演算子(infix operator)を組み合わせることで、関数合成を実現できます。
カスタム演算子「>>>」の定義
infix operator >>> : AdditionPrecedence
func >>> <T, U, V>(
left: @escaping (T) -> U,
right: @escaping (U) -> V
) -> (T) -> V {
return { right(left($0)) }
}
上記のコードで何が行われているのか、順番に見ていきましょう。
- カスタムの中置演算子「>>>」を宣言しています。結合規則は左結合で、優先順位は「+」演算子と同じに設定しています。
- 中置演算子と同じ名前の関数を宣言しています。この関数は T、U、V の3つのジェネリクスを使用し、2つのクロージャを引数として受け取ります。
- 左辺のパラメータはクロージャで、型 T の入力を受け取り、型 U の出力を返します。
- 右辺のパラメータもクロージャで、型 U の入力を受け取り、型 V の出力を返します。
- >>> 関数は、型 (T) -> V の関数(クロージャ)を返します。返されるクロージャは型 T の入力を受け取り、型 V の出力を返します。ここで、左辺パラメータの出力が右辺パラメータの入力になっています。
left : (T) -> U
right: (U) -> V
出力の型: (T) -> V
関数合成の数学的な表現を理解していれば、Swiftでの実装がそれとまったく同じ構造であることが分かるでしょう。
- 関数本体では、左辺パラメータの結果に対して右辺パラメータを適用した結果を返しています。
関数合成を使った実装例
同じ結果(重複のない偶数を2乗した配列)を、関数合成を使って得てみましょう。
let numbers = [1, 2, 3, 4, 5, 6, 7, 8]
let filterEvens = { (values: [Int]) -> [Int] in
values.filter { $0 % 2 == 0 }
}
let uniquify = { (values: [Int]) -> [Int] in
Array(Set(values))
}
let squareAll = { (values: [Int]) -> [Int] in
values.map { $0 * $0 }
}
let result = (filterEvens >>> uniquify >>> squareAll)(numbers)
// [4, 16, 36, 64]
これは同じ結果を返す関数のチェーンです。処理の順序が人間の思考の流れと一致しているため、可読性が大きく向上し、誰にとっても理解しやすいコードになっています。
まとめ
関数合成は、複数の小さな関数を組み合わせて1つの処理の流れを作る強力なテクニックです。Swiftではカスタム演算子とジェネリクスを組み合わせることで、数学的な概念をそのままコードに反映できます。処理を小さな関数に分割し、合成でつなげることで、保守性と可読性の高いコードを目指しましょう。
最後までお読みいただきありがとうございました!
-
CSS rgba()関数の使い方を解説!透明度付きの色指定方法と実例
rgba()関数とはCSSのrgba()関数は、赤(Red)、緑(Green)、青(Blue)の3原色に加えて、アルファ(Alpha)チャンネルによる透明度を含めたRGBAカラーバリューを定義するための関数です。アルファ値は色の不透明度を表し、0.0〜1.0の範囲の数値で指定します。1.0が完全に不透明な状態、0.0が完全に透明な状態を意味します。基本的な構文は以下のとおりです。rgba(red, green, blue, alpha)red / green / blue:各色成分を0〜255の整数、またはパーセンテージで指定します。alpha:不透明度を0.0(完全に透明)〜1.0(完全に不
-
C言語のfillpoly()関数とは?図形の塗りつぶし方法を解説
概要C言語のグラフィックス用ヘッダーファイル「graphics.h」には、三角形、四角形、五角形、六角形などの多角形(ポリゴン)を描画し、その内部を塗りつぶすための fillpoly() 関数が用意されています。この関数は、線のみを描く drawpoly() 関数と同じ引数を受け取る点が特徴です。構文void fillpoly( int number, int *polypoints );各引数の意味は以下の通りです。number:頂点の数を n とした場合、「n + 1」個の点の数を指定します。polypoints:「n × 2」個の整数からなる配列へのポインタです。整数は2つで1組となり、