Share
Embed
Transcript
120 slides
-
Cotents • 導入 • 複雑性との戦いと抽象の役割 • tagless-finalによるDSL構築:抽象の具現化 • クエリDSLと最適化: Layered
Final • DSLの活用:AI × ソフトウェアプロダクトライン • まとめ 2 -
もともとはキレイなドメインモデル • 明確な責務 • 予測可能な状態遷移 • 理解とメンテナンスが容易 case class FiscalPeriod(
unit: FiscalPeriodUnit, year: Int, periodNumber: Int, var isOpen: Boolean,...) { def open(): Unit def close(): Unit } ビジネスモデルを明確に反映して信頼性が高い状態 -
あるとき変更される case class FiscalPeriod( unit: FiscalPeriodUnit, year: Int, periodNumber: Int,
var isOpen: Boolean,...) { def open(): Unit def close(): Unit def temporaryClose(): Unit } このときは正しかった変更 四半期末の速報のため、一時的に期間を終了 したい 監査が終了するまで、完全に閉鎖することは 避けたい 他部署のレポート待ちのため、一時的に閉じた い -
モデルの外部に判断が漏れる case class FiscalPeriod( unit: FiscalPeriodUnit, year: Int, periodNumber: Int,
var isOpen: Boolean,...) { def open(): Unit def close(): Unit def temporaryClose(): Unit } temporaryClose は使ってよ さそう 呼び出すタイミングは状況による なぁ close()を呼び出すだけでよくな い? Uses temporaryClose() Uses both with if logic temporaryClose() Dev A Dev B Dev C そして、構造がルールを定義しなく なると、意味は失われ始める -
意味を保持できなくなったモデル case class FiscalPeriod( unit: FiscalPeriodUnit, year: Int, periodNumber: Int,
var isOpen: Boolean,...) { def open(): Unit def close(): Unit def temporaryClose(): Unit } temporaryClose() を使う 両方を使い分ける temporaryClose()は無視 Dev A Dev B Dev C 使われ方がバラバラ 意味を守れない構造 if (isPreliminary) period.temporaryClose() if (isAudited) period.close() else period.temporaryClose() period.close() -
DSL(ドメイン特化言語)とは 12 • 特定のドメインにおける問題解決を目的として設計された言語 • 汎用プログラミング言語とは異なり、そのドメインの概念や用語、規則を直 接表現できる • ドメインの意味を直接表現可能にする「言語的構造」を与える ◦
誤った意味を記述できないようにしてしまえばいい DSLは「意味を埋め込む型安全なインターフェース」とも言える 後述するTagless-Finalはその一つの実装技法 -
Frederick P. Brooks, JR. 著, 滝沢徹, 牧野祐子, 富澤昇 訳. 『人月の神話』.
丸善出版 (2014) Brooks, Fred P. (1986). “No Silver Bullet — Essence and Accident in Software Engineering”. Proceedings of the IFIP Tenth World Computing Conference: 1069–1076. -
すべてのソフトウェア構築には、 本質的作業として抽象的なソフトウェア 実体エンティティを構成する複雑な概念構造体を作り上げること、 およ び、 偶有的作業としてそうした抽象的実在をプログラミング言語で表現 し、 それをメモリスペースとスピードの制約内で機械言語に写像するこ とが含まれている。 Frederick
P. Brooks, JR. 著, 滝沢徹, 牧野祐子, 富澤昇 訳. 『人月の神話』, 第16章. 丸善出版 (2014) “ 銀の弾丸はない ー ソフトウェアエンジニアリングの本質と偶有的事項 17 -
本質的複雑性 (Essential) 偶有的複雑性 (Accidental) 定義 解くべき問題そのものが持つ、避けられ ない複雑性 問題解決の過程で、道具や技術的制約によっ て付随的に生まれてしまった複雑性 発生原因
問題領域(ドメイン) 技術的制約や解決方法、設計、実装の選択に 起因 作業 頭の中で概念構造体をつくる 実装過程 避けられるか 避けられない 回避・削減可能 例 税制ルール、為替変動 依存地獄、冗長なコード 対応方法 深いドメイン理解、正しい抽象化 適切な技術選定、設計、ツール等 本質的複雑性と偶有的複雑性 “私が本質と言っているソフトウェア構築の部分は頭の 中で概念構造体を作ることであり、 偶有的事項と言って いる部分はインプリメンテーション過程のことなのだ。 ー「人月の神話」第17章より 18 -
抽象 21 Abstraction is about digging deep into a situation
to find out what is at its core making it tick. Another way to think of it is about stripping away irrelevant details, or rather, stripping away details that are irrelevant to what we’re thinking about now. 抽象とは、ある状況を深く掘り下げ、物事の核心にある「それを動かしている 本質」を見つけ出すことです。別の言い方をすれば、今考えていることにとっ て重要でない詳細を取り除いていく、あるいは不要な詳細をそぎ落としてい くことです。 “ Cheng E. The Joy of Abstraction: An Exploration of Math, Category Theory, and Life. Cambridge University Press; 2022. -
抽象の役割 23 在庫管理 ドメイン 受注ドメイン 顧客管理 ドメイン 顧客管理 コンテキスト 在庫管理
コンテキスト 受注コンテキスト 問題空間 解決空間 現実世界 ドメインモデル 抽象 問題空間と解決空間の橋渡しをする -
安定依存の原則と安定度・抽象度等価の原則 24 Entities Controller G atew ay Presenter Use Cases
UI W eb Devices D B External Interfaces Martin, R. C. (2017). Clean Architecture: A Craftsman's Guide to Software Structure and Design. Prentice Hall. 依存方向 -
抽象の特性:本質の抽出 (意味の核を形成する) 27 最小化 Minimalism 蒸留 Distillation 表現力 Expressiveness 意味の核以外のノイズを除去する
ドメイン知識から本質概念を抽出し、DSLの語彙として定義する 必要なものだけを公開し、余計なものを隠す DSLに含める演算・型・操作を厳選。誤用を減らし、意味の純度を保つ 意味が明快に表現されており、意図が伝わる ドメインの意図が直接・簡潔にDSLコードに表現される -
抽象の特性:構造に作用する 28 合成可能性 Composability 拡張可能性 Extensibility 直交性 Orthogonality 合成可能な構造であること。意味が壊れずに繋がる DSLの操作が安全に合成可能であること(例:式のネスト、関数合成)
他の構成要素と独立に機能し、影響を与え合わない DSL内の各構成要素が独立しており、干渉しにくい 将来的な拡張に強く、壊れにくい 将来の新しいビジネス要件に対応する拡張ポイントを設計する -
余計な詳細の例:偶有的複雑性の一例 31 class InventoryProcessor { private val validator: StockValidator =
{ try { new StockValidatorImpl(...) } catch { case ex: InitializationException => // ... 何らかのエラーハンドリング throw ex } } def process(orders: List[Order]): Unit = { orders.foreach { order => validator.validateStock(order.productId, order.quantity) } } // ... } -
余計な詳細の例:偶有的複雑性の一例 32 class InventoryProcessor { private val validator: StockValidator =
{ try { new StockValidatorImpl(...) } catch { case ex: InitializationException => // ... 何らかのエラーハンドリング throw ex } } def process(orders: List[Order]): Unit = { orders.foreach { order => validator.validateStock(order.productId, order.quantity) } } // ... } 余計な詳細 偶有的な複雑さ -
余計な詳細の例: ノイズを除去する 33 class InventoryProcessor[F[_]: Monad](validator: StockValidator[F]) { def process(orders:
List[Order]): F[Unit] = orders.traverse_(order => validator.validateStock(order.productId, order.quantity) ) } 依存注入 副作用の抽象化 複雑さが減ると AIにも嬉しい! tagless-finalパターン というやつ -
• 型安全な埋め込み言語を構築する手法(≠tagless-fnalパターン) • DSLの型付けがホストとなる言語のそれに帰着する ◦ 型検査アルゴリズムを実装しなくてよい • HOASが使える ◦ ホストとなる言語によってスコープ安全性が担保される
• 言語コンポーネントの合成が可能 • パーサーを作らなくて良い • ホスト言語のエコシステムが使える(IDE、AIエージェント等々) tagless-finalとは 36 Claude Codeでtagless プログラムを書くのがアツい! -
メタ言語 (meta language) 対象言語とメタ(ホスト)言語 37 対象言語 (object language) 表現したい言語、DSL DSLの埋め込み先となる言語
e.g.) Scala, Rust 構文はメタ言語の関数機構、型付けはメタ言語の型システムの帰着 -
言語を構成する三要素:構文、型付け規則、意味論 38 インタフェース インタプリタ インタプリタ インタプリタ 構文&型付け規則 意味論 複数の意味をもた せることができる
Symantics: Syntax, Typing rules and Semantics -
対象言語を設計する: Typing Rules 50 Syntax Typing Rules 上が成立していれば 下を導くことができる n
が整数であるならば n は整数(Int) の型を持つ -
対象言語を設計する: Typing Rules 51 Syntax Typing Rules 上が成立していれば 下を導くことができる n
が整数であるならば n は整数(Int) の型を持つ e1 が整数であるならば かつ e2 が整数であるならば e1 + e2 は整数(Int) の型を持つ -
対象言語を設計する: Semantics 54 n は n と評価される e1 は n1
に評価される Syntax Typing Rules Semantics -
対象言語を設計する: Semantics 55 n は n と評価される e1 は n1
に評価される かつ e2 は n2 に評価される Syntax Typing Rules Semantics -
対象言語を設計する: Semantics 56 n は n と評価される e1 は n1
に評価される かつ e2 は n2 に評価される このとき、 n1 + n2 の結果は n となる Syntax Typing Rules Semantics -
対象言語を設計する: Semantics 57 n は n と評価される e1 は n1
に評価される かつ e2 は n2 に評価される このとき、 n1 + n2 の結果は n となる ならば、 e1 + e2 は n と評価される Syntax Typing Rules Semantics -
エンコーディング手順:まずは構文を関数で表現 64 Syntax Typing Rules trait ArithSym { def int(n:
Int): Int def add(e1: Int, e2: Int): Int } -
エンコーディング手順:まずは構文を関数で表現 65 Syntax Typing Rules trait ArithSym { def int(n:
Int): Int def add(e1: Int, e2: Int): Int } このままでは 世界がごっちゃ になる -
エンコーディング手順:私たちの世界の型を表現する 67 Syntax Typing Rules trait ArithSym { def int(n:
Int): Int def add(e1: Int, e2: Int): Int } -
エンコーディング手順:私たちの世界の型を表現する 68 Syntax Typing Rules trait ArithSym { type Repr[T]
def int(n: Int): Int def add(e1: Int, e2: Int): Int } 世界を表現する型 Repr をつくる -
エンコーディング手順:私たちの世界の型を表現する 69 Syntax Typing Rules trait ArithSym { type Repr[T]
def int(n: Int): Repr[Int] def add(e1: Repr[Int], e2: Repr[Int]): Repr[Int] } Reprで包む -
エンコーディング手順:私たちの世界の型を表現する 70 Syntax Typing Rules trait ArithSym { type Repr[T]
def int(n: Int): Repr[Int] def add(e1: Repr[Int], e2: Repr[Int]): Repr[Int] } Z が Repr[Int] Reprで包む -
エンコーディング手順:私たちの世界の型を表現する 71 Syntax Typing Rules trait ArithSym { type Repr[T]
def int(n: Int): Repr[Int] def add(e1: Repr[Int], e2: Repr[Int]): Repr[Int] } -
注意: Polymorphic Lifting メタ言語から対象言語の世界への流入経路は慎重に設計する 多相の値を受け取ると、意図しない値が世界に紛れ込む 73 def int(n: Int): Repr[Int]
def lit[A](n: A): Repr[A] 非推奨 OK 世界の入口 世界の入口 -
ユーザープログラム 変数機構はホスト言語のものがそのまま使える 76 class Example[S <: ArithSym](val sym: S): import
sym._ def example1: Repr[Int] = add(int(1), int(2)) -
ユーザープログラム 変数機構はホスト言語のものがそのまま使える 77 class Example[S <: ArithSym](val sym: S): import
sym._ def example1: Repr[Int] = add(int(1), int(2)) Reprの世界になっている -
意味論の実装:設計をなぞるだけ 80 Semantics object EvalInterpreter extends ArithSym { type Repr[T]
= T def int(n: Int): Repr[Int] = n def add(e1: Repr[Int], e2: Repr[Int]): Repr[Int] = e1 + e2 } 素のまま扱うため表現型は T と置く -
意味論の実装:複数のインタプリタ 81 case class Writer[A](log: List[String], value: A) { def
map[B](f: A => B): Writer[B] = Writer(log, f(value)) def flatMap[B](f: A => Writer[B]): Writer[B] = { val next = f(value) Writer(log ++ next.log, next.value) } } object WriterInterpreter extends ArithSym { type Repr[T] = Writer[T] def int(n: Int): Repr[Int] = Writer(List(s"Creating int: $n"), n) def add(e1: Repr[Int], e2: Repr[Int]): Repr[Int] = { val v1 = e1.value val v2 = e2.value Writer(e1.log ++ e2.log :+ s"Adding $v1 and $v2", v1 + v2) } } Writerモナド この世界ではWriterとして扱う インタプリタを切り替えても、ユーザープログラムには影響しない -
ユーザープログラムを実行する 83 object Main extends App { // Eval example
val exampleEval = new Example(EvalInterpreter) val result1 = exampleEval.program println(result1) // 3 // Writer example val exampleWriter = new Example(WriterInterpreter) val result2: Writer[Int] = exampleWriter.program println(s"Result: ${result2.value}") // 3 println("Logs:") result2.log.foreach(println) // Creating int: 1 // Creating int: 2 // Adding 1 and 2 } def program: sym.Repr[Int] = { val a = int(1) val b = int(2) add(a, b) } ユーザープログラムは同じ インタプリタ インタプリタ -
言語を合成できる、tagless-finalならね 85 trait ArithSym { type Repr[T] def int(n: Int):
Repr[Int] def add(...(略) } trait LamSym { type Repr[T] def lambda[A, B](f: Repr[A] => Repr[B]): Repr[A => B] def apply[A, B](f: Repr[A => B], x: Repr[A]): Repr[B] } object EvalInterpreter extends ArithSym with LamSym { type Repr[T] = T def int(n: Int): Repr[Int] = n def add(e1: Repr[Int], e2: Repr[Int]): Repr[Int] = e1 + e2 def lambda[A, B](f: A => B): A => B = f def apply[A, B](f: A => B, x: A): B = f(x) } -
みんな大好きクエリ言語 89 • Nested Relational Calculus(NRC)ベース ◦ Bag(多重集合)を扱うリレーショナル計算体系 for x
in Product where x.price > 10000 yield x -
ドメインロジックのなかでクエリがしたい 期首の為替レートで計算した明細の一覧がほしい 91 for record in Transactions: yield ( {
department: record.department, amount: if record.currency != "JPY" then record.amount * ExchangeRate[record.currency][FiscalPeriod.startMonth] else record.amount, scenario: CurrentScenario } ) -
Layered Final 96 • tagless-finalをベースにしつつ、多層のインタプリタ(意味)を与え最適化を施せるように したアプローチ • Ad-hoc Rewriteだと拡張や保守が困難であるため、管理しやすいようにパイプライン化 DSL
Frontend (Symantics) Expression Lifting Fusion Algebraic Normalizer Composition Engine Code Generator Execution Backend (Interpreter) classic module classic module Fusion IR Any Bridge e.g.) Writer, Provenance -
Symantics for内包表記に相当するモナディックな操作 97 trait QuerySym: type Repr[+_] def table[A](name: String):
Repr[List[A]] def map[A, B](src: Repr[A], f: A => B): Repr[B] def flatMap[A, B](src: Repr[A], f: A => Repr[B]): Repr[B] def filter[A](src: Repr[A], p: A => Boolean): Repr[A] def union[A](left: Repr[A], right: Repr[A]): Repr[A] def empty[A]: Repr[A] (以下略) -
Fusion的中間表現 GADT(一般化代数的データ型)を使って、DSLの項の型安全性を守る 98 enum Fusion[+A]: case Yield(value: A) case Map[S,
B](source: Fusion[S], f: S => B) extends Fusion[B] case FlatMap[S, B](source: Fusion[S], f: S => Fusion[B]) extends Fusion[B] case Filter[S](source: Fusion[S], p: S => Boolean) extends Fusion[S] case Union(left: Fusion[A], right: Fusion[A]) extends Fusion[A] case Table[A](name: String) extends Fusion[List[A]] case Empty -
Fusion的中間表現 IRに変換するインタプリタ 99 object ExpressionLifter extends QuerySym: type Repr[+A] =
Fusion[A] def table[A](name: String): Fusion[List[A]] = Fusion.Table[A](name) def map[A, B](src: Fusion[A], f: A => B): Fusion[B] = Fusion.Map(src, f) def flatMap[A, B](src: Fusion[A], f: A => Fusion[B]): Fusion[B] = Fusion.FlatMap(src, f) def filter[A](src: Fusion[A], p: A => Boolean): Fusion[A] = Fusion.Filter(src, p) def union[A](l: Fusion[A], r: Fusion[A]): Fusion[A] = Fusion.Union(l, r) def empty[A]: Fusion[A] = Fusion.Empty -
Fusion Normalizer 代数的性質として正規化する (正規化規則を精密に組み合わせるの結構大変なので) 100 操作 代数法則 (Rewrite Law) Map
Fusion map(map(xs, f), g) ⇒ map(xs, g ∘ f) Filter Fusion filter(filter(xs, p1), p2) ⇒ filter(xs, p1 ∧ p2) Union Identity union(xs, empty) ⇒ xs FlatMap Stability flatMap(xs, f) ⇒ flatMap(normalize(xs), f) -
Fusion Normalizer 正規化もインタプリタとして構成する 101 class NormalizerInterpreter(base: QuerySym { type Repr[+X]
= Fusion[X] }) extends QuerySym: type Repr[+A] = Fusion[A] def table[A](name: String): Fusion[List[A]] = normalize(base.table(name)) def map[A, B](src: Fusion[A], f: A => B): Fusion[B] = normalize(base.map(src, f)) def flatMap[A, B](src: Fusion[A], f: A => Fusion[B]): Fusion[B] = normalize(base.flatMap(src, f)) def filter[A](src: Fusion[A], p: A => Boolean): Fusion[A] = normalize(base.filter(src, p)) def union[A](l: Fusion[A], r: Fusion[A]): Fusion[A] = normalize(base.union(l, r)) def empty[A]: Fusion[A] = normalize(base.empty) (以下略) -
taglessのインタプリタはそれぞれが別のReprを持っている 102 trait QuerySym: type Repr[+_] def table[A](name: String): Repr[List[A]]
def map[A, B](src: Repr[A], f: A => B): Repr[B] def flatMap[A, B](src: Repr[A], f: A => Repr[B]): Repr[B] def filter[A](src: Repr[A], p: A => Boolean): Repr[A] def union[A](left: Repr[A], right: Repr[A]): Repr[A] def empty[A]: Repr[A] object EvalInterpreter extends QuerySym: type Repr[+A] = () => List[A] def table[A](name: String): () => List[List[A]] = () => List(db.getOrElse(name, Nil).asInstanceOf[List[A]]) def map[A, B](src: () => List[A], f: A => B): () => List[B] = () => src().map(f) (以下略) class WriterInterpreter( base: QuerySym { type Repr[+X] = Fusion[X] }) extends QuerySym: type Repr[+A] = WriterFusion[A] private inline def add(msg: String, xs: List[String]) = xs :+ msg def table[A](name: String): WriterFusion[List[A]] = WriterFusion(base.table(name), List(s"table($name)")) def map[A, B](src: WriterFusion[A], f: A => B): WriterFusion[B] = WriterFusion(base.map(src.ir, f), add("map", src.log)) (以下略) type Repr[+A] = () => List[A] type Repr[+A] = WriterFusion[A] -
層を繋ぐ 103 trait LayerBridge[F[+_], G[+_]]: def embed[A](fa: F[A]): G[A] def
project[A](ga: G[A]): F[A] いわゆる自然変換 (natural transformation) embed project F の世界 G の世界 A A Embedding-Projection Pair構造 各層が局所的に変換を定義するだ けで積層可能に 変換 変換 -
順序付き積層: 層ごとの世界を繋いで、インタプリタを積み上げる 層ごとの変換を安全に合成して、柔軟にパイプライン全体を組み立てる 105 class LayerStack[Base[+_], Current[+_]]( val bridge: LayerBridge[Base,
Current] ): def addLayer[Next[+_]](next: LayerBridge[Current, Next]): LayerStack[Base, Next] = new LayerStack(LayerBridge.compose(bridge, next)) def build(baseInterpreter: QuerySym { type Repr[+A] = Base[A] }): QuerySym = new QuerySym: type Repr[+A] = Current[A] def table[A](name: String): Repr[List[A]] = bridge.embed(baseInterpreter.table(name)) def map[A, B](src: Repr[A], f: A => B): Repr[B] = bridge.embed(baseInterpreter.map(bridge.project(src), f)) (以下略) 世界を渡る インタプリタを生成 -
順序付き積層: 層ごとの世界を繋いで、インタプリタを積み上げる 層ごとの変換を安全に合成して、柔軟にパイプライン全体を組み立てる 106 class LayerStack[Base[+_], Current[+_]]( val bridge: LayerBridge[Base,
Current] ): def addLayer[Next[+_]](next: LayerBridge[Current, Next]): LayerStack[Base, Next] = new LayerStack(LayerBridge.compose(bridge, next)) def build(baseInterpreter: QuerySym { type Repr[+A] = Base[A] }): QuerySym = new QuerySym: type Repr[+A] = Current[A] def table[A](name: String): Repr[List[A]] = bridge.embed(baseInterpreter.table(name)) def map[A, B](src: Repr[A], f: A => B): Repr[B] = bridge.embed(baseInterpreter.map(bridge.project(src), f)) (以下略) G へのembed F へのproject -
順序付き積層: 層ごとの世界を繋いで、インタプリタを積み上げる 層ごとの変換を安全に合成して、柔軟にパイプライン全体を組み立てる 107 object FusionLayerStack: val stack = LayerStack[Fusion,
Fusion](LayerBridge.identity[Fusion]) .addLayer(NormalizerBridge) .addLayer(WriterBridge) -
Code Generator, Evaluator 例えばSQLを生成する 109 object FusionToSQL: private var aliasCounter
= 0 private def nextAlias(): String = aliasCounter += 1 s"t$aliasCounter" def toSQL[A](ir: Fusion[A]): String = ir match case Fusion.Table(name) => s"SELECT * FROM $name" case Fusion.Map(src, f) => val alias = nextAlias() val srcQuery = toSQL(src) s"SELECT ${mapFunctionToSQL(f)} FROM ($srcQuery) AS $alias" (以下、省略) -
Layered Final • クエリ言語のような最適化や変換、あるいはトレーシングのためのロギング やProvenanceを後付けしたいときに便利 • 層を追加しても削除しても、DSLそれ自体やユーザープログラムに影響は 無い • ユーザープログラムやDSL自体を早期にビジネス検証できる旨味は健在
• 実用面ではアプリエンジニアというよりは、アーキテクトや基盤エンジニアが 担当すると良さそう 110 -
可変の差別化要素 ソフトウェアプロダクトライン(Software Product Line; SPL) 112 共通の製造手段を使用し、共有されたソフトウェア資産から類似のソフトウェアシステムのコレクションを 作成するための手法 液晶パネル 電源
OS 基本UI … 共通基本構造・コンポーネント サイズ (32型〜75型) 画質 (フルHD、4K、8K) 国ごとの仕様 (電圧、放送方式、言語対応) … -
可変の差別化要素 ソフトウェアプロダクトライン(Software Product Line; SPL) 113 認証認可 API基盤 メッセージング・ 通知
ロギング・監査 … 共通基本構造・コンポーネント 課金ルール 定額/従量/階段式/ 複合課金 配賦・按分ルール 配賦基準/多段階配賦/ 配賦対象/しきい値 承認フロー 予算承認/期中修正ルール/ 金額閾値分岐 … ワークフロー エンジン レポート エンジン 配賦・仕訳生成 ルールエンジン 料金計算 ロジック 給与計算 エンジン クエリエンジン キャッシュ層 マルチテナント 抽象レイヤ 権限制御 IP制限 集計/スナップ ショット 認証認可 … ドメイン基盤 技術基盤 シナリオ・シミュレーション ドライバー定義/シナリオ分岐 /変動係数 共通の製造手段を使用し、共有されたソフトウェア資産から類似のソフトウェアシステムのコレクションを 作成するための手法 -
エンタープライズプロダクトの価値 115 A社にとっての 価値 B社にとって の価値 D社にとって の価値 C社にとって の価値
プロダクト のコア価値 ※事業戦略・プロダクト戦略によります -
似て非なるユースケース差分 • 汎用エンジンに寄せる → 重厚長大化 • 個別・個社開発に寄せる → 保守難易度高 116
可変ポイントの形式化 ドメインエキスパート+AIによる開発 ガバナンスレイヤー 共通資産レイヤー 可変ポイントレイヤー DSLエキスパート ドメインエキスパート AIエージェント 基盤エンジニア 安全なコードしか書けない AIエージェントが生成してもある程 度の信頼性は担保 -
まとめ 119 • 曖昧さが偶有的複雑性の温床になる • tagless-finalアプローチはメタ言語の力を借りて型安全にDSLを作れる • tagless-finalで作った言語は抽象の特性を持たせやすい ◦ 安定度・抽象度等価の原則
◦ 安定依存の原則 • インタプリタを後付けできるので ◦ 早期のビジネス検証に最適 ◦ あとから最適化しやすい • Layered Final で実用性の高い最適化諸々パイプラインを構築 • AI駆動&SPLでtagless-final DSLを導入することで、 開発の出力を最大化