SlideShare a Scribd company logo
1 of 15
Download to read offline
© Chatwork
JSConf JP 2021
Chatwork 株式会社
CTO 室 / エンジニア採用広報
高瀬 和之 (@Guvalif)
関数型プログラミングの
デザインパターンひとめぐり with Ramda.js
 
© Chatwork
■ 概要 🗒
Haskell ライクな関数型プログラミングを
JavaScript にて行えるようにするライブラリ "Ramda.js" を (一部) 例にして、
関数型プログラミングにおいて頻出のデザインパターンをご紹介します。
■ 関数型プログラミングに対する私の立ち位置 󰞹
- 良い設計を発見する ための、ベース知識として用いる
- 安全な開発を実現する ための、勘所として用いる
- これらは決してプログラミング言語に依存すること無く、
普遍的に応用可能 だと考える
- つまり、ライトユーザーです  (コワクナイヨ!
2
まえおき
 
© Chatwork 3
純粋関数
- 関数型プログラミングの大原則 → 主役は "純粋関数"
- 純粋関数とは?
- 引数に同じ値を与えたら、常に同じ戻り値を返す関数のこと
- なおかつ、副作用が存在しないもの
- "参照透過" というキーワードで、ひとえに説明しても良い
- なぜ純粋関数を用いるのか?
- 副作用が存在しない ≒ 挙動が予測しやすい
- 型システム を併用することで、予測可能性をさらに高める ことができる
const add: (_0: number, _1: number) => number =
(x, y) => x + y;
const log: (_: string) => void =
(s) => console.log(s);
純粋関数の例 (インプレイスな置き換えが可能) 純粋関数でない例 (外部から観測できない作用がある)
 
© Chatwork 4
カリー化
- "複数の引数を取る関数" と "単一の引数を取る関数" は、相互変換ができる
- 理論的背景を知りたい方は ... → "積対象 指数対象 随伴" で Let's Google 🔍
- cf. 『圏論と Swift への応用』 by inamiy さん
const add : (_0: number, _1: number) => number =
(x, y) => x + y;
const add_: (_0: number) => (_1: number) => number =
(x) => (y) => x + y;
// 使い方が異なるだけで、効果は変わらない
add(1, 2) === add_(1)(2);
 
© Chatwork 5
カリー化による設計上のメリット
- 例) カリー化による環境の固定
- 同様に、Dependency Injection にも応用できる
- ちなみに、Ramda.js では全ての関数が標準でカリー化 されている 🎉
const buildSendMessageRequest = ({ secret, roomId }: ChatworkEnv) => (body: string) => ({
method: 'POST',
url: `https://api.chatwork.com/v2/rooms/${roomId}/messages`,
headers: {
'X-ChatworkToken': secret,
},
data: `body=${body}`,
});
// builder: (body: string) => Request として、簡便に使いまわすことができる
const builder = buildSendMessageRequest({ secret: 'XXXX', roomId: '123456789' });
 
© Chatwork 6
副作用との付き合い方
- "純粋関数が主役" という考え方より、副作用も観測可能にしたい ...
- どうする? → 副作用をなるべくデータ型として表現する (≒ 作用を型で明示する)
- 例) Tagged Union Type により、try - catch を用いずに異常系を表現する
interface Left<T> {
value: T;
_tag: 'Left';
}
interface Right<T> {
value: T;
_tag: 'Right';
}
// 慣例的に、Left が異常系,Right が正常系を表す
type Either<L, R> = Left<L> | Right<R>;
function safeDiv(
x: number,
y: number,
): Either<Error, number> {
if (y === 0) {
return left(new Error('/ by 0 !'));
}
return right(x / y);
} ファクトリ関数とする
 
