Skip to content

動的言語型付けバトル

公開日:

Download PDF

スライドテキスト

Page 1

動的言語型付けバトル

Dynamic Language Typings Battle

pixiv Inc.
USAMI Kenta

1 2024-03-09

Page 2

動的言語とは何か
 動的プログラミング言語(dynamic programming languages)はプログ ラミング言語の一類型で、実行時(run-time)にプログラムの性質を制御したり、
実行時まで決定が遅延される性質が強いプログラミング言語の総称です。
 プログラムの実行時に何ができるか(どんな操作を許すか)は、プログラミング 言語設計の重要な一要素であり、言語によってかなり大きく異なるといえます。  一般に動的言語といわれる言語にはLisp族, シェルスクリプト, AWK, Perl, Smalltalk, Python, Ruby, JavaScriptなどがあります。動的言語に特徴的 な機能としてはevalがあります。これは文字列またはリストなどでソースコード を構築し、実行時に評価(evaluate)できると機能です。これは柔軟な処理がで きる反面、不用意な実装によって脆弱性を孕むリスクがあるため敬遠される傾向 にある一方、ライブラリ(スクリプト)の動的ロードなどは意識せず使われる場面も
多いでしょう。

Page 3

静的型付き vs 動的型チェック
 静的型付き言語(statically typed languages)は静的、つまりプログラム を実行する前に関数・変数・項などのとりうる型が決定される言語のことです。  誤解しないでおきたいのは、静的型のプログラミング言語というのはソース
コードに型をいちいち書かなければいけない言語、というわけではありません

 動的言語と呼ばれる言語では一般に静的には型を書かず(あるいは型付けの機 能そのものを持たず)実行時まで型が定まりません。動的言語では実行時に値の 型情報(型タグ)を検査することで安全に実行できます。このような性質を型な 、あるいは便宜的に動的型付け(dynamic typing)と呼ぶことがあります。

 静的型付き言語は収集した情報をもとに効率的かつ実行効率のよい実行ファイ ルにコンパイルできる余地があります。これは語弊のある表現ですが、コンパイル されたプログラムは鉄道のようなもので、バスと違って道路信号で頻繁に一時停 止させられることなく移動できるもの、のように考えられるかもしれませんね。

Page 4

静的型にもグラデーションがある
 静的型付き言語として分類される言語は多くありますが、ひとえに静的型付き
と言っても言語が提供する型システムには大きな違いがあります。
 型システムは、健全性(soundness)=正しく型がついていればエラーなく実行 できるか、完全性(completeness)=プログラムの性質を適切に型で表現でき
るか、といった指標で評価できます。
 一般に静的型付き言語と分類される言語でも、C++のように実行時型情報
(RTTI)を提供して動的な処理を可能にする言語があります。C#もdynamic型 を提供しています。これらの機能は、プログラムの柔軟性を上げることと引き換 えに健全性が低下し、コンパイル時に検出できないエラーのリスクが生じます。  また、代数的データ型(ADT)という型同士を式のように扱える型も考えられま す。これをサポートする言語の型システムでは、静的な型として簡潔かつ柔軟な
コード記述が可能になります。

Page 5

型推論の完全性
 静的型付き言語と呼ばれる言語の中にも、型を明示しなくてもコンパイラがコー
ドをきちんと調べ上げることで、静的な型を与えてくれるものがあります。

val add : int -> int -> int = <fun>

# let add x y = x + y;;

val id : 'a -> 'a = <fun>

# let id a = a ;;

 これはOCamlというプログラミング言語の例ですが、きちんと関数に型がつい ている(add関数は二つのint引数をとってintを返す、id関数は引数と同じ型を
そのまま返す)ことが確認できます。
 すべての言語で同じように型がつけばハッピーなのですが、現実は厳しい。
特に動的言語ではevalなど動的な言語機能により推論困難な場面が多いです。 静的型付きの言語でも型システムの兼ね合いで完全なものは難しいのですが、
ローカル変数に型を明示する必要がない言語が近年浸透しつつあります。

Page 6

動的言語、世にはばかる
 動的言語の処理系は静的言語のコンパイラのように実行前にプログラムの型の 整合性をチェックすることは(あまり)やりません。つまり型の健全性なんてあった
