Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Step by Stepで学ぶ、ADT(代数的データ型)、モナドからEffect-TSまで
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Tech Leverages
PRO
May 13, 2024
Technology
17k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Step by Stepで学ぶ、ADT(代数的データ型)、モナドからEffect-TSまで
TSKaigi2024で、テックリードの竹下が発表した
セッション
のスライドです。
※竹下は、一般社団法人TSKaigi Asscosiationの代表理事も兼任しております
Tech Leverages
PRO
May 13, 2024
More Decks by Tech Leverages
See All by Tech Leverages
Slackを「AIエージェントのHub」にする 〜 SlackbotとHolmesGPTで実現するAIOps
leveragestech
PRO
0
120
データ組織の転換期 一足飛びしない段階的戦略
leveragestech
PRO
0
220
並列化でチームのアウトプットを増やす
leveragestech
PRO
0
61
Engineering ManagerがAI時代に この先生きのこるには?
leveragestech
PRO
1
150
最新技術を"今は選ばない"という技術選定
leveragestech
PRO
0
610
毎⽇dumpされるDBにCDCは無⼒だっ た、、FederatedQueryで繋ぎ直した データ連携の試⾏錯誤
leveragestech
PRO
0
120
Tableauを活かすためにTableauに制約を設けた話
leveragestech
PRO
0
100
営業支援システムと歩んだ7年半の変遷
leveragestech
PRO
0
190
DMBOKを使ってレバレジーズのデータマネジメントを評価した
leveragestech
PRO
0
940
Other Decks in Technology
See All in Technology
2026-09-11 【Snowflake World Tour Tokyo 2026】Snowflakeを起点に、AI Agentが自律稼働し続ける未来へ / Driving AI Agents with Snowflake
civitaspo
0
670
Issue 駆動でスペシャリストの意図を届ける、AI 実装のアクセシビリティ向上
thkt
0
120
GoのInterface内部構造から学ぶ!最高パフォーマンスを出すコード設計
yappli_developers
0
110
DEFCON34-Write-up_HYCu-MYCu
daikiokazaki
0
160
AIによるクリエイティブ生成を行う上での試行錯誤
plaidtech
PRO
0
160
え、こんなに早く改修できるの?──新人エンジニアとスクラムマスターの2人が語る、AI×アジャイル開発の現場
ysasago
0
150
研究開発部の紹介 / Sansan R&D Profile
sansan33
PRO
5
25k
おい、エージェントを使って終わらせろ
nwiizo
1
590
アプリログインとWeb認証基盤をつなぐ ASWebAuthenticationSession 作法
shimastripe
1
330
人間はどの意思決定を手放せるのか
kawasima
15
7.1k
Cloudflare Workers 向けアプリを C# で構築する ~WASM Native AOT への道~
nenonaninu
0
100
エージェントはローカル、検証はMicroVM — Lambda MicroVMsでつくるServerless CI
fujioka6789
1
110
Featured
See All Featured
Optimising Largest Contentful Paint
csswizardry
37
4k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
520
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
The Curse of the Amulet
leimatthew05
3
14k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
240
The Cult of Friendly URLs
andyhume
79
7k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
330
Marketing to machines
jonoalderson
1
5.8k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
500
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.4k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
203
76k
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.3k
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/