© Chatwork 7
作用を便利に扱うための演算群
- 慣例的に、ジェネリック関数の3つ組を考えることが多い
- 理論的背景を知りたい方は ... → "計算効果 Monad" で Let's Google 🔍
- cf. 『Notions of Computation and Monads』 by Eugenio Moggi
// Haskell では 'pure' という名で知られる
// cf. https://ramdajs.com/docs/#of
of: <T>(value: T) => M<T>
// Haskell では 'fmap' という名で知られる
// cf. https://ramdajs.com/docs/#map
map: <A, B>(f: (_: A) => B) => (_: M<A>) => M<B>
// Haskell では 'bind' という名で知られる
// cf. https://ramdajs.com/docs/#chain
chain: <A, B>(kf: (_: A) => M<B>) => (_: M<A>) => M<B>
 
© Chatwork 8
各演算群の直感的な意味
- 例) M<_> を Promise<_> で置き換えると ...
- ※ 厳密には、Promise だと良い性質を持たないので注意!
// "値" を "作用のある値" に変換する (※ もっとも自明な変換を用いる)
const of: <T>(value: T) => Promise<T> =
(value) => Promise.resolve(value);
// "関数" を "作用のある関数" に変換する
const map: <A, B>(f: (_: A) => B) => (_: Promise<A>) => Promise<B> =
(f) => (promise) => promise.then(f);
// "途中で作用をもつ関数" を "作用のある関数" に変換する
const chain: <A, B>(kf: (_: A) => Promise<B>) => (_: Promise<A>) => Promise<B> =
// .then メソッドの性質から、map と実装が変わらない (ややつまらない実装)
(kf) => (promise) => promise.then(kf);
 
© Chatwork
of の役割:
"値" から ...
"作用のある値" への変換
map の役割:
"関数" から ...
"作用のある関数" への変換
chain の役割:
"途中で作用をもつ関数" から ...
"作用のある関数" への変換
map(f): (_: M<A>) => M<B>
f: (_: A) => B
9
各演算群の直感的な意味 (図解版)
X A B
M<A> M<B>
map
M<X>
of: (_: X) => M<X>
M<A> M<B>
chain(kf): (_: M<A>) => M<B>
kf: (_: A) => M<B>
A
chain
 
© Chatwork 10
ADT と Catamorphism
- Union Type や Tuple Type を (複合的に) 用いたデータ型を、ADT と呼ぶ
- ADT = Argebraic Data Type の意 → 代数的データ型
- ADT に対しては、自然な分解と変換 を定義することができる → 代表例が Catamorphism
- 理論的背景を知りたい方は ... → "F 始代数" で Let's Google 🔍
const either = <L, R, T>(lmap: (_: L) => T) => (rmap: (_: R) => T) => (m: Either<L, R>): T => {
// 対称性を強調するために、それぞれの場合分けを明示的に記述
if (m._tag === 'Left') {
return lmap(m.value);
}
if (m._tag === 'Right') {
return rmap(m.value);
}
};
 
© Chatwork 11
Catamorphism の応用例
- 例) Either に対する、統一的なエラーハンドリング
- ちなみに、配列に対する reduceRight も Catamorphism だったりする
- いわゆる "畳み込み" と呼ばれる操作は、Catamorphism として一般化できる
const lmap = (e: Error) => e.name;
const rmap = (x: number) => `${x}`;
const handler: (_: Either<Error, number>) => string =
either(lmap)(rmap);
// try - catch 方式と、大きく使い勝手が変わらない
console.log(handler(safeDiv(N / M)));
 
© Chatwork 12
複合的なユースケース → "ブラックジャック" の例
- Qiita に参考記事を掲載しているので、ぜひ一読してみてください 🗒
- cf. 『JavaScript で (なるべく) 関数型にブラックジャックを実装する』 by Guvalif
×
 
