次世代言語Flixにみる設計原則とデータ指向アプリケーションの可能性
こんにちはこんばんわ、ログラスで開発のあれやこれやをしているknih(Kenichi Suzuki)です。
クリスマスいかがお過ごしでしょうか?本稿はログラスのプロダクトアドベントカレンダー最終日の投稿になります。
さて、強い組織であるためには情報の伝導率が重要です。意思決定をするにしても現状がどうなっているのか、決定されたことがどうなったのか、情報が錯綜して見通しが立たない状況では何をするにしても動きが悪くなってしまうでしょう。
ソフトウェアも同じく、型の伝導率が重要です。型を無理やり剥がしていては、リファクタリングするときの正確性が損なわれて、改修を重ねるたびにいつの間にか品質が落ちてしまいます。
高信頼で安全、それでいて簡潔で認知負荷の低い、すなわち複雑性に立ち向かえるソフトウェアを作っていくためにはどうしたらよいでしょうか。そのヒントになるやもしれないと、私が近年注目しているFlixというプログラミング言語についてご紹介させていただきます。
Flixの特徴
プログラミング言語大合戦のこの時代に、なぜ新たに言語を作らなければならなかったのでしょうか。
Flix言語を開発した理由として、Flixのリード開発者であるMagnus Madsen先生は次のように述べています。
プログラミング言語の世界で、何かが欠けていると感じていました。それは、主に関数型言語でありながら、命令型プログラミングを完全にサポートし、さらに論理プログラミングのひねりを加えた言語です。OCaml、Haskell、Scalaの最高のアイデアを取り入れた、デザイン空間におけるユニークなポイントにある言語です。
純粋関数型言語を使うと、破壊的操作を伴う命令的な記述のほうが高速なのに、記述が煩わしく感じることがあります。
関数型をしていると、命令的に記述したほうが高速ですし、手っ取り早いケースがあります。
論理プログラミング機構は、制約ソルバーを使って論理問題を簡単に解きたいときに重宝します。後述しますが、なんとDatalog(どこかの監視犬ではないです)が組み込みで使えてしまうのです。
私は、明確に表現されたアイデアと設計目標の理念に基づいた新しい言語を求めていました。単なる機能の寄せ集めではありません。私の考えでは、言語は万人のすべてになろうとするべきではありません。いくつかのコアとなるアイデアに集中し、それを得意とする方が良いのです。個人的には、代数的なデータ型、パターンマッチ、高階関数など、関数型プログラミングの強力な機能を備えた、使うのが楽しくなるような言語が欲しかったのです。
上記のコメントやOnward!2022[1](SPLASH国際会議内のプログラムの1つ)で、Flixは opinionated な言語であると暗に述べています。opinionateとは意見を強調する、頑固である、等の意味がありますが、例えばGo言語は1つの opinionatedな言語であると言えるでしょう。Goにはクラスや実行時例外がなく、比較的ミニマムな機能セットが提供されています。言語の複雑さがプログラマーの生産性とコードの保守性の障害になるという主張なのでしょう。
以降では、Flixの設計思想や機能の特徴について述べていきます。
(本記事で紹介する以外にもまだまだ特徴はあります!まだ実験段階ですが、型レベルプログラミングも言語機能としてサポートしています。)
Hello World
定番のHello WorldはFlixで書くと以下のようになります。
def hello(): Unit \ IO =
println("Hello World!")
構文はScalaに似ています。hello
関数は戻り値がないため戻りの型は Unit
ですが、Flixは後述するエフェクトシステムを備えており、副作用を関数シグニチャとして表現できます。
println
関数によって IO
エフェクトが発生するので、型とエフェクトのアノテーションはUnit \ IO
となります。
エフェクトを直接扱えることによって、純粋な関数と不純な関数を明確に切り分けることが可能です。
品質の高いソフトウェアを作るには、純粋な関数の面積を増やすことが大事です。テストの観点からみても、不純な関数よりも純粋な関数のほうがテストがしやすく、品質コントロールがしやすくなります。
サクッと動かしてみたい、という方はPlaygroundで試すこともできます。
設計思想の特徴
Flixの設計は、明確さ、信頼性、および開発のしやすさを重視しています。
いくつかの特徴がありますが、ここでは以下について簡単にご紹介します。
- Simple Made Easy
- null値なし
- 純粋なコードと不純なコードを分離する
Simple Made Easy
“Simple Made Easy”[2]とは、ClojureのRich Hickey氏の言葉ですが、氏に触発されたこの原則は、Flix言語の核心と言えるでしょう。
この思想は、シンプルさを追求することで、ソフトウェアの複雑さを減少させるという考え方に基づいています。これは、複雑さがソフトウェアのバグやエラー、メンテナンスの困難さの主要な原因であるという認識から生まれました。
Flixは構文的に直感的であり、意味論的も曖昧さを排除した明確な意味を持つようになっているため、振る舞いを予測しやすく、デバッグやメンテナンスが容易です。
また、関数型プログラミングへの慣れは必要であるものの、習得しやすい言語に仕上がっています。個人的には1関数型言語としてみても、関数型プログラミングを学ぶのに適していると感じます(教材が豊富かどうか問題はさておき)。
null値なし
nullは、その発明者であるトニー・ホーア卿によって「10億ドルの間違い(Billion Dollar Mistake)」[3]と呼ばれ悪名高いですが、いまなお多くの言語でnullが扱われています。
Flixは、他の関数型プログラミング言語と同様に、null値を持たず、その代わりに、存在しない可能性のある値を明示的に表現するOptionデータ型に依存しています。この解決策は理解しやすく、うまく機能し、あの忌々しいNullPointerExceptionが起こらないことを保証します。
しかし例外があります。FlixはJavaとの相互運用性を備えており、FlixとJavaとの境界ではnullチェックが必要となるでしょう。なお、Java側のコードを呼び出す際にnull値を渡せるようにするための特別なnullがNull型の値として導入されています。
純粋なコードと不純なコードを分離する
Flix言語の設計思想において、純粋なコードと不純なコードの分離は中心的な概念の一つです。この分離は、副作用を持つコード(不純、impure)と副作用のないコード(純粋、pure)を明確に区別することを意味します。
-
純粋なコード(Pure Code): 純粋な関数や式は、外部の状態に依存せず、与えられた同じ入力に対して常に同じ結果を返します。これらは副作用を持たず、予測可能で再利用しやすいです。
-
不純なコード(Impure Code): 不純な関数や操作は、外部の状態に依存したり、外部の状態に影響を与えたりすることで副作用を生じます。例えば、データベースへの書き込み、グローバル変数の変更などがあります。
この思想を支えるために後述するエフェクトシステムが導入されています。
機能的な特徴
高階カインド+拡張可能レコードというだけで感激ですが、多相エフェクトシステムを持ちつつ、Datalogが第一級でサポートされています。SML#等のSQLを組み込んだ言語はありますが、Datalogというのは衝撃です。
紹介したい機能がたくさんありますが、多相エフェクトシステム、第一級Datalogについて述べていきたいと思います!
他にも、拡張可能レコードや高階カインド、構造的チャネル、純粋リフレクション等々、高品質なプログラムを作るための機構が多々ありますので、ぜひ公式ページ等をチェックしてみてください。
多相エフェクトシステム
多相エフェクトシステムは、関数の副作用を型レベルで表現し、追跡するためのシステムです。副作用とは、関数が持つ外部の状態に対する影響のことで、例えばIO操作、例外の発生、グローバル変数へのアクセスなどが含まれます。
Flixの多相エフェクトシステムでは、これらの副作用を明示的に型に注釈することにより、コードの振る舞いをより明確にし、エラーを防ぎ、より安全なプログラムを書くことを可能にします。
エフェクトは、型システムによって検査され、関数の合成やモジュール間のインタラクションにおいて、副作用が正確に伝播される用に設計されています。
PureとImpureなプログラムの例
/// 純粋な関数
def pureAdd(x: Int32): Int32 = x + 1
/// 副作用がある関数
def impureAdd(x: Int32): Int32 \ IO =
println("x = ${x}");
x + 1
上記では、 impureAdd
関数内でコンソール出力を行っているためにIOエフェクトが発生しています。
一方、pureAdd
では1
を加算した値を返しているだけなので副作用がありません。
副作用のないpureな関数のエフェクトは明示的に \ {}
とも書けます。
/// 純粋な関数
def pureAdd(x: Int32): Int32 \ {} = ...
上記の純粋関数と副作用のある関数を、同じ関数内で呼び出すと、その関数には副作用が伝播してしまうので、注意してください。
def someFunc(): Int32 \ IO = // 内部で副作用のある関数を呼び出している
let r1 = pureAdd(100); // pure
let r2 = impureAdd(200); // impure
r1 + r2 // pure
この副作用の伝播はテストを難しくするため、ソフトウェア全体で、可能な限り純粋関数の割合を増やすことが大事です。
多相エフェクト
Flixではエフェクト多相な関数を定義することができます。以下では ef
がエフェクト多相を表す変数になっています(型変数ならぬエフェクト変数とでも呼ぶべきか)。
def map(f: a -> b \ ef, xs: List[a]): List[b] \ ef =
match l {
case Nil => Nil
case x :: xs => f(x) :: map(f, xs)
}
上記関数を前述のimpureAdd
関数に適用させると、結果型は List[Int32] \ IO
になります。
第一級Datalog制約とデータ指向の応用
Flixの他の言語にみられない特徴として、第一級の値としてDatalogプログラムが扱えるというものがあります。Datalogは論理プログラミング言語の一種で、特にデータベースと関係データのクエリに焦点を当てた言語で、SQLと同じぐらい歴史があります(登場から半世紀近く経っている)。
Data + Prologという名前の由来になっている通り、Prolog言語から派生しており、そのシンプルな構文と強力な表現力で、複雑なクエリやデータの推論ルールを定義するのに適しています。
FlixではDatalogプログラムを第一級として扱えるため、Datalog値をローカル変数に格納したり、関数の引数として渡したり、データ構造に格納したり、他のDatalogの値と合成したり、好き放題です。Datalog自体が1つの言語であり、Flixに組み込まれて第一級として扱えるため、FlixはDatalogに対するメタプログラミング言語とみなすことができます。
以下の例では、与えられたグラフに対して推移閉包を探索しています。ルールを記述するだけで関数が定義出来てしまいます。
def transitive(g: List [( Int32 , Int32 )]): List [( Int32 , Int32 )] =
let db = inject g into Edge ;
let pr = #{
Path (x, y) :- Edge (x, y).
Path (x, z) :- Path (x, y) , Edge (y, z).
};
query db , pr select (x, y) from Path (x, y)
Datalogを活用することで、複雑な分析クエリやルールベースの推論、アクセス制御ポリシー等の記述が容易に正確になります。特に大規模なデータセットから、特定の有用なデータを抽出したり、なんらかのサプライチェーンにおいて、最適な配送ルートを抽出したり、ワークフローがモデル化されていれば、ボトルネックが起きやすいフローを特定することも可能になります。
まとめ
Flixは、関数型に慣れてさえしまえばとても分かりやすく、実践的な設計思想を持つ言語です。現段階では普及しているとは言えず、プロジェクトやプロダクト全体に導入するには勇気が必要かもしれません。ですが、JVM上で動くので、使用場面を限定すれば実践導入も十分可能であると思います。
本稿ではいろいろな機能を盛り込んだ言語ではなく、明確な思想を持った opnionated な言語から、その設計思想や機能の一端を見てきました。特にFlixの言語としてのこだわりは明確で分かりやすいために、習得が容易でした。もし、ソフトウェアの複雑さに対抗するために、なにか言語を導入しようとするなら、その設計思想に重心を置いてチームやプロジェクトに取り込むと良いかもしれません。
まだまだ紹介したい機能や発信したい考えはありますが、またの機会とします。
こんな発表もするよ
ログラスでは、2024年1月にTECH PLAY様のData Conference、3月にObject-Oriented Conferenceでの登壇・スポンサーを予定しております。関数型やデータ基盤に関するお話をする予定です。
ご興味ある方はぜひともご参加くださいませ。
エンジニア絶賛募集中
事業が成長すると、解くべき技術課題の難易度も上がっていくのは世の理です。
ログラスの開発は品質や生産性に気を配っているので、その分、スケールはチャレンジングで、皆様のお力添えがますます必要です。
良い景気は良いコードから、ということでご興味を持った方は私と握手。
どうぞ素敵なクリスマスをお過ごしくださいませ。そして良いお年を。
Discussion