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
Step by Stepで学ぶ、ADT(代数的データ型)、モナドからEffect-TSまで
Search
Tech Leverages
May 13, 2024
Technology
1
14k
Step by Stepで学ぶ、ADT(代数的データ型)、モナドからEffect-TSまで
TSKaigi2024で、テックリードの竹下が発表した
セッション
のスライドです。
※竹下は、一般社団法人TSKaigi Asscosiationの代表理事も兼任しております
Tech Leverages
May 13, 2024
Tweet
Share
More Decks by Tech Leverages
See All by Tech Leverages
We Are PdE!! 〜高価値なプロダクトを作れるようになるための勉強会〜
leveragestech
1
560
Prisma Typed SQLのススメ
leveragestech
1
84
今日から始める技術的負債の解消
leveragestech
3
530
ドキュメントとの付き合い方を考える
leveragestech
2
200
開発者体験を向上させる ボトムアップな組織改善
leveragestech
1
240
市場価値の高いエンジニアを 目指そう!!
leveragestech
2
66
より快適なエラーログ監視を目指して
leveragestech
5
1.7k
絶賛設計中!参画者のエンゲージメントを最大化する体験重視のオンボーディング
leveragestech
1
120
SREが強化するべき組織のケイパビリティ
leveragestech
0
100
Other Decks in Technology
See All in Technology
Taming you application's environments
salaboy
0
200
20241120_JAWS_東京_ランチタイムLT#17_AWS認定全冠の先へ
tsumita
2
310
The Role of Developer Relations in AI Product Success.
giftojabu1
0
150
電話を切らさない技術 電話自動応答サービスを支える フロントエンド
barometrica
1
150
CysharpのOSS群から見るModern C#の現在地
neuecc
2
3.6k
100 名超が参加した日経グループ横断の競技型 AWS 学習イベント「Nikkei Group AWS GameDay」の紹介/mediajaws202411
nikkei_engineer_recruiting
1
170
初心者向けAWS Securityの勉強会mini Security-JAWSを9ヶ月ぐらい実施してきての近況
cmusudakeisuke
0
140
B2B SaaSから見た最近のC#/.NETの進化
sansantech
PRO
0
960
OCI Network Firewall 概要
oracle4engineer
PRO
0
4.2k
Adopting Jetpack Compose in Your Existing Project - GDG DevFest Bangkok 2024
akexorcist
0
120
DynamoDB でスロットリングが発生したとき/when_throttling_occurs_in_dynamodb_short
emiki
0
270
Platform Engineering for Software Developers and Architects
syntasso
1
530
Featured
See All Featured
Fireside Chat
paigeccino
34
3k
Git: the NoSQL Database
bkeepers
PRO
427
64k
Statistics for Hackers
jakevdp
796
220k
It's Worth the Effort
3n
183
27k
The Invisible Side of Design
smashingmag
298
50k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
Producing Creativity
orderedlist
PRO
341
39k
Unsuck your backbone
ammeep
668
57k
Practical Orchestrator
shlominoach
186
10k
Designing on Purpose - Digital PM Summit 2013
jponch
115
7k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
506
140k
VelocityConf: Rendering Performance Case Studies
addyosmani
325
24k
Transcript
Confidential © 2023 Leverages Co., Ltd. Step by Stepで学ぶ、ADT(代数的データ 型)、モナドからEffect-TSまで
2024/5/11 テクノロジー戦略室 室長 竹下 義晃
© 2023 Leverages Co., Ltd. レバレジーズ株式会社 竹下 義晃 テクノロジー戦略室室長 一般社団法人TSKaigi
Association 代表理事、一般社団法人 Japan Scala Association理事 2009年に東京大学大学院農学生命科学科を修了 芸者東京 株式会社で、アプリen、バックエンドen、フロントen、アプリマーケターを経て 2020年にレバレジーズに入社 フルスタックの技術力を背景に、レバレジーズ社の技術の向上とエンジニア組織文化の構築に取り組む。また、 ScalaMatsuriやTSKaigiの運営にも関わり、技術コミュニティを盛り上げる活動も行っている。
© 2023 Leverages Co., Ltd. 関数型と聞いて どんな印象がありますか? 3
© 2023 Leverages Co., Ltd. 関数型ってこんなもんなのか • すでに身近に使っている ◦ Arrayのfilter,
map, flatMap, reduce • 関数型の概念も、根底にはなんらかの課題があり、それを 解決している • 私はJava,C# => Scalaで関数型に入門しました 4
© 2023 Leverages Co., Ltd. 目次 1. このセッションでの説明範囲 2. 今日のサンプルコード
3. 代数的データ型: リッチな型表現 4. Either: 成功/失敗を型で表現 5. TaskEither: 非同期処理も巻き込む 6. Effect-TS: すべてを一つに 5
© 2023 Leverages Co., Ltd. このセッションでの説明範囲 01 6
© 2023 Leverages Co., Ltd. 説明すること • ADT(代数的データ型), Either(Result型), TaskEither,
Effect-tsを、型の表 現力に焦点を当ててStep by Stepで説明していきます 7 説明しないこと • 関数型言語や、モナドの理論的な話 • Effect-tsのCollection MonadやIO Monadなどの側面 • 各ライブラリ(fp-tsやEffect-TS)の詳しい使い方 • コードや設計自体の良し悪し
© 2023 Leverages Co., Ltd. 今日のサンプルコード 02 8
© 2023 Leverages Co., Ltd. ユーザー登録 9 e-mailとpasswordで、ユーザー登録を行い、完了メールを送る // 入力値のチェック
function validateInput( param: { email: string, password: string}): boolean // DBにレコードを保存 function save(param: {email: string, password: string}): { id: string} // 登録完了メールの送信 function sendRegisteredEmail(email: string): void
© 2023 Leverages Co., Ltd. 普通のコード 10 function createUser( param:
{email: string, password: string}): {id: string} { if( !validateInput(param)) { throw new Error("バリデーションエラー ") } let result: { id: string } = { id: "" }; try { result = save( param ); } catch { throw new Error("すでに登録済みのemailです。") } sendRegisteredEmail(param.email); return result }
© 2023 Leverages Co., Ltd. 普通のコード 11 function createUser( param:
{email: string, password: string}): {id: string} { if( !validateInput(param)) { throw new Error("バリデーションエラー ") } let result: { id: string } = { id: "" }; try { result = save( param ); } catch { throw new Error("すでに登録済みのemailです。") } sendRegisteredEmail(param.email); return result } 戻り値がboolの場合、true/falseどちらが成功パ ターンになるか人依存 どんなエラーが投げられるかよくわからない
© 2023 Leverages Co., Ltd. 問題点 12 • ソースコードを読まないと関数の挙動がわからな い
◦ 成功時はUserIDが帰って来そうだが、エラー 時は? • ifとかtryとか入り乱れる
© 2023 Leverages Co., Ltd. 代数的データ型 03 13
© 2023 Leverages Co., Ltd. 代数的データ型(ADT)とは 14 • 直積型と、直和型を組み合わせて表現される型 直積
type User = {name : string, age: number} = nameとageの直積型 直和 type SNS = “Google” | “Facebook” | “X”
© 2023 Leverages Co., Ltd. ADTの導入 15 function validateInput(param: {
email: string; password: string; }): | "Success" | { type: "InvalidEmail" ; message: string } | { type: "InvalidPassword" ; message: string } function save(param: { email: string; password: string; }): | { id: string } | { type: "DuplicateEmail" ; message: string } function sendRegisteredEmail(email: string): void {}
© 2023 Leverages Co., Ltd. ADTの導入 16 function validateInput(param: {
email: string; password: string; }): | "Success" | { type: "InvalidEmail" ; message: string } | { type: "InvalidPassword" ; message: string } function save(param: { email: string; password: string; }): | { id: string } | { type: "DuplicateEmail" ; message: string } function sendRegisteredEmail(email: string): void {} どんなエラーが返ってくるかまで、表現できるよう になった
© 2023 Leverages Co., Ltd. ADTの導入 17 function createUser(param: {
email: string; password: string }): | { id: string } | { type: "ValidationError"; field: string; message: string } | { type: "AlreadyRegistered"; message: string } { const validationResult = validateInput(param); if (validationResult !== "Success") { return { type: "ValidationError", field: validationResult.type === "InvalidEmail" ? "email" : "password", message: validationResult.message, }; } const saveResult = save(param); if (saveResult.type === "AlreadyRegistered") { return { type: "AlreadyRegistered", message: saveResult.message, }; } sendRegisteredEmail(param.email); return saveResult; }
© 2023 Leverages Co., Ltd. ADTの導入 18 function createUser(param: {
email: string; password: string }): | { id: string } | { type: "ValidationError"; field: string; message: string } | { type: "AlreadyRegistered"; message: string } { const validationResult = validateInput(param); if (validationResult !== "Success") { return { type: "ValidationError", field: validationResult.type === "InvalidEmail" ? "email" : "password", message: validationResult.message, }; } const saveResult = save(param); if (saveResult.type === "AlreadyRegistered") { return { type: "AlreadyRegistered", message: saveResult.message, }; } sendRegisteredEmail(param.email); return saveResult; } 別々のフィールドをもたせられるようにもなってい る
© 2023 Leverages Co., Ltd. 改善点と問題点 19 改善点 • 型定義を見るだけでなんとメソッドの挙動がわか
る ◦ = 宣言的になった 問題点 • 依然としてif文多い ◦ 結果のチェックミスによるバグが発生する
© 2023 Leverages Co., Ltd. Either 04 20
© 2023 Leverages Co., Ltd. Eitherとは 21 • Either<Left, Right>の型定義を持つMonad
◦ Right=正しいので、成功の型を表す • Result<Success>や、Result<Success, Failure>と同じ 使われ方 成功したか、失敗したかを型で表現できるようにしたもの
© 2023 Leverages Co., Ltd. Eitherの導入 22 import { Either,
left, right } from "fp-ts/lib/Either" ; function validateInput(param: { email: string; password: string; }): Either< | { type: "InvalidEmail" ; message: string } | { type: "InvalidPassword" ; message: string }, void > function save(param: { email: string; password: string; }): Either<{ type: "AlreadyRegistered" ; message: string }, { id: string }> function sendRegisteredEmail(email: string): void
© 2023 Leverages Co., Ltd. Eitherの導入 23 function createUser(param: {
email: string; password: string }): Either< | { type: "ValidationError"; field: string; message: string } | { type: "AlreadyRegistered"; message: string }, { id: string } > { const validationResult = validateInput(param); if (isLeft(validationResult)) { return left({ type: "ValidationError", field: validationResult.left.type === "InvalidEmail" ? "email" : "password", message: validationResult.left.message, }); } const saveResult = save(param); if (isLeft(saveResult)) { return left({ type: "AlreadyRegistered", message: saveResult.left.message, }); } sendRegisteredEmail(param.email); return saveResult; }
© 2023 Leverages Co., Ltd. Eitherの導入 24 function createUser(param: {
email: string; password: string }): Either< | { type: "ValidationError"; field: string; message: string } | { type: "AlreadyRegistered"; message: string }, { id: string } > { const validationResult = validateInput(param); if (isLeft(validationResult)) { return left({ type: "ValidationError", field: validationResult.left.type === "InvalidEmail" ? "email" : "password", message: validationResult.left.message, }); } const saveResult = save(param); if (isLeft(saveResult)) { return left({ type: "AlreadyRegistered", message: saveResult.left.message, }); } sendRegisteredEmail(param.email); return saveResult; } 成功/失敗はisRight/isLeftで判定すれば良い
© 2023 Leverages Co., Ltd. 改善点と問題点 25 改善点 • 型で成功失敗までわかるようになった
• 条件判定も画一化した 問題点 • 依然としてif文多い • むしろ冗長になっている
© 2023 Leverages Co., Ltd. pipeの導入 26 import { Either,
left, right, flatMap, tap, mapLeft } from "fp-ts/lib/Either"; import { pipe } from "fp-ts/function"; function createUser(param: { email: string; password: string }) { const convertValidationError = (error: { type: "InvalidEmail" | "InvalidPassword"; message: string; }) => (...) const convertSaveError = (error: { message: string }) => (...) return pipe( pipe( validateInput(param), mapLeft(convertValidationError)), flatMap(() => pipe( save(param), mapLeft(convertSaveError) )), tap(() => sendRegisteredEmail(param.email)) ); }
© 2023 Leverages Co., Ltd. pipeの導入(型の確認) 27 type CreateUserValidationError =
{ type: "ValidationError"; field: string; message: string } type ValidateInputError = | { type: "InvalidEmail"; message: string } | { type: "InvalidPassword"; message: string }
© 2023 Leverages Co., Ltd. pipeの導入 28 import { Either,
left, right, flatMap, tap, mapLeft } from "fp-ts/lib/Either"; import { pipe } from "fp-ts/function"; function createUser(param: { email: string; password: string }) { const convertValidationError = (error: { type: "InvalidEmail" | "InvalidPassword"; message: string; }) => (...) const convertSaveError = (error: { message: string }) => (...) return pipe( pipe( validateInput(param), mapLeft(convertValidationError)), flatMap(() => pipe( save(param), mapLeft(convertSaveError) )), tap(() => sendRegisteredEmail(param.email)) ); } エラー型を戻り値の型に変換する処理として分離
© 2023 Leverages Co., Ltd. 改善点と問題点 29 改善点 • if文がなくなった
◦ データフローとして表現できるように • 処理を分割して、組み合わせやすくなった 問題点 • pipeの書き方/読み方に慣れる必要ある ◦ 型パズルの一端 • あれ?Promiseは? ◦ saveとか普通Promiseだよね?
© 2023 Leverages Co., Ltd. TaskEither 05 30
© 2023 Leverages Co., Ltd. Promise<Either<L,R>>だと? 31 async function add(a:
number, b: number): Promise<Either<string, number>> { return right(a + b); } async function div(a: number, b: number): Promise<Either<string, number>> { return right(a / b); } async function main() { pipe( await add(1, 2), // Compile Error! // Type 'Promise<Either<string, number>>' is not assignable // to type 'Either<unknown, unknown>' // flatMapが非同期関数を受け取れない flatMap(async (result) => await div(result, 0)) ) }
© 2023 Leverages Co., Ltd. TaskとTaskEither 32 • Task<T>は、”処理”を型で表現したMonad ◦
実態は() => Promise<T> ◦ Taskは作るだけではまだ実行されていない ◦ 作ってrunしたタイミングで初めて処理が実行 される • TaskEither<L,R>はTaskとEitherをMonad Transformerで合成した、新しいMonad
© 2023 Leverages Co., Ltd. TaskEitherの導入 33 import { TaskEither,
left, right } from "fp-ts/lib/TaskEither" ; function validateInput(param: { email: string; password: string; }): TaskEither< | { type: "InvalidEmail" ; message: string } | { type: "InvalidPassword" ; message: string }, void > function save(param: { email: string; password: string; }): TaskEither<{ type: "AlreadyRegistered" ; message: string }, { id: string }> async function sendRegisteredEmail(email: string): Task<void> {} EitherがTaskEitherに置き換わっただけ
© 2023 Leverages Co., Ltd. TaskEitherの導入 34 import { ...,
fromTask } from "fp-ts/lib/TaskEither"; import { Either } from "fp-ts/lib/Either"; import { pipe } from "fp-ts/function"; function createUser(param: { email: string; password: string }): Promise<Either< | { type: "ValidationError"; field: string; message: string } | { type: "AlreadyRegistered"; message: string }, { id: string } >> { ... const program = pipe( pipe( validateInput(param), mapLeft(convertValidationError)), flatMap(() => pipe( save(param), mapLeft(convertSaveError))), tap(() => fromTask(sendRegisteredEmail(param.email))) ); return program();
© 2023 Leverages Co., Ltd. TaskEitherの導入 35 import { ...,
fromTask } from "fp-ts/lib/TaskEither"; import { Either } from "fp-ts/lib/Either"; import { pipe } from "fp-ts/function"; function createUser(param: { email: string; password: string }): Promise<Either< | { type: "ValidationError"; field: string; message: string } | { type: "AlreadyRegistered"; message: string }, { id: string } >> { ... const program = pipe( pipe( validateInput(param), mapLeft(convertValidationError)), flatMap(() => pipe( save(param), mapLeft(convertSaveError))), tap(() => fromTask(sendRegisteredEmail(param.email))) ); return program(); Task => TaskEitherへの変換が発生
© 2023 Leverages Co., Ltd. 改善点と問題点 36 改善点 • PromiseもMonadに巻き込めた
問題点 • 型パズル感UP ◦ TaskをTaskEitherへ変換が必要
© 2023 Leverages Co., Ltd. Effect-TSの導入 06 37
© 2023 Leverages Co., Ltd. • Effect Monadを中心としたライブラリ ◦ 今回はTaskEitherの範囲での紹介
◦ Effect自体にはもっと豊富な機能があります • ScalaのZIOをTypeScriptにポートしてきたライブラリ Effect-TS 38
© 2023 Leverages Co., Ltd. • Effect<Success, Error = unknown,
Requirements = unknown> の型を持つ • Requirementsは今回触れませんが、副作用の分離にも 使えます Effect 39
© 2023 Leverages Co., Ltd. Effectの導入 40 import { Effect
} from "effect"; function validateInput(param: { email: string; password: string; }): Effect.Effect< void, | { type: "InvalidEmail"; message: string } | { type: "InvalidPassword"; message: string } > function save(param: { email: string; password: string; }): Effect.Effect< { id: string }, { type: "AlreadyRegistered"; message: string } > function sendRegisteredEmail(email: string): Effect.Effect<void> TaskEitherがEffectに置き換わっただけ
© 2023 Leverages Co., Ltd. Effectの導入 41 import { Effect,
pipe} from "effect"; function createUser(param: { email: string; password: string }): Promise<{ id: string; }> { ... const program = pipe( pipe( validateInput(param), Effect.mapError(convertValidationError)), Effect.flatMap(() => pipe( save(param), Effect.mapError(convertSaveError))), Effect.tap(() => sendRegisteredEmail(param.email)) ); return Effect.runPromise(program); }
© 2023 Leverages Co., Ltd. Effect generatorスタイル 42 import {
Effect, pipe} from "effect"; function createUser(param: { email: string; password: string }): Promise<{ id: string; }> { ... const program = Effect.gen(function* (_) { const validationResult = yield* _( pipe(validateInput(param), Effect.mapError(convertValidationError)) ); const saveResult = yield* _( pipe(save(param), Effect.mapError(convertSaveError)) ); yield* _(sendRegisteredEmail(param.email)); return saveResult; }); return Effect.runPromise(program); }
© 2023 Leverages Co., Ltd. まとめ 43
© 2023 Leverages Co., Ltd. 1. ADTで型の表現力上げたい 2. 成功/失敗などまで型で表現してバグを減らしたい 3.
Promiseも含めて一緒くたに扱いたい 4. そうだ、Effect-TS使おう 流れ 44
© 2023 Leverages Co., Ltd. • 今回は、Step by Stepのためfp-tsを使ってモナド周りを説 明しました
• 実践ではEffect-TSだけで機能は実現できます Effect-TSだけ使えばOK 45
© 2023 Leverages Co., Ltd. • バグが減る ◦ ロジックまで型で表現できて、コンパイラがチェックして くれる
• 拡張性、メンテナンス性UP ◦ 処理をより小さく分割し易い ◦ データフローの定義になり、より宣言的なコードになる 効能 46
© 2023 Leverages Co., Ltd. 是非、Effect-TSを使って より良いサービスを作ってください 47
© 2023 Leverages Co., Ltd. We are hiring! 40を超えるサービスを、 すべてオールインハウスで開発!
現在レバレジーズでは一緒に働いてくれる仲間を募集しています。 もし、ご興味ある方は、以下のリンクからご応募ください。 https://recruit.leverages.jp/recruit/engineer/