© Chatwork 13
現実的なユースケース in Chatwork → リリース基盤のバックエンド実装
- Ramda.js をフル活用して、CLI や CRON を実装しています 🗒
- 余談) 公式技術ブログに記事化したいと思い続けて、一年半が過ぎた 😇
const rejectEmptyEffect = R.ifElse(
R.isEmpty,
() => Promise.reject(new Error('スナップショットに含めるべきファイルが存在しません')),
Promise.resolve.bind(Promise),
);
const uploadFileEffect = createUploadFileEffect(s3Env, birdcageEnv);
const uploadFileWithLoggingEffect = R.pipe(
R.tap<File>((file) => console.log(`Uploading: ${file.name}`)),
uploadFileEffect,
);
 
© Chatwork 14
まとめ
- 関数型プログラミングの考え方には、実用上も便利 なものが多い 👌
- なおかつ、理論的背景 もしっかりしている 📖
- Haskell を使うのは大変でも、Ramda.js だったらライト に始められる 🚀
- 時間があったら取り扱いたかったトピック:
- 合成可能な setter と getter の組,Lens
- Applicative による Validation の一般化
- map も、filter も、reduce も自由自在,Transducer
- (思い切って Session 枠を取りにいっても良かった感 🤔)
 
© Chatwork 15
働くを
もっと楽しく、
創造的に
We are Hiring !!!
Chatwork 株式会社では、
フロントエンド,バックエンドのどちらでも、
関数型の考え方で設計にチャレンジしたい
エンジニアを 募集🔗
しています 🙌

More Related Content

What's hot

オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?
Moriharu Ohzu
 
オブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツオブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツ
増田 亨
 

What's hot (20)

関数プログラミング入門
関数プログラミング入門関数プログラミング入門
関数プログラミング入門
 
TDD のこころ
TDD のこころTDD のこころ
TDD のこころ
 
強いて言えば「集約どう実装するのかな、を考える」な話
強いて言えば「集約どう実装するのかな、を考える」な話強いて言えば「集約どう実装するのかな、を考える」な話
強いて言えば「集約どう実装するのかな、を考える」な話
 
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
 
イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意
 
Unified JVM Logging
Unified JVM LoggingUnified JVM Logging
Unified JVM Logging
 
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
「実践ドメイン駆動設計」 から理解するDDD (2018年11月)
 
MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法
 
オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?
 
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
 
GoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホンGoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホン
 
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
 
モジュールの凝集度・結合度・インタフェース
モジュールの凝集度・結合度・インタフェースモジュールの凝集度・結合度・インタフェース
モジュールの凝集度・結合度・インタフェース
 
やはりお前らのMVCは間違っている
やはりお前らのMVCは間違っているやはりお前らのMVCは間違っている
やはりお前らのMVCは間違っている
 
Pythonによる黒魔術入門
Pythonによる黒魔術入門Pythonによる黒魔術入門
Pythonによる黒魔術入門
 
オブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツオブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツ
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsug
 
イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)
 
マイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチマイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチ
 

