この記事の動機
以前から少し興味あったJuliaをちょっといじってみようと思ってその導入にChatGPTを使ってみた。
すると、独特な仕様などがありそれらを聴きながら少し深堀していくと色々な背景が見えてきたのでQiita記事に纏めようと思った。
ChatGPTに関してはJuliaに関してはかなりいい線のコードを出してくれる(と思う、まだ多くのコードを試してないが...)
仕様の解説やそれらの理由、過去の技術からのつながりなどもかなりのレベルで出してくると感じた、その一方でそれらをまとめることは難しくこの点は人がする必要があると感じた。
一つのことをやりたい、調べたいならグーグル先生よりはよっぽど優秀であると感じた。
この記事制作に使ったプロンプトはこれ、https://chatgpt.com/share/684f51ef-6bf8-8012-bae3-8c21749ea264 である。
Juliaとは
Julia は、MIT の研究者たちによって 2009 年頃から開発が始まり、2012 年に公開された、科学計算に特化した汎用プログラミング言語である。過去の言語、特に Python や C の「いいとこ取り」を目指しつつ、数値計算・並列処理・メモリ管理の面でも高いパフォーマンスを発揮するよう最適化されている。
Julia は Jupyter Notebook の前身である IPython Notebook と共に発展してきた経緯があり、文法も Python に近くスクリプト実行であり、Python ユーザーにとって比較的親しみやすい構文となっている。
また、科学技術計算に特化した言語であることを示す特徴として、配列のインデックスが 1 から始まる「1 インデックス方式」が採用されている。これは Fortran、Matlab、R などの科学計算向け言語と共通する仕様である。
このうち Fortran は古くからある汎用言語だが、現代的な開発環境との親和性は高くない。また、Matlab や R はアプリケーション内の DSL(ドメイン固有言語)的な側面が強く、拡張性や言語的自由度に課題がある。
これに対して Julia は、現代的な構文と強力なパッケージエコシステムを備えた、汎用性と専門性を兼ね備えた科学技術計算向け言語といえる。
インストール方法と実行
公式サイト https://julialang.org/downloads/ に各種OS、Windows/macOS/Linux、があるのでそれをダウンロードしてインストール方法に従えばインストールできる。
その後、ターミナル/コマンドプロンプトでJuliaと打って以下のようにREPL(対話環境)が起動すればインストール成功である。
Hello Worldは以下のようであり、ファイル保存して$ julia hello.jl
としても実行できる。
$ julia
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.10.5 (2024-08-27)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/ |
julia>
julia> println("Hello! World!")
Hello! World!
プロジェクトとパッケージ管理
Julia は Pkg という、言語内部に完全統合された特徴的なパッケージ管理システムを持っている。
これは、パッケージ管理システムを持たず外部ビルドツールに依存する Fortran や C/C++、
言語専用のパッケージ管理はあるが外部ツールとして扱われる Python や JavaScript/TypeScript とは対照的である。
Rust の Cargo のように言語と密接に統合されたスタイルに近いが、Julia の Pkg は REPL やスクリプト内で直接制御できる点でさらに一歩踏み込んでおり、パッケージ管理とプロジェクト環境の切り替えが非常にシームレスである。
$ cd my_julia/
my_julia$ julia
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.10.5 (2024-08-27)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/ |
julia>]
# Julia REPLでは `]` を押すだけで「パッケージモード」に切り替わり、次のように表示されます:
(@v1.10) pkg> activate ./
Activating new project at `~/local/github/my_julia`
(my_julia) pkg> generate "MyPackage" # 空の自分用パッケージ制作
Generating project MyPackage:
MyPackage/Project.toml
MyPackage/src/MyPackage.jl
(my_julia) pkg> add Example # hello()のみの外部パッケージ
Resolving package versions...
Updating `~/local/github/my_julia/Project.toml`
[7876af07] + Example v0.5.5
Updating `~/local/github/my_julia/Manifest.toml`
[7876af07] + Example v0.5.5
(my_julia) pkg> BS
#「パッケージモード」から抜ける際は"Backspace"を押す
julia> exit()
とすると、Manifest.toml
、Project.toml
が作られる。(npmのpackage.json
、package.lock.json
のようなもの)
パッケージを追加しない場合Project.toml
は作られずManifest.toml
だけが作られる。
activate
しないと共通ディレクトリ.julia/environments/vX.XX
に作られる。
インストールしたパッケージはホームディレクトリ以下の~/.julia/packages
に置かれる、これは複数のプロジェクトで同じものを使っていた場合のストレージ節約のためである。
これらのパッケージはJuliaの「ブラックボックスにしない」という哲学のもと基本的にJuliaで書かれているのでアルゴリズムの確認などにも適している。
インプレース形式関数 f!(...)
Juliaの特殊な記法としてインプレース記法がある。
これは第一引数を関数の返り値とみなすものであり関数名の後に!
をつける。
function f(u, p, t)
return -p * u
end
function f!(du, u, p, t)
du[1] = -p * u[1]
end
ここで注意すべきは、inplace 形式の関数において du
や u
が「値型」ではなく「参照型(たとえば配列)」として渡される必要があるという点です。
これは返り値であるdu
に結果を代入したいが**「値型」(組み込み型)**だと上書きされるが「参照型」だとオブジェクトの差す値が変わって外からもその値が参照できるためである。
引数が値かオブジェクトで挙動が変わるのはPython/JavaScriptあたりではよく知られているが明示的に型を持ちポインタがあるCなどでは多少、奇異に映るかもしれない。
しかしCではポインタ(参照)渡しによって返り値代わりに使う手法は多用されているのでそれをイメージするといいかもしれない。
また数値計算では同じ形式を数万~場合によっては数億回以上繰り返すので同じオブジェクトを使いまわすのが速度に影響する。inplace形式を使うと同じオブジェクトの使いまわしができて速度的な利点もある。
このように、!
を付けた関数(in-place 関数)は、大量の数値計算やODEのように「計算結果をメモリ再利用しながら繰り返す」場面で極めて有効です。
実際、Julia の多くの数値計算ライブラリ(DifferentialEquations.jl
など)はこの慣習に従って関数の !
付き版と !
なし版を提供しています。
多様な関数の記法
Juliaには多様な関数の書き方が可能であり上記のような例ではラムダ関数やそれに近い
一行で書ける関数に使える短縮記法(short-form function definition)がある。
f = (u, p, t) -> -p * u
f(u, p, t) = -p * u #
また、Juliaには最後の評価値を返すという仕様があるので以下でも動く
function f(u, p, t)
-p * u # ← returnなしでもOK(最後の式が戻り値)
end
ラムダや短縮記法は簡単な関数やクロージャーを定義するときには便利であるが
複雑なロジックを実装する際はfunction ... end
を使うべきである。
しかし、その際にreturn
なしでの暗黙的な値の返却は意図しない式の評価がされる恐れがあるので明示的にreturn
することを強く推奨する。
Juliaの特徴と他言語との関係
Juliaには以下のような設計上の特徴がある。
ここでは、それらを他言語との関係の観点から解説する。
- 関数型プログラミング
- 演算子オーバーロード
- 動的型付け
- JIT (Just-In-Time) コンパイル
- In-place形式
- 言語内部に完全に包括されたパッケージ管理システム
Pkg
関数プログラミング・演算子オーバーロード・動的型付け
数学・科学計算において関数を引数に渡せる関数型プログラミングは重要である。
これは、関数が第一級オブジェクトであることを意味し、Lisp、Scheme、Haskellなどの関数型言語で発展してきた。
それらの言語では副作用のない「純粋関数」が基本であり、数学的関数に近く、科学計算との親和性が高い。
また、科学計算において数式をコードに忠実に写すには、演算子オーバーロードは不可欠である。
この機能を言語レベルで導入した代表的な例はC++で、比較的新しい言語技術である。
動的型付け言語ではJulia以外ではPythonがサポートしており、科学計算で広く使われている。
JIT (Just-In-Time) コンパイル
動的型付けの代表例であるPythonはインタプリタ方式であり、実行速度は遅い。
その対策としてNumPyなどはCで実装されているが、速度と表現力の両立は難しい。
JuliaはJIT方式を採用しており、C/C++に匹敵する速度を持つとされる。
動的型にもかかわらず、型推論によって最適化された機械語を生成できるのが、JuliaのJITの強みである。
In-place形式
科学計算では同じ構造を何度も更新するため、変数の再生成は非効率である。
Cなどではポインタ渡しでメモリを使い回すが、Juliaでは !
の付いたIn-place形式で明示的に第一引数が更新対象であることを明示的に示すことができ、メモリ効率の良いコードが書ける。
Pkg
パッケージ管理システム
Juliaの Pkg
は、外部ライブラリをFFIやABIを通じて扱う従来言語の手法とは異なり、
言語内に完結したパッケージ管理と依存関係解決を提供する。
パッケージのソースはすべてJuliaで書かれており、そのまま読んだり修正したりできる。
さらに、プロジェクトごとに環境を分離管理できるため、Pythonのような依存性の混乱が起きにくい。
また、ソースコードレベルで改変できる(確認できる)ということは数値レベルでの微妙な挙動に敏感な科学計算系には必須であり、Juliaはそれに応えられる仕様になっている。
まとめ
JuliaはPythonのように簡単にかけて実行できる科学計算特化型の汎用言語である。そのライブラリは、特に科学計算系ではほかの言語と遜色ないレベルであり内部実装まで言語レベルに内包してる点で特徴的である、これは科学計算に必要な細かい確認・調整には大きなアドバンテージである。
内部技術的には関数型プログラミングによる柔軟な記述やCのような低レベルでの効率化などが考慮され使われいる。それらを実現するために動的型付けの記述性と型による効率化を両立するためのJITコンパイルが導入されている。