オブジェクト指向プログラミングと関数型プログラミングの違い:手法、コード例、ユースケースごとに解説
関数型プログラミングモデルの採用を考える開発者は多い。だが、採用するなら、関数型プログラミングモデルとオブジェクト指向のアプローチがどのように異なるかを正確に理解することが重要だ。
プログラミングのパラダイムを決めることは、どのようなアプリケーション開発作業にとっても重要なステップの1つだ。関数型プログラミングとオブジェクト指向プログラミングのどちらを選ぶかは、この2つしか選択肢がないわけではないとしても、今日の多くの開発者が直面する課題の1つになっている。
本稿では、関数型プログラミングとオブジェクト指向プログラミングの主な違いを復習し、両コーディングパラダイムが機能する仕組みを幾つか示し、いずれかを選択する際に最も重要な考慮点を確認する。
オブジェクト指向プログラミングと関数型プログラミングの手法
基本的には、関数型のプログラムは、一般的な数学関数(温度をセ氏からカ氏に変換する計算など)のように動作する。関数は、同じ入力に対しては一貫して同じ結果を返す。「純粋な」関数は決定論的で、副作用は起きない。つまり、呼び出されれば常に同じ値を返す。関数のスコープやパラメーター以外は一切変更されない。
関数型の手法の一例に、Googleによる「MapReduce」の実装がある。そのアプローチでは特定の検索条件に対して結果が返される。MapReduceは、「reduce」という関数を使ってキーと値のペアの形式で検索条件を結び付ける。結び付けの過程では、検索条件が集約され、条件一つ一つに値が割り当てられる。この値が返すべき結果のタイプを示すことになる。データセットが同じであれば、Googleの検索では副作用なしに毎回同じ検索結果が返される。
一方、オブジェクト指向プログラミングでは、状態に応じて値が変わる変数が含まれる可能性がある。つまり、オブジェクトは必ずしも一定の値を保持するとは限らない。例えば、平均給与額を返すよう設計されたメソッドがあり、現在の給与総額で5万ドルを返すとする。開発者が給与総額を10%増やしてこのメソッドを実行し再度平均給与額を求めると、このメソッドは5万5000ドルを返すことになる。オブジェクト指向プログラミングではグローバル変数や静的変数を含めることもできる。こうした変数を使うことで毎回異なる応答を返すことが可能になる。
オブジェクト指向プログラミングは周知された開発アプローチで、構造化プログラムの基礎として大半の開発者がそのキャリアの初期に作成方法を学ぶことが多い。オブジェクト指向プログラミング言語の多くは、関数とほぼ見分けがつかない要素を含んでいる。だが、そのメカニズムは、「Haskell」のような純粋な関数型プログラミング言語のメカニズムとは懸け離れている。
オブジェクト指向プログラミングと関数型プログラミングのコード例
以下に示す関数型プログラミングのコード例は、「FizzBuzz」というコーディング課題に対応している。FizzBuzzはコーディングの試験によく使われ、開発者は次の簡単なルールに基づいて一連の文字と数字を出力する関数を作成する。
- 数字が3で割り切れる場合はその数字を単語「Fizz」に置き換える
- 数字が5で割り切れる場合はその数字を単語「Buzz」に置き換える
- 数字が3でも5でも割り切れる場合はその数字を単語「FizzBuzz」に置き換える
多くの関数型言語では、このロジックを個別の関数に構造化することができる。以下はは「F#」で記述したコード例を示している。
open System let isFizzBuzz value = (value % 15) = 0 let isFizz value = (value % 3) = 0 let isBuzz value = (value % 5) = 0 let output (value : int) = if isFizzBuzz value then "FizzBuzz" else if isFizz value then "Fizz" else if isBuzz value then "Buzz" else string value [<EntryPoint>] let main argv = let message = seq { for i in 1..100 do output i} |> String.concat " " printfn "%s" message printfn "Done"
では、「C#」のようなオブジェクト指向言語を使って同じことを実現しようとするとどうなるだろう。ロジックは似ている。だが、アプローチの方法は大きく異なり、オブジェクト指向の場合はコードによってロジックをオブジェクトに変換する。そのオブジェクトでは、置き換える必要のある数字を個別の変数に格納する。このオブジェクト指向のアプローチには、2つの具体的なメリットがある。まず、アプリケーション全体の一連のロジックをオブジェクトにカプセル化すると、そのオブジェクト同士がシンプルなインタフェースを通じて相互作用可能になる。次に、プログラマーは継承を利用し、必要に応じてロジックを追加または変更することで、FizzBuzzに似た試験をシリーズとして作成することができる。
以下は、C#を使って同じことを行う例を示している。この例では、置き換えのプロセスを指示するクラスとメソッドを作成している。
public class Fizzer { private int _val; public Fizzer() { _val = 1; } public string getNewVal() { string answer = ""; if (_val%5==0) answer = "Fizz"; if (_val%3==0) answer+= "Buzz"; if (!(_val%3==0 || _val%5==0)) answer = Convert.ToString(_val); answer+="\n"; _val+=1; return answer; } } class Program { static void Main(string[] args) { Fizzer f = new Fizzer(); for (int idx=1;idx<100;idx++){ Console.Write(f.getNewVal()); } } }
抽象化のレベルが高ければ、オブジェクトは最初から最後までループしてメソッドを呼び出すことができる。これは、1つのファイルに収容されたルールを使ってビルドしたいプログラマーの役に立つ。プログラマーは時間がたつにつれ、ファイルのルールを変更できる。
オブジェクト指向プログラミングと関数型プログラミングのユースケースの違い
オブジェクト指向プログラミングは、基本的なアプリケーション開発の多種多様なユースケースに対応する。ユーザーインタフェースの設計には、オブジェクト指向のアプローチが特に適している。ユーザーの画面に表示されるウィンドウは、ボタン、テキストボックス、メニューを使って構築されるのが一般的だ。こうしたウィンドウには状態がある。例えば、ワードプロセッサのページ上のテキストは、ユーザーの入力に応じて状態が変化する。ウィンドウの基本レイアウトを用意すれば、他のプログラマーはこの基本レイアウトのコードを継承して再利用し、その基本レイアウトにオブジェクトを徐々に追加していくことができる。
オブジェクト指向プログラミングの問題点は、時間がたつにつれ管理が難しくなる、複雑なコードベースを生み出すリスクがあることだ。オブジェクト指向アプローチに慣れた熟練の開発部門であっても、上述の例のように手続きコードの大きなセクションの中に幾つかのオブジェクトが散在するのが一般的だ。
そのため、他のオブジェクトを作成するオブジェクト、外部のデータベース、APIと結び付くオブジェクトのテスト、デバッグは難しくなる。エラーが起きた場合、そのエラーの完全な記録があるとしても、全てのオブジェクトの値やオブジェクトを複製した正確な方法をプログラマーが把握している可能性は低い。
こうした問題に対処する設計パターンも幾つか存在するのは確かだが、そうしたパターンを採用する業界はやや限られている。いずれにせよ、オブジェクト指向のアプローチに取り組む開発部門は、コードレビューやメンテナンスプロジェクトの難しさが課題になることが多い。
一方、関数型プログラミングにも課題はある。関数型プログラミングは学習して実践に移すのが非常に難しい。関数型のアプローチには、コードについて全く形式の異なる考え方、かなり長時間の投資、細部への厳密な注意が必要になる。こうしたことから、関数型プログラミングをリスクととらえるIT部門の管理職もいる。
ただし、他の関数に基づいて構築される関数がアプリケーションに含まれている場合は、関数型プログラミングが適している。例えば、「UNIX」では、開発者は多数の小さなプログラムを相互に結び付けるのが一般的で、プロセスの結果を送信して、grepコマンドを使って特定の検索結果を一覧したり、lessコマンドを使って一度に1ページで表示したりする。
また、オブジェクト指向プログラミングと関数型プログラミングの間に「妥協点」を見いだせる可能性もある。例えば、開発者が本来オブジェクト指向のアプローチでアプリケーションを設計しているとしても、1つのアプリケーションの中の個別のコンポーネントやモジュールをプログラマーが関数型アプローチを使って作成しても構わない。こうした方針は、保険見積もりルーティン、製造スケジュールの設定、抽出、変換、読み込み(ETL)の手続きなど、データをまとめてバッチ形式で結果を生成するプロセスに適している。以前にオブジェクト指向言語で構築されているプログラムに関数を追加できる場合もある。例えば、C#や「Java」などの言語は、オブジェクト指向プログラミングに似た構造内で関数型のアプローチを採用できる機能を備えるようになっている。そうした機能の1つに、共通言語ランタイムを通じてC#のコードと相互運用できるコードをF#で記述できる機能などがある。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 一見それほど変わらないように思えるKotlinとScala、どう使い分けるべき?
ScalaとKotlinはどちらも、汎用プログラミング言語として多くの機能を提供する。とはいえ、ユースケースも同じというわけではない。 - Stability AI、オフラインで使用できるコーディング生成AI「Stable Code 3B」を発表
Stability AIは、コーディング用生成AI「Stable Code」の最初のメジャーリリースとなる「Stable Code 3B」を発表した。 - 2024年に人気が出る言語は? 「2023年の言語」はやはりC#に
プログラミング言語の人気ランキング「TIOBEインデックス」の2024年1月版が公開された。C#が「2023年のプログラミング言語」となり、Fortran、Kotlin、Scratch、PHPもこの1年で順位を伸ばした。