関数型プログラミングについて今日は紹介するよ!
- 関数型プログラミングってなに?
- 関数型プログラミングとオブジェクト指向は何が違うの?
- 関数型プログラミングのメリットは?
- 関数型のプログラミング言語って?
などの疑問をお持ちの方の悩みを解決できる記事になっています!
- 関数型プログラミングについて知りたい人
- 関数型プログラミングで綺麗なコードを書きたい人
- 関数型プログラミング言語を知りたい人
目次
関数型プログラミングとは
関数型プログラミングとは、プログラミングの「関数」を組み合わせてアプリケーションを作るプログラミング手法です。特定のフレームワークやライブラリなどではなく、コードを記述する方法の一つです。
Haskell、Scalaなどの関数型プログラミング言語で書けば関数型プログラミングになるというわけでなく、あくまで書き方のスタイルになります。ですので、JavaScriptやPythonでもやろうと思えば関数型プログラミングで書けます。関数型プログラミング言語は、関数型プログラミングという書き方のサポート役という位置付けです。
関数型プログラミングの対になる概念として、命令型プログラミングがあります。(手続き型プログラミングとも呼ばれます)
命令型プログラミングは、コードを上から列として記述しその過程でデータの更新や状態の保持をし、課題の解決をします。
一方、関数型プログラミングは状態の保持をしません。一つ一つの関数を組み合わせることによって、課題の解決をします。
命令型プログラミングでも、関数を使います。しかし、関数の定義方法や使い方、目的が異なります。
関数型プログラミングの関数は次のようなことを目的としています。
- 副作用を減らす
- 状態遷移を減らす
- データの制御フローを抽象化する
手続き型プログラミングだと、データの状態を保持して変化させながらプログラムを実行します。そのため、コードが複雑になると、データの状態管理が難しくなります。
具体的には次のような問題が発生します。
- どこで変更があったかわからない
- 非同期処理などの副作用で意図しないデータの変更が発生する
- コードが複雑化して状態遷移が多くなり、データの変化を追えない
実際のプロジェクトだと、このようなことは頻繁に起こります。なぜなら、データの流れが複雑で処理を予測しづらくなるからです。
関数型プログラミングは、このような問題を解決するための機能が多く提供されています。
Haskellなどの関数型プログラミング言語は特に厳格に制御され、複雑なコードをなるべく排除する仕組みが備わっています。
関数型プログラミングの基本は、状態変化のない関数を組み合わせてプログラムを作ることになります。
そもそも関数とはなにか
関数型プログラミングは、関数を組み上げることでアプリケーションを作る手法です。
しかし、この「関数」とはそもそも何でしょうか。
プログラミングの関数は、数学の関数とは異なります。プログラミングにおける関数は、与えられた値を元に処理を実行しその結果を返すもの になります。
プログラミングの関数は、英語で言うと「function」になります。functionは「機能」という意味を持ちますが、プログラミングの関数はこちらの意味合いが強いです。
関数の詳しい説明と、サンプルコードはこちらで確認できます。
関数型プログラミングを構成する概念
関数型プログラミングを理解するためには、次のことを覚えておきましょう。
- 宣言型プログラミング
- 純粋関数型
- 非純粋関数型
宣言型プログラミング
関数型プログラミングは、宣言型プログラミングの一種です。
宣言型プログラミングとは、「問題の性質を宣言的に記述する」プログラミングスタイルです。反対に、命令型プログラミングは「問題を解く手順を記述する」プログラミングスタイルです。
このように書くとよくわかりませんによね。
簡潔に説明すると、次のようになります。
- 宣言型プログラミングは、「何がしたいか(What)」を明示的に書く
- 命令型プログラミングは、「どのようにしたいか(How)」を書く
宣言型プログラミングは、目的のみを要求するのでそこまでのプロセスは問いません。
しかし、命令型プログラミングはどのようにするかを問います。そのため、一つ一つプロセスを書かなければなりません。
例えば、「お茶を飲む」という目的があるとします。
宣言型プログラミングの場合は、「お茶を飲む」という目的を宣言的に記述するだけです。どのようにお茶を飲むのかは記述しません。コンビニで買ってきてもいいし、スーパーで買ってもいい、お茶畑からお茶を積んで飲んでもいいでしょう。
反対に命令型プログラミングは、「どのようにお茶を飲む」かを書きます。コンビニへ行って、お茶を購入して、自宅で飲むなどを一つ一つ命令してプログラムを組み立てます。
命令型プログラミングが「How」に対して記述し、宣言型プログラミングは「What」に注目して記述するプログラミングスタイルです。
関数型プログラミングも宣言型プログラミングの一種なので、この宣言型の特徴を兼ね備えています。
まとめると次のようになります。
性質 | 命令型プログラミング | 宣言型プログラミング・関数型プログラミング |
スタイル | どのようにしたいかを書く(How) | 何がしたいかを書く(What) |
純粋関数型
純粋関数型とは、参照透過性のある関数のことを指します。
参照透過性とは関数の式の要素が同じなら、常に同じ値を返すことです。
例えば、JavaScriptのコードを例を見てみましょう。
このdouble
関数は与えられた値を2倍にして返します。
function double(num) {
return num * 2
}
試しに実行してみましょう。
引数に「4」を渡せば、必ず「8」が返ってきます。
このような、式の条件が同じなら値が必ず同じになる性質を参照透過性と言います。
そして、参照透過性がある関数を純粋関数型と言います。
この純粋関数型で構成するプログラミング言語を純粋関数型プログラミング言語と言います。
例えば、Haskellなどは純粋関数型言語です。
非純粋関数型
非純粋関数型とは、純粋関数型よりも厳格ではなく副作用をある程度許容する関数です。
副作用とは、関数を実行した時にデータの値が変更されることを言います。
JavaScriptを例に見てみましょう。
次の関数は、関数の外にある変数を2倍にして値を返します。
var num
function calc() {
num *= 2
return num
}
これは、副作用がある関数です。
なぜなら、関数外にある変数のデータを変更してしまっているからです。変数のnum
は別の関数や処理で使われるかもしれません。calc
を実行することでnum
の値は変更され、他の処理に影響を与えてしまいます。
このような現象を副作用があると表現します。
副作用はなるべく少なくした方がメンテナンス性は高まります。なぜなら、データの遷移を追いやすいからです。
しかし、アプリケーションでは副作用は頻繁に起こります。
その副作用をある程度許容している関数を非純粋関数型と言います。
非純粋関数型のプログラミング言語は、ScalaやErlangなどがあります。
関数型プログラミングのメリット
- 宣言的に書けるのでわかりやすく、コードがシンプルになる
- 再利用性が向上する
- 拡張性が高くなる
- 副作用が発生しづらい
- テストがしやすい
宣言的に書けるのでわかりやすく、コードがシンプルになる
関数型プログラミングは、宣言型プログラミングの一部でもあります。
そのため、宣言的にプログラムを記述できるのでコードが分かりやすくなる傾向があります。
例えば、ユーザー情報を取得してリストに出力するプログラムがあったとしましょう。
命令型プログラミングで書くと次のようになります。
// ユーザーを取得
let users = await axios.get("/api/users")
// ユーザーの値を整形
for (var i = users.length - 1; i >= 0; i--) {
users[i].name = users[i].lastName + users[i].firstName
}
// ユーザーリストを出力
let html = ""
for (var i = users.length - 1; i >= 0; i--) {
html += `
${users[i].name}
` } document.getElementById(“test”).innerHtml = html
これを関数型プログラミングで書き直してみましょう。
const data = await fetchUsers()
const users = preapre(data)
render(users)
宣言的に目的だけを書いているので、だいぶスッキリしましたね。命令型に比べると読みやすくなっているのも特徴です。
また、JavaScriptの草案に上がっているpipeline演算子を使うと次のように書くこともできます。(まだ動作はしない)
await fetchUsers
|> preapre
|> render
再利用性が向上する
関数型プログラミングは、小さな関数を組み合わせて目的を達成するプログラミングスタイルです。
そのため、必然的に関数の再利用性が向上します。
なぜなら、関数の集合でプログラムを作るので汎用的な機能は関数にまとめることができるからです。
命令型プログラミングでも、オブジェクト指向の概念を使って再利用性を上げることはできます。
しかし、関数型プログラミングの方がより小さな単位でモジュール化(まとめること)ができるので再利用性が高くなります。
拡張性が高くなる
関数型プログラミングでは、他の関数に影響を与えないプログラミングスタイルです。
前述した副作用がない純粋関数型では、お互いの関数に影響を与えません。
そのためプログラムの拡張性が高くなります。
一つの変更で他の処理も変更が必要な場合は、拡張性が低いコードになります。
プログラミングの世界では、変更に強く拡張性の高いコードが好まれます。
関数型プログラミングではそれを実現できる機能・書き方が多く提供されています。
副作用が発生しづらい
関数型プログラミングは、命令型プログラミングに比べると副作用が発生しづらいです。
なぜなら、データの状態を保持しないためです。
先程のJavaScriptの例を見てましょう。
function double(num) {
return num * 2
}
double
関数は状態を持っていません。そして、関数外の変数に一切の影響(副作用)を与えていません。
引数の渡した値を2倍にして新たな値を生成しています。
純粋関数型では、副作用を許容しません。そのため、副作用が発生せずにデータの変化を予測しやすくなっています。
テストがしやすい
関数型プログラミングはテストがしやすいプログラミングスタイルです。
関数の集合でプログラムが組まれるので、一つ一つの関数をテストすればプログラムの動作を担保することができます。
また、副作用がないので関数に引数を渡して、期待する値を書けば簡単にテストすることができます。
関数型プログラミングとオブジェクト指向の違いは
関数型プログラミングとオブジェクト指向の違いは何でしょうか。
関数型プログラミングとオブジェクト指向の一番の違いは、データの状態の持ち方です。
オブジェクト指向は、
- クラス
- 継承
- カプセル化
- ポリモーフィズム
などの技術を用いてプログラムを組み立てます。
データの状態変化はクラスに内包され、副作用はインターフェースを使って抽象化されます。
一方、関数型プログラミングはデータの状態を持ちません。
複数の関数を通して、プログラムの目的を達成します。
副作用はモナドなどの技術で隠蔽されます。
関数型プログラミングとオブジェクト指向の違いは、プログラミングの書き方と問題を解決するためのアプローチの違いでもあります。
オブジェクト指向に自信がないという方は、こちらをご確認ください。
関数型プログラミング言語
それでは、代表的な関数型プログラミング言語を見てみましょう。
- Haskell
- Scala
- Elm
- Elixir
- Reason
Haskell
引用元: Haskell
Haskellは、1990年に誕生した純粋関数型プログラミング言語です。
Haskellは、関数型プログラミング言語ですが手続き型の書き方もできるプログラミング言語です。Haskellと聞くと、関数型の印象が強いですが実際は手続き型の書き方もよく使います。
Haskellの主な特徴は次の通りです。
- 関数型と手続き型の書き方ができる
- 参照透過性
- モナド
- 遅延評価
Scala
引用元: scala-lang
Scalaは、サーバーサイドで動作する関数型プログラミング言語です。
Scalaの特徴は、関数型とオブジェクト指向を統合していることです。
Haskellなどの純粋関数型と違い、Javaをベースとしたオブジェクト指向に関数型の機能を提供しています。
Elm
引用元: elm
Elmは純粋関数型で、Webアプリケーションのフロントエンド開発を可能にするプログラミング言語です。
Webアプリケーションに使われるので、HTML・CSS・JavaScriptにコンパイルされます。しかし、直接JavaScriptやCSSを操作することはできないので基本的にElmが提供している関数経由で行います。そうすることで、独自の書き方をして保守しづらくなったり、コードが汚くなるのを防ぎます。
ElmはThe Elm Architectureというフレームワークとセットで使われます。
Elixir
引用元: elixir
Elixirは、2012年に開発されたサーバーサイドの関数型プログラミング言語です。Erlang仮想環境上で動作し、Ruby on Railsのコアメンバーによって開発されました。
Elixirの特徴は
- 関数型プログラミングの機能が備わっている
- 並行処理が得意
- Rubyエンジニアに馴染みやすい
などがあります。
PureScript
引用元: PureScript
PureScriptは、Elmと同様にコンパイル結果としてJavaScriptを出力します。
そのためWebアプリケーションで動作することができます。
PureScriptの言語仕様は、Haskellがもとになっています。そのため、関数型プログラミングの高度な機能を多く備えています。
まとめ
この記事では、関数型プログラミングについてまとめました。
最後にもう一度内容を確認しましょう。
- 関数型プログラミングは、一つ一つの関数を組み合わせることによってプログラムを作り上げる
- 関数型プログラミングは、データの状態を保持しない
- 関数型プログラミングを構成する概念は、宣言型プログラミング、純粋関数型、参照透過性、非純粋関数型、副作用など
- 関数型プログラミングの最大のメリットは、コードがシンプルで分かりやすくなること
関数型プログラミングは習得が難しいプログラミングスタイルです。
しかし、前述した通りメリットが多くあり、使いこなせるようになれば強力なツールになることは間違いないでしょう。
上記にあげた関数型プログラミング言語以外でも、関数型プログラミングの書き方をすることができます。
JavaScriptではReactなどのフレームワークが宣言型プログラミングをベースに、関数型プログラミングの機能を盛り込んでいます。
また、pipelineなどの関数型プログラミングの機能を標準で導入する動きもあります。
今後、関数型プログラミングのパラダイムが他のプログラミング言語でも採用される流れになっているのを感じます。
エンジニアとして一歩上のレベルへ進むには、関数型プログラミングを学ぶことをおすすめしますよ!