関数型プログラミングのデザインパターンひとめぐり

  • 1. © Chatwork JSConf JP 2021 Chatwork 株式会社 CTO 室 / エンジニア採用広報 高瀬 和之 (@Guvalif) 関数型プログラミングの デザインパターンひとめぐり with Ramda.js
  • 2.   © Chatwork ■ 概要 🗒 Haskell ライクな関数型プログラミングを JavaScript にて行えるようにするライブラリ "Ramda.js" を (一部) 例にして、 関数型プログラミングにおいて頻出のデザインパターンをご紹介します。 ■ 関数型プログラミングに対する私の立ち位置 󰞹 - 良い設計を発見する ための、ベース知識として用いる - 安全な開発を実現する ための、勘所として用いる - これらは決してプログラミング言語に依存すること無く、 普遍的に応用可能 だと考える - つまり、ライトユーザーです  (コワクナイヨ! 2 まえおき
  • 3.   © Chatwork 3 純粋関数 - 関数型プログラミングの大原則 → 主役は "純粋関数" - 純粋関数とは? - 引数に同じ値を与えたら、常に同じ戻り値を返す関数のこと - なおかつ、副作用が存在しないもの - "参照透過" というキーワードで、ひとえに説明しても良い - なぜ純粋関数を用いるのか? - 副作用が存在しない ≒ 挙動が予測しやすい - 型システム を併用することで、予測可能性をさらに高める ことができる const add: (_0: number, _1: number) => number = (x, y) => x + y; const log: (_: string) => void = (s) => console.log(s); 純粋関数の例 (インプレイスな置き換えが可能) 純粋関数でない例 (外部から観測できない作用がある)
  • 4.   © Chatwork 4 カリー化 - "複数の引数を取る関数" と "単一の引数を取る関数" は、相互変換ができる - 理論的背景を知りたい方は ... → "積対象 指数対象 随伴" で Let's Google 🔍 - cf. 『圏論と Swift への応用』 by inamiy さん const add : (_0: number, _1: number) => number = (x, y) => x + y; const add_: (_0: number) => (_1: number) => number = (x) => (y) => x + y; // 使い方が異なるだけで、効果は変わらない add(1, 2) === add_(1)(2);
  • 5.   © Chatwork 5 カリー化による設計上のメリット - 例) カリー化による環境の固定 - 同様に、Dependency Injection にも応用できる - ちなみに、Ramda.js では全ての関数が標準でカリー化 されている 🎉 const buildSendMessageRequest = ({ secret, roomId }: ChatworkEnv) => (body: string) => ({ method: 'POST', url: `https://api.chatwork.com/v2/rooms/${roomId}/messages`, headers: { 'X-ChatworkToken': secret, }, data: `body=${body}`, }); // builder: (body: string) => Request として、簡便に使いまわすことができる const builder = buildSendMessageRequest({ secret: 'XXXX', roomId: '123456789' });
  • 6.   © Chatwork 6 副作用との付き合い方 - "純粋関数が主役" という考え方より、副作用も観測可能にしたい ... - どうする? → 副作用をなるべくデータ型として表現する (≒ 作用を型で明示する) - 例) Tagged Union Type により、try - catch を用いずに異常系を表現する interface Left<T> { value: T; _tag: 'Left'; } interface Right<T> { value: T; _tag: 'Right'; } // 慣例的に、Left が異常系,Right が正常系を表す type Either<L, R> = Left<L> | Right<R>; function safeDiv( x: number, y: number, ): Either<Error, number> { if (y === 0) { return left(new Error('/ by 0 !')); } return right(x / y); } ファクトリ関数とする
  • 7.   © Chatwork 7 作用を便利に扱うための演算群 - 慣例的に、ジェネリック関数の3つ組を考えることが多い - 理論的背景を知りたい方は ... → "計算効果 Monad" で Let's Google 🔍 - cf. 『Notions of Computation and Monads』 by Eugenio Moggi // Haskell では 'pure' という名で知られる // cf. https://ramdajs.com/docs/#of of: <T>(value: T) => M<T> // Haskell では 'fmap' という名で知られる // cf. https://ramdajs.com/docs/#map map: <A, B>(f: (_: A) => B) => (_: M<A>) => M<B> // Haskell では 'bind' という名で知られる // cf. https://ramdajs.com/docs/#chain chain: <A, B>(kf: (_: A) => M<B>) => (_: M<A>) => M<B>
  • 8.   © Chatwork 8 各演算群の直感的な意味 - 例) M<_> を Promise<_> で置き換えると ... - ※ 厳密には、Promise だと良い性質を持たないので注意! // "値" を "作用のある値" に変換する (※ もっとも自明な変換を用いる) const of: <T>(value: T) => Promise<T> = (value) => Promise.resolve(value); // "関数" を "作用のある関数" に変換する const map: <A, B>(f: (_: A) => B) => (_: Promise<A>) => Promise<B> = (f) => (promise) => promise.then(f); // "途中で作用をもつ関数" を "作用のある関数" に変換する const chain: <A, B>(kf: (_: A) => Promise<B>) => (_: Promise<A>) => Promise<B> = // .then メソッドの性質から、map と実装が変わらない (ややつまらない実装) (kf) => (promise) => promise.then(kf);
  • 9.   © Chatwork of の役割: "値" から ... "作用のある値" への変換 map の役割: "関数" から ... "作用のある関数" への変換 chain の役割: "途中で作用をもつ関数" から ... "作用のある関数" への変換 map(f): (_: M<A>) => M<B> f: (_: A) => B 9 各演算群の直感的な意味 (図解版) X A B M<A> M<B> map M<X> of: (_: X) => M<X> M<A> M<B> chain(kf): (_: M<A>) => M<B> kf: (_: A) => M<B> A chain
  • 10.   © Chatwork 10 ADT と Catamorphism - Union Type や Tuple Type を (複合的に) 用いたデータ型を、ADT と呼ぶ - ADT = Argebraic Data Type の意 → 代数的データ型 - ADT に対しては、自然な分解と変換 を定義することができる → 代表例が Catamorphism - 理論的背景を知りたい方は ... → "F 始代数" で Let's Google 🔍 const either = <L, R, T>(lmap: (_: L) => T) => (rmap: (_: R) => T) => (m: Either<L, R>): T => { // 対称性を強調するために、それぞれの場合分けを明示的に記述 if (m._tag === 'Left') { return lmap(m.value); } if (m._tag === 'Right') { return rmap(m.value); } };
  • 11.   © Chatwork 11 Catamorphism の応用例 - 例) Either に対する、統一的なエラーハンドリング - ちなみに、配列に対する reduceRight も Catamorphism だったりする - いわゆる "畳み込み" と呼ばれる操作は、Catamorphism として一般化できる const lmap = (e: Error) => e.name; const rmap = (x: number) => `${x}`; const handler: (_: Either<Error, number>) => string = either(lmap)(rmap); // try - catch 方式と、大きく使い勝手が変わらない console.log(handler(safeDiv(N / M)));
  • 12.   © Chatwork 12 複合的なユースケース → "ブラックジャック" の例 - Qiita に参考記事を掲載しているので、ぜひ一読してみてください 🗒 - cf. 『JavaScript で (なるべく) 関数型にブラックジャックを実装する』 by Guvalif ×
  • 13.   © Chatwork 13 現実的なユースケース in Chatwork → リリース基盤のバックエンド実装 - Ramda.js をフル活用して、CLI や CRON を実装しています 🗒 - 余談) 公式技術ブログに記事化したいと思い続けて、一年半が過ぎた 😇 const rejectEmptyEffect = R.ifElse( R.isEmpty, () => Promise.reject(new Error('スナップショットに含めるべきファイルが存在しません')), Promise.resolve.bind(Promise), ); const uploadFileEffect = createUploadFileEffect(s3Env, birdcageEnv); const uploadFileWithLoggingEffect = R.pipe( R.tap<File>((file) => console.log(`Uploading: ${file.name}`)), uploadFileEffect, );
  • 14.   © Chatwork 14 まとめ - 関数型プログラミングの考え方には、実用上も便利 なものが多い 👌 - なおかつ、理論的背景 もしっかりしている 📖 - Haskell を使うのは大変でも、Ramda.js だったらライト に始められる 🚀 - 時間があったら取り扱いたかったトピック: - 合成可能な setter と getter の組,Lens - Applicative による Validation の一般化 - map も、filter も、reduce も自由自在,Transducer - (思い切って Session 枠を取りにいっても良かった感 🤔)
  • 15.   © Chatwork 15 働くを もっと楽しく、 創造的に We are Hiring !!! Chatwork 株式会社では、 フロントエンド,バックエンドのどちらでも、 関数型の考え方で設計にチャレンジしたい エンジニアを 募集🔗 しています 🙌