Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
関数プログラミングの考え方
Search
Tomohiko Himura
July 25, 2023
Programming
1
230
関数プログラミングの考え方
Tomohiko Himura
July 25, 2023
Tweet
Share
More Decks by Tomohiko Himura
See All by Tomohiko Himura
バイナリ読むのにElixirしてみた
eiel
0
12
アジャイルはさておきMake People Awesomeしたい
eiel
0
110
レビューは最優先にするようにしている
eiel
0
220
再考 Fourkeys メトリクス
eiel
2
610
Test mockをSnapshot testする
eiel
0
92
devenvに入門した
eiel
0
62
逆コンウェイ作戦はフィードバックループを作るために 逆向きの流れをつくること (5分版)
eiel
0
310
組織のパフォーマンスを高めるために 第1話 学習と文化
eiel
0
190
チームとチームのチーム
eiel
1
2.1k
Other Decks in Programming
See All in Programming
ブラウザ単体でmp4書き出すまで - muddy-web - 2024-12
yue4u
3
480
create_tableをしただけなのに〜囚われのuuid編〜
daisukeshinoku
0
270
PHPUnitしか使ってこなかった 一般PHPerがPestに乗り換えた実録
mashirou1234
0
230
Stackless и stackful? Корутины и асинхронность в Go
lamodatech
0
830
Jakarta EE meets AI
ivargrimstad
0
260
Go の GC の不得意な部分を克服したい
taiyow
3
800
命名をリントする
chiroruxx
1
420
PHPで学ぶプログラミングの教訓 / Lessons in Programming Learned through PHP
nrslib
3
300
ドメインイベント増えすぎ問題
h0r15h0
2
360
CSC305 Lecture 26
javiergs
PRO
0
140
menu基盤チームによるGoogle Cloudの活用事例~Application Integration, Cloud Tasks編~
yoshifumi_ishikura
0
110
短期間での新規プロダクト開発における「コスパの良い」Goのテスト戦略」 / kamakura.go
n3xem
2
170
Featured
See All Featured
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
8
1.2k
How to train your dragon (web standard)
notwaldorf
88
5.7k
Designing for humans not robots
tammielis
250
25k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
656
59k
Bootstrapping a Software Product
garrettdimon
PRO
305
110k
Embracing the Ebb and Flow
colly
84
4.5k
Mobile First: as difficult as doing things right
swwweet
222
9k
No one is an island. Learnings from fostering a developers community.
thoeni
19
3k
BBQ
matthewcrist
85
9.4k
Building Adaptive Systems
keathley
38
2.3k
Documentation Writing (for coders)
carmenintech
66
4.5k
A better future with KSS
kneath
238
17k
Transcript
関数プログラミングの考え方 2023/07/19 ひむら ともひこ
注意事項 本内容には個人の見解が含まれます。独自の見解や説明の都合上一般的 な説明とは多少異なる定義や説明を行うことがあります。ウェブサイト や書籍を参考にする場合はその場の定義を確認するようにしてくださ い。また、同じ用語でも認識の違いによってSNSで炎上する事例が近年よ くみられます。内容を鵜呑みしないようにしたり、SNSでの発言には十分 に注意してください。
自己紹介 ひむら ともひこ 所属 コミュニケーションのためのSaaSを提供する会社 • ソフトウェアエンジニア • メタソフトウェアエンジニア
今日のあらすじ なぜ関数プログラミングの考え方を学ぶべきか 関数プログラミングとはなにか まとめ
なぜ関数プログラミングの考え方を 学ぶべきか
開発・設計に使える道具を増やせる
なぜ 開発・設計に使える道具を増やせるのか
オブジェクト指向と関数プログラミングは 共存できる 向き不向きに合わせて使い分けできる
なぜ関数プログラミングを使うべきか
None
プログラムの動作を予測しやすい
なぜ関数プログラミングの考え方を学ぶべきか オブジェクト指向を習得済みのプログラマが関数プログラミングの考え 方を学ぶべき理由は、関数プログラミングはコードが簡潔で読みやす く、動作を予測しやすいプログラムを書くことができます。また、オブ ジェクト指向と共存できるため使い分けをすることができます。例え ば、動作が複雑な部分だけ関数プログラミングすることもできます。つ まり、開発・設計で使える道具を増やすことができます。
関数プログラミングとは何か
今日のあらすじ なぜ関数プログラミングの考え方を学ぶべきか 開発・設計の幅が広がる 動作を予測しやすい コードが簡潔 関数プログラミングとはなにか まとめ
プログラミングパラダイム 宣言型 プログラミング 命令型 プログラミング 関数 プログラミング 論理 プログラミング 手続き型
プログラミング オブジェクト指向 プログラミング 状態を保持できる (ステートフル) 状態が持てない (ステートレス) 関数を組み合わせる オブジェクトが連携する ※宣言型に状態を追加したものは厳密には命令型とは言い切れませんが便宜的に命令型としています
状態を持てないプログラミング
状態が持てない(ステートレス)とは • 変数に束縛はできるが再代入することができない ◦ ※過去の束縛を隠して再束縛できる言語はあります • メンバ変数、インスタンス変数は持てるが変更できない ◦ 変更する代わりに新しいオブジェクトを作る必要がある ◦
⇒ Immutable • ⇒ 変数を共有しても並行並列に動作する処理に影響を与えない
ステートフルな関数プログラミングもある
関数プログラミング プログラミングパラダイム 宣言型 プログラミング 命令型 プログラミング 純粋関数 プログラミング ステートフルな関数 プログラミング
オブジェクト指向 プログラミング 状態を保持できる (ステートフル) 状態が持てない (ステートレス) 純粋関数を組み合わせる オブジェクトが連携する ※宣言型に状態を追加したものは厳密には命令型とは言い切れませんが便宜的に命令型としています 非純粋関数を使う
関数プログラミングには2種類ある • 純粋関数プログラミング ◦ 動作を予測しやすい • ステートフルな関数プログラミング ◦ 簡潔な記述ができる
概要 なぜ関数プログラミングの考え方を学ぶべきか 開発・設計の幅が広がる 関数プログラミングとはなにか 純粋関数プログラミング 動作を予測しやすい ステートフルな関数プログラミング 簡潔な記述を提供 まとめ
純粋関数プログラミングとはなにか
純粋関数プログラミングとは • 純粋関数を組み合わせてプログラミングする • 純粋関数とは ◦ 同じ入力に対して常に同じ出力を返す関数 ◦ 入力が出力以外のものに影響を与えない ▪
「画面に出力する」など、 実行に影響がない場合無視しても良いとする場合がある ◦ 入力が出力以外に影響がある場合、「副作用がある」という • 純粋関数は「副作用がない」
具体例 純粋関数 • 足し算をする関数 add ◦ add(1, 1) # =>
2 ◦ add(1,2) # => 3 • 足し算は引数が同じであれば常に同じ結果を返す
具体例 副作用のある関数 • 時間を返す関数 ◦ getDateTime() # => 実行するたびに結果が変わる •
画面に出力する関数 ◦ println(“Hello, World”) # => 常に出力を返さない ◦ 画面にはHello, Worldと印字される ◦ 何度も実行するとHello, Worldが増えていく ▪ 画面を表示するための状態が変化している ◦ 常に出力を返さない関数は副作用があることが期待される
純粋関数プログラミングって 画面に出力すらできないの…?
そもそも純粋関数だけでは アプリケーションがつくれない
純粋関数だけではアプリケーションはつくれない • プログラム自体は作れるが画面すら出力できない ◦ ユーザからフィードバックを得ることができない • 非純粋関数はなるべく局所化し分離する ◦ ステートレスとステートフルは組み合わせることができる ◦
オブジェクト指向でも純粋関数を使うことができる • 純粋関数プログラミングは ◦ 再代入なしでプログラムしやすいように進化した
雑談 純粋関数型言語の副作用 • 純粋関数型言語は純粋関数だけでプログラムが書ける ◦ Haskell, PureScript • Haskell自身は副作用のある関数は取り扱わない ◦
再代入する値を生成する関数を作るだけである (????) • 具体例 文字列を画面に出力する putStrLn ◦ 文字列を受け取って関数を返す関数 (????) ▪ String => IO () ◦ 現在の状態を受け取って次の状態を返す ▪ IO () = State => (State, ()) ▪ 画面の状態を受け取って、新しい画面の状態をつくる ▪ 実際に新しい状態へ反映する部分はプログラムできない
雑談 プログラミングの純粋関数と数学の関数 • 数学の関数 ≒ 純粋関数 ◦ 理論上は同じものとしてよい • プログラム上の純粋関数は実際のコンピュータで動く
◦ 動かすコンピュータによって結果が変わる要素がある ◦ メモリ不足 ⇒ 結果を返せず停止してしまう ◦ CPUパワー不足 ⇒ 結果を返すのにかかる時間が違う • 事実上無視できる前提で純粋関数プログラミングは行われている
関数を組み合わせる • 関数と関数を組み合わせることでプログラムする • 一番簡単な組み合わせ方法 ◦ 関数の出力を次の関数の入力に使う +3する関数 ×2 する関数
1 1+3 4 4 × 2 8 2 2+3 5 5 × 2 10
関数を組み合わせる pythonの例 def add3(x): return x+3 def mul2(x): return x*2
def add3Mul2(x): # 実質関数の組み合わせをしている return mul2(add3(x)) add3Mul2(1) # => 8 add3Mul2(2) # => 10
関数を組み合わせる Haskellの例 add3 = (+3) mul2 = (*2) add3mul2 =
mul2 . add3 # . は関数と関数を組み合わせる演算子 add3mul2 1 -- => 8 add3mul2 2 -- => 10 (mul2 . add3) 1 -- => 8 # 関数を先に組み合わせて関数を使っている mul2 (add3 1) -- => 8 # add3を先に使って、結果をmul2に使っている
別の組み合わせ方法 • 途中の処理をあとから追加する ◦ 同じ色の矢印は同じ関数とする +3する関数 ×2 する関数 +3 する関数
4 + 3 1 1 + 3 4 7 7 × 2 14 ×2 する関数 4 × 2 1 1 + 3 4 8 8 × 2 16
別の組み合わせ方法 Python版 def add3fxMul2(f): return lambda x: f(x+3) * 2
add3fxMul2(lambda x: x+3)(1) # => 14 add3fxMul2(lambda x: x*2)(1) # => 16
別の組み合わせ方法 Haskell版 add3fxMul2 f = (*2) . f . (+3)
add3fxMul2 (+3) 1 -- => 14 add3fxMul2 (*2) 1 -- => 16
なぜ純粋関数プログラミングが動作を予測しやすいか • 純粋関数と純粋関数を組み合わせても純粋関数である ◦ 同じ入力であれば出力がかわらない ◦ 組み合わせた関数に渡される入力も変わらない ◦ ⇒ 最終的な出力も変わらない
• 動作に影響を与えるものはすべて入力で受け取る ◦ 入力が分かれば計算結果を求められる ◦ 期待した入力がされてないならそれ以前に問題がある ◦ 期待した入力がされているならそれ以前に問題はない ◦ 同時に複数の関数を計算してもお互いに影響を与えない ▪ 並行並列化しやすい
純粋関数プログラミングは順番に左右されない • 直線を引く関数を使って、三角形を描くプログラムを考える (1,2) (1,0) (0,0)
state = [] state = drawLine(state,0,0,1,0) state = drawLine(state,1,0,1,2) state
= drawLine(state,1,2,0,0) exec(state) # 画面に反映する非純粋関数とする # 3本ある直線をどの順番で描いても三角形が表示できる 純粋関数は順番に左右されない (1,2) (1,0) (0,0)
drawLine(1,0) # 原点からx方向に1移動 drawLine(0,2) # (1,0)からy方向に2移動 drawLine(-1, -2) # (1,2)から
x方向に-1,y方向に-2移動 # 引数を減らせるが順番を変えると違う形になる drawLine(0,2) # 原点からy方向に2移動 drawLine(1,0) # (0,2)からx方向に1移動 drawLine(-1,-2) # (1,2)からx方向に-1,y方向に-2移動 現在の位置を覚えておいて移動する距離を渡して描く場合 (1,2) (1,0) (0,0) (1,2) (0,2) (0,0)
雑談 純粋関数メタプログラミング • 純粋関数の特性を活かしたコンパイル前処理 • 純粋関数は入力が分かれば出力がわかる ◦ もし入力値がコンパイル前からわかっているものがあれば… ◦ コンパイル時に計算することができる
雑談 純粋関数パッケージマネージャ • NixOSのパッケージマネージャ • 純粋関数の特性を活かしたパッケージマネージャ • インストールしたいパッケージやパッケージリポジトリ、システム設定 などを入力にすることでシステムを算出する •
パッケージや入力を追加してシステムが壊れたとしても ◦ 追加前と同じ入力を与えることでシステムを元の状態に戻せる
雑談 スケールしやすいWebアプリはステートレス • Webアプリケーションは急激なアクセスがあったとき • 大量のアクセスに耐える必要がある ◦ アプリケーションサーバーを増やせるようにする ◦ 状態をデータベースにおきステートレスにする
◦ ステートレスなら複数のサーバで同時に実行できる ▪ 並列処理 ▪ もちろんデータベースがボトルネックになる
まとめ 純粋関数プログラミングとは何か • 宣言型プログラミングの一種でステートレス • 宣言型プログラミングでは変数を再代入できない • 関数プログラミングでは、純粋関数を用いてステートレスを実現 • 純粋関数は動作を予測しやすい
• 純粋関数と純粋関数を組み合わせても純粋関数 • 関係ない純粋関数同士は同時に計算できる
概要 なぜ関数プログラミングの考え方を学ぶべきか 開発・設計の幅が広がる 関数プログラミングとはなにか 純粋関数プログラミング 動作を予測しやすい ステートフルな関数プログラミング 簡潔な記述を提供 まとめ
ステートフルな関数プログラミングとはなにか
関数プログラミングで発展した機能 オブジェクト指向 値としての関数 関数合成 パターンマッチ カリー化 高階関数 再帰 近年のオブジェクト指向言語 取り入れる
Monad 型クラス
再代入可能な環境での関数プログラミング • 純粋関数プログラミングで生まれた機能がオブジェクト指向で使える ◦ 再代入と純粋関数プログラミングを組み合わせることができる • 近年、関数プログラミングといったとき、こちらを指す場合がある
ステートフルな関数プログラミング • 関数が状態を持てると特化したクラスのように使える ◦ 具体例 ▪ React Hooks ▪ クロージャと再代入
具体例 React Hooks • React登場時は状態を持つコンポーネントはクラスコンポーネント使 う必要があった • React Hooksによって関数コンポーネントで状態を扱えるようになっ た
具体例 React Hooks function Counter() { const [counter, setCounter] =
useState(0); return ( <div> <button onClick={() => setCounter(x => x+1)} > counter is {counter} // ボタンを押されるたびに数字が増える </button> </div> ); } https://codesandbox.io/p/sandbox/bold-cache-pxp83l?file=%2Fsrc%2FApp.tsx%3A18%2C1
具体例 React hooksの代わりにclass class Counter extends React.Component { constructor(props) {
super(props); this.state = { count: 0 }; } render() { const { counter } = this.state; return ( <div> <button onClick={() => { const { counter } = this.state; this.setState({ counter: counter+1 }); }} > counter is {counter} // ボタンを押されるたびに数字が増える </button> </div> ); } }
具体例 クロージャと再代入 function createCounter() { let state= 0; return ()
=> { // クロージャ state = state+ 1; return state; } } const counter = createCounter(); counter() // => 1 counter() // => 2 結果が変わるので純粋関数ではない const counter2 = createCounter(); counter2() // => 1 独立したcounterを作成できる counter() // => 3
具体例 クロージャと再代入の代わりにクラス class Counter: state = 0 def increment(self): self.state
= self.state + 1 return self.state counter = Counter() counter.increment() # => 1 counter.increment() # => 2 counter2 = Counter() counter2.increment() # => 1 counter.increment() # => 3
関数を返す関数は特化したクラス クラスはコンストラクタ関数を呼び出すとオブジェクトを返す オブジェクトにはメソッド(関数)があり呼び出すことができる つまり、クラスは関数をもったオブジェクトを返す関数とみなせる
具体例 再びクロージャと再代入 function createCounter() { let state= 0; return {
increment: () => { state = state + 1; return state; } } } const counter = createCounter(); counter.increment() // => 1 counter.increment() // => 2
クラスとクロージャ • メソッドを一つしか持たないクラスであれば ◦ クロージャで書き換えることができる ◦ 複数メソッドも実現可能 • さらにコンストラクタも不要な場合 ◦
ただの関数とみなせる • 言語によっては記述量が減るため見通しがよくなる ◦ Javaでは匿名クラスなどが以前は使われていた ◦ iOSもイベントハンドラにはdelegateパターンが使われていた ▪ 現在ではクロージャが使われる
ステートフルな関数プログラミングとはなにか 再代入できる環境での関数プログラミングはクラスと同等の能力を短く 記述できる。関数プログラミングでは再代入しない前提で様々な機能が 発展したが、現代ではオブジェクト指向言語にも取り込まれつつある。 そのため、関数プログラミングといったときに純粋関数プログラミング を指してない事が増えてきた。
まとめ
関数プログラミングの分類 純粋関数プログラミング ステートフルな関数プログラミング 再代入 できない できる 純粋関数 利用できる 利用できる 特徴
動作を予測しやすい 並行並列化が容易 クラスより短くに記述可能
関数プログラミングには2種類ある • 純粋関数プログラミング ◦ 本来の関数プログラミング ◦ 入力に対して出力が一定な純粋関数を使う ◦ 動作が予測しやすい •
ステートフル関数プログラミング ◦ 再代入が使え、オブジェクト指向と同等の能力がある ◦ クラスよりも記述が短くなる
純粋関数プログラミングの考え方 • 小さな純粋関数を組み合わせてプログラムを作成する ◦ 出力を別の関数の入力に使うことで組み合わせることができる ◦ 純粋関数を組み合わせて作った関数もまた純粋関数である • 実際には非純粋関数を組み合わせる必要がある ◦
非純粋関数を組み合わせると純粋関数プログラミングのメリットが薄まる ▪ 入力に対して出力が不定になり、動作してみないとわからなくなる ▪ 非純粋関数の利用を局所化することでメリットを享受しやすくする
なぜ関数プログラミングの考え方を取り入れるか 適材適所で使うことでよりよいアプリケーションをつくることができる • 関数プログラミングのメリット ◦ 動作を予測しやすい ◦ 並行・並列環境でも安全に動作する ◦ ステートフルな関数プログラミングは記述を短くする
• 関数プログラミングのデメリット ◦ ステートレスな関数は引数が長くなる ◦ 非関数型プログラミング言語だと最適化できず速度に影響を与えることがある ◦ 関数プログラミングの高度な手法は数学的概念がベースで学習コストが高いと言わ れている
関数プログラミングの考え方を知ることで 設計や実装方法の幅を広げることができる
おまけ 高度な機能
理論上、純粋関数は数学の関数とみなせる
つまり、数学で証明されている 理論を活用することができる
数学の概念から取り入れらているもの(雑な説明) • Monoid ◦ 2項演算子をもち、単位元があり、結合法則が成り立つ ◦ 2項演算子はどこからでも計算できる • Functor ◦
ある関数を別の型の関数として使えるようにできる • Monad ◦ ネストした構造をフラットな構造に戻す自然な手段が存在する