ものではなく、基本的にノーガード戦法をとっています。
 どうしてそんな安全性も保障できない不安全なプログラミング言語が世の中に 蔓延しているのでしょうか。コンパイラによる検査も無料ではなく、型システムが
高度になり対象コードが増えるほどコンパイル時間が増加します。
 動的言語は90年代から2000年代にかけて、コンパイル時間がなく型の知識も 不要で軽快に実行できるストレスフリーな言語として普及した側面があります。 特にWebアプリケーションではボトルネックがデータベースアクセスなどであり、 言語による実行速度が生じない(あるいは、処理系の性能改善によって許容範囲 内に抑えられている)ことが現代でも動的言語が許容される理由のひとつです。

Page 7

なぜ動的言語に… 型がつかないのか  型推論という素晴しいものがあるなら、PHPのような動的言語のソースコード も丹念に解析してやればOCamlのような静的型付き言語と同等になるのではな
いか。……そう思っていた時代が私にもありました。
 とても残念なことに動的言語にはそれを阻むダイナミックな性質がたくさんあ ります。evalやjson_decode()、unserialize()などの関数、include式によ
る動的ロードなどは、原理上あらゆる型の値を返します。
 PHPには存在しない(ことになっている)関数の再定義や、Rubyにはクラスで はなくインスタンス固有の特異メソッドというものも存在します。PHP関数マニュ アルを手がかりに型を付けても、実用にならない型に発散していくばかりです。  動的言語での演算子のオーバーロードは型推論と非常に相性が悪いです。PHP は基本はオーバーロードはない(ことになっている)のですが、問題があります。

Page 8

動的言語でも静的解析したい
 二項演算子 + が整数の加算のみをサポートしている言語を考えてみましょう。 関数 add(x, y) => x + y (引数xとyをとって加算結果を返す)を定義すると
き、 add(1, false) のような呼び出しが間違っていることは明らかです。
 静的な型検査(つまり実行する前にソースコードの整合性を調べる)ことで、 そのような明らかに間違ったコードを見過さないようにできます。静的型付き言 語の多くで実行ファイルのコンパイルの一部として型検査を実施することで、実行 すると間違った動作をするプログラム実行ファイルを生成しないようにします。  これが先述の健全性という概念ですが、多くの動的言語ではほとんど何の期待 もできませんでした。足し算をするadd(1, false)が間違ったコードだというこ とは人間様の目には自明ですが、常に人間が整合性を監視し続けることは困難で す。コード規模が大きくなるほど不整合を事前に確かめたくなるでしょう。いかに
人間様が賢くても、24/365で人間の手を煩わせるわけにはいきません。

Page 9

どうして動的言語に型をつけたがるの  ここまで説明した通り、動的言語の静的解析、特に型をつける簡単なことではあ
りません。それでも型検査をしたくなるのはなぜでしょうか。
 ひとつは、実行ファイルのコンパイルが必要なくとも実装中(コーディング中)の linterや継続的インテグレーションの一環として静的解析することで、確実では なくとも辻褄の合わない不安全な可能性を早めに検知したいという需要です。  もうひとつの動機としてはIDEなどのツールの支援を受けやすくして生産性を 高めることも挙げられるでしょうか。動的言語ではスクリプトとしてコードそのも のが再配布されることが多いため、パラメータや戻り値の型がわからなければマ
ニュアルを読むかコードそのものを読み解け、という思想でもあります。
 型が明示されていれば、そのような手間と迷いを減らせるほか、戻り値の型が 適切に定義されていれば、静的解析に基いた入力補完で、コーディング作業を
よりなめらかにできるかもしれません。

Page 10

PHP vs 型
 PHPは紛れもなく強い動的型の言語であり変数に型はありませんが、関数・メ ソッドやプロパティには型宣言ができます。これは実行時に処理系が型をチェック してくれるので、型宣言された文脈では確実に型が保障されるという有利な特徴 があります。もちろん関数呼び出しの度に型を調べるので実行時コストはかかる のですが、一般的な用途で型宣言がボトルネックになることは多くないと考えら れます。パフォーマンスをプロファイラで検査した上でボトルネックになることも 考えられますが、そのような場合は単に型宣言を削除すると検査コストを削減で
きます。

 ただし私の持論では