Upgrade to Pro — share decks privately, control downloads, hide ads and more …

堅牢なシステム開発を目指して 関数型で学ぶイミュータブル ワールド

aoyagi
August 12, 2022

堅牢なシステム開発を目指して 関数型で学ぶイミュータブル ワールド

関数型プログラミング の概要を 30 分で学びます
命令型(手続き型)プログラミング と比較しながら関数型の基礎となる イミュータブル の堅牢性をできる限り分かりやすくお伝えします

aoyagi

August 12, 2022
Tweet

More Decks by aoyagi

Other Decks in Programming

Transcript

  1. 本発表について • スライドでは細かな説明は省略しています • 難しい領域なので手探りですが間違い等あればご指摘ください • 発表時間: 30 分 •

    発表者 ◦ @aoyagi9936 ◦ クラウドエンジニア(主に Google Cloud を扱っています) ◦ 環境問題を解決する新規事業の立ち上げ&プラットフォーム構築中です ◦ メンバー大募集中です 2
  2. イミュータブルとは? • イミュータブル = 不変 ◦ 一度作成した後は変更は不可 ◦ 分かりやすく言えば Read

    Only(読み取り専用)や Constants(定数) • アプリケーション開発/インフラ構築のどちらの領域にも登場 ◦ 私の最初の出会いは Scala の不変コレクション → scala.collection.immutable ◦ その後インフラの勉強中に Chef を触り始め再び出会う → immutable infrastracture ◦ DB設計では CRUD で Update と Delete を排除した概念を知る → immutable data 3
  3. なぜイミュータブルが良い? ① • 月並みですがシステム開発はとても複雑です ◦ 『プログラミングにおける変数や処理結果 』『インフラ構築におけるパラメータ 』『データベー スにおけるデータモデル』など、システムは多種多様な要素の相互接続の上に成り立って います

    ◦ システムの規模が大きくなればなるほど1つ1つの要素も組み合わせも複雑になっていき ます ◦ 複雑性の定義として Complex と Complicated があり対処は異なりますが、ここでは詳し く触れないので気になる人は “カネヴィンフレームワーク” で調べましょう 4
  4. なぜイミュータブルが良い? ② • 私達は更新したくない ◦ “既にあるもの” を更新することは危険を伴います ▪ リスト構造更新時の Index

    out of range(要素数を超えた添字の指定) ▪ バージョンアップ時の Destructive Change(破壊的変更) ◦ イミュータブルでも変化は避けられません ▪ 1つ1つの要素がイミュータブルでも相互作用による変化は発生します ▪ ですが “新規作成” は “更新” よりも変化を予測しやすく安全です ◦ 既存の要素を更新する時、私達はその要素を “移行” しています 5
  5. 関数型プログラミングとは? • 関数を主軸にしたプログラミングを行うスタイルである(Wikipedia) ◦ ここからはプログラミングについてのみ触れます ◦ 関数型を学ぶには以下の概念を知る必要があります ▪ データを不変なものとして扱うこと •

    元の要素を変更せず、新しい要素を返します ▪ 同じ引数に対して必ず決まった値を返すこと(参照透過性) • 引数に与える変数への再代入は行わない • 関数自体が状態を持たないということではない 6
  6. 関数型プログラミングの堅牢性 • 関数型プログラミングは従来の命令型プログラミングに比べバグの入り込む余地が少ない記述 となります。以下はリスト内の偶数を出力するプログラムの例です。 val list = List[Integer](1, 2, 3,

    4, 5) list.filter(n => n % 2 == 0) .foreach(println(_)) filter は条件によって要素を抽出するメソッドです。意図も分かりやす く無駄のないシンプルな記述です。println の (_) は Scala 特有のシ ンタックスシュガーですが v => println(v) と書き直すこともできます。 val list = List[Integer](1, 2, 3, 4, 5) for (s <- list) { if (s % 2 == 0) { println(s) } } for の「ループする」命令と if の「もし〜だったら...する」命令を組み 合わせて1つの処理を表現しています。 構文を見ただけでは内容が分かりにくく「for 〜 if」「if 〜 println 」の 前後関係に拘束力もないため、間に処理が入った場合は複雑性が 増し「偶数で絞り込む」という目的が不透明になってしまいます。 Scalaによる命令型プログラミング Scalaによる関数型プログラミング 8
  7. Functional Programming Level 1 • 命令型から関数型へ ◦ 「まだ良さがよく分からない」という方はリスト構造を扱う際に関数型に置き換えてみることから 始めると良いと思います ◦

    様々なプログラミング言語も関数型プログラミングに対応しています ▪ C# → LINQ ラムダ式 ▪ Javascript / Typescript → ES6 以降のアロー関数(ES5 以前は Lodash.js / Unserscore.js を利用) ▪ Java SE8 → java.utils.function パッケージ、Stream API ▪ Python → iterator、generator、itertools ▪ Rust → Iterator、Closure ▪ Golang → 未対応だが 1.18 から Generics に対応したことで各種ライブラリも誕生 9
  8. Functional Programming Level 2 • 関数型が分かってくると全てを関数型で書きたくなってきます ◦ しかし、暫くするとその難しさに気がつきます ◦ 現実のシステムは多種多様な要素の相互接続によってビジネスを表現します

    ◦ 関数の外側で発生する相互作用は「副作用」と呼ばれます ▪ ファイルの入出力、データベースの更新、HTML(DOM)の更新、API通信など ◦ 繰り返しになりますがシステムには多種多様な要素があり相互に影響を与えます ◦ システム全体に関数型を取り入れるには言語だけでは “表現力” に限界を感じるようになりま す 10
  9. Functional Programming Level 3 • 関数型をより強固にするライブラリ/フレームワークの導入 ◦ React(Javascript / Typescript)

    ▪ React Hooks によりコンポーネントを関数型で扱うことができます ▪ React Hook Form や GraphQL Code Generator による Hooks 自動生成など、エコシス テムもとても充実しています ▪ Storybook との相性も良い ◦ ZIO(Scala) ▪ 純粋関数型プログラミングベースで非同期・並列処理を実現する Scala ライブラリ ▪ quill, tapir, sttp など既存のライブラリも続々と ZIO に対応しています ▪ DI や Test など大規模システム開発で必要な機能がオールインワンになっています 11
  10. React による イミュータブル ワールド import { useState } from 'react'

    import { useGetUsers } from ‘./api/generated/schema/’ const ExampleComponent = () => { const { msg, setMsg } = useState(‘’) const { data, fetching, error } = useGetUsers() return ( <> <p>{msg}<p> { fetching ? <p>loading…</p> : error ? <p>error</p> : data ? data.getUsers.map(v => <input type=’button’ value={v.name} onClick={(e) => setMsg(e.target.value} /> ) } </> ) } export const ExampleComponent 関数コンポーネント内は全て const (定数) によって イミュータブ ル に宣言されています。 自動生成しておいた useGetUsers Hooks を使って非同期通信 処理 (GraphQL) を 関数コンポーネント に接続しています。 GraphQL ライブラリである Urql では data (結果) , fetching (取 得中) , error (失敗) がそれぞれ格納されるので三項演算子を使 うことで結果に応じた表示の変更を シンプル に表現できます。 useState は状態を保存するための React の機能で ステートフ ル な値とそれを更新するための関数を取得できます。 更新用の関数を通すことに回りくどさを感じるかもしれませんが、 React は Hooks での更新を検知して 再レンダリング を スケ ジュール する際に更新前と同じ値の場合は処理を終了するなど の判断も担ってくれます。 12
  11. ZIO によるイミュータブルワールド object ExampleService { trait ExampleService { def getUsers():

    Task[List[User]] } def getUsers(): RIO[ExampleService, List[Users]] = ZIO.serviceWithZIO(_.getUsers()) lazy val live: ZLayer[Repo, Nothing, ExampleService] = ZLayer { for { repo <- ZIO.service[Repo] } yield new ExampleService { def getUsers(): Task[List[User]] = for { list <- repo.getUserList() res <- ZIO.succeed( list.map(v => User(v.id, s”${v.lastName} ${v.firstName}”))) } yield res }} } ZIO (2.0) の DI パターンです。 前半はこの オブジェクト を DI するための インター フェース と アクセサ です。 「lazy val live = ...」がこのオブジェクトの実装となりま す が val はイミュータブルな宣言であり不変です (ちなみ にミュータブルは var )。 Scala の for 式 は yield をつけることで変数を束縛し ながら最終的な値を生成するジェネレーターとして動作 させることができます。 getUsers() では ZIO によって依存性が注入された Repo を利用してデータを取得し加工 (姓と名を結合) して返却しています。 一連の流れを関数を繋げた形式で記述でき、余計な処 理が入る余地が限りなく少なくなっていることが分かり ます。 13
  12. 参考リンク • 関数型プログラミングについて ◦ Functional programming is finally going mainstream

    ◦ Functional Programming 101 • スライド内で紹介した技術 ◦ Fundamentals of functional programming with React ◦ Structuring ZIO 2 applications • 併せて学ぶと良いもの ◦ リアクティブ プログラミング ▪ Reactive X 14