WARNING
書きかけです。
# 動的型付けと静的型付けの違いってなに?
# 1. 違い
# 1.1. 簡単に言えば
簡単に言えば、大抵の場合、変数に代入される型を書かない言語は「動的型付け」に分類されます。 例えば Python は動的型付けに分類されます。
#
# Python
#
i = 1 # int と書かなくても動く
print(i)
簡単に言えば、大抵の場合、変数に代入される型を書く言語は、「静的型付け」に分類されます。 例えば C 言語は静的型付けに分類されます。
//
// C 言語
//
#include<stdio.h>
int main (void) {
int i = 1; // <--- int と書かないと動かない
printf("%d\n", i);
}
# 1.2. 正確に言えば
# 違い その 1 - 仕組み
実行してみないと変数や属性に代入されているオブジェクトの型がわからないのが「動的型付け」で、 実行する前から変数や属性に代入されているオブジェクトの型がわかっているのが「静的型付け」です。
静的型付けと動的型付け - Wikipedia (opens new window)
プログラムを実行前に型検査を行うのが静的な型付け、静的型付けであり、 プログラムを実行しながら型検査を行うのが動的な型付け、動的型付けである。
Python は「動的型付け」に分類されます。 しかし、実行しないとわからないとはどういうことでしょうか? 例えば、以下のコードを見れば Person クラスが代入されていることは一目瞭然です。 なぜ Python は、実行してみないとわからないのでしょうか?
person = Person('岩倉玲音')
Python は、まず第一段階として コンパイラ によって、 ソースコードから抽象構文木そしてバイトコードに変換ます。 つぎに第二段階として ランタイム(仮想マシン)によってバイトコードが実行されます。 リンク先の図がわかりやすいです。
この第一段階、コンパイラ がバイトコードに変換された時点では、 変数に代入されるオブジェクトの型は決定できません。 そのため第二段階で、 Python の ランタイム(仮想マシン) が、 変数や属性に代入されたオブジェクトを読み込む度に、 そのオブジェクトがどの型に属しているかを調べながら実行しています。
以下は Python の生みの親である Guido van Rossum 氏のブログです。
Python の コンパイラ は、Python プログラムの中にあるオブジェクトの型について知らない; ランタイム(仮想マシン) だけが知っている
The Python compiler doesn't know aboutthe types of the objects you pass around in a Python program; only the run-time (the Virtual Machine) does.
選択的静的型付けとはなにか? - All Things Pythonic (opens new window)
What is Optional Static Typing? - All Things Pythonic
# 違いその 2 - 実行速度
「変数や属性に代入されたオブジェクトを読み込む度に、 そのオブジェクトがどの型に属しているかを調べながら実行しています。」って 言われても抽象的すぎてピンときません。 この作業によって実際どういう影響が私たちにあるのでしょうか?
静的型付けの場合は、 変数に代入されたオブジェクトがどの型に属するかの分類はすでに終わってしてしまっているので、 そういった分類の作業が不要になります。 あまり関係ないですが、こちらの記事が結構面白く、勉強になります。
この 型を都度判定するという作業が想像以上に重いらしく、 静的型付け言語の方が動的型付け言語よりも、処理速度は速いです。 こちらの記事は、すこしずつ型付けをして Python の高速化をはかっている記事で、とても面白いです。 正確には Python ではなく Cython という Python のコードを C に変換してくれる ツールを使って実験をしています。
昔、競プロの問題が解けなくて、もどかしさのあまり数年ぶりに C 言語で書き直したら 100 倍速くなりました笑 これは極端な例ですが 10 倍くらいは一般的に速くなるらしいです。 詰まるところ Python は遅い。という訳ですが、 速度よりも可読性重視というところでしょうか。
パフォーマンスについては気にしないで。必要であれば、後から最適化を行うので。
Pythonの設計哲学 - The History of Python.jp (opens new window)
# ◯ Python も型を書けば速くなるの?
しかし Python で型を書けば速くなるのかというと、
残念ながら、そんなことはありません。
型は、コメント #
と同じで CPython は、
これを理解できないからです。
注意してほしいのは、ここでいう型宣言の需要は、人間のために書く、ドキュメントとしての型ヒントで、コンパイラに効率よくランタイムを生成してもうらための型ではありません。これを混同している人が多いですし、「高水準な、良く設計されたプログラミング言語」はそれらを区別せずにプログラマに書かせようとしてきます。(Rust などは低水準を目指しているので明示的に区別します)
漸進的型付け言語の時代に必要なもの - mizchi's blog (opens new window)
じゃあ、型を書いたら速くなる機能を実装してよ、とも思うのですが、 mypyc というプロジェクトがあるらしいです。 ほとんどの方が使われている Python は CPython と呼ばれる C 言語で書かれた Python です。 それとは別の Python のインタープリタということです。
mypyc は(ほとんど)一般的な Python 開発にはまだ有用ではありません。 mypyc は、mypy 型ヒント付きの静的に型付けされた Python モジュールを CPython の C 拡張にコンパイルするコンパイラーです。
Mypyc is (mostly) not yet useful for general Python development. Mypyc is a compiler that compiles mypy-annotated, statically typed Python modules into CPython C extensions.
python/mypy - GitHub (opens new window)
# 2. 動的型付けのメリットとデメリット
速度, 安全, 書きやすさ, 読みやすさ の 4 点が違いかなと思います。
規模が小さいうちは、動的言語の方がやりやすいですが、 規模が大きくなると、動的言語だと辛くなるのかなと思います。
あるいは規模の大小に関わらず、他の人と協調して書くような時に、 型が必要だという指摘もあります。
俺の中で型書きたくなるのは堅牢に書きたい時でも規模が大きい時でもなくて、俺の預かり知らぬところで酷いコードが merge される時。全てのコードを俺が書く or レビューするなら規模に関わらず型は書きたくない。型書きたくなる時はいつも他人のコードを見ている時。
— Ryo@GBVS (@OgiharaRyo) November 12, 2019
漸進的型付けをいつ使うべきかという使い分けを明確にするために、 動的型付けを基準にメリット、デメリットについて考えていきます。
# 2.1. 速度
# メリット
なし
# デメリット
速度については、動的言語は明確にデメリットです。
# 2.2. 安全
# メリット
なし
# デメリット
安全については、動的言語は明確にデメリットです。
型ヒントを使うことで型に関するバグは実行する前に発見することができます。 とは言え、静的型付けを使って型で確認できることは、型に限られているよね、という指摘もあります。
同じでないのは自明として、可読性としてはassertがあれば型はなくとも十分で、型で防げる問題はあまり多くないと言う認識。(自分は行数ベースで10-20%ぐらいassertやassertの様なコメントを書いてる https://t.co/6ILaLcIpqk
— idiotton (@idiotton) January 18, 2020
実際 Guido van Rossum 氏は、Guido van Rossum 氏のブログを読んでいると 「かなり些細な 類のバグ(例えばメソッド名の書き間違え)」という文言が見えます。
このことは(訳者注: 動的型付けは) Python の表現を豊かにし、そして柔軟にもしますが。 時として、かなり些細な 類のバグ(例えばメソッド名の書き間違え)が、 開発者が期待していたよりも後になって見つかるような事態を引き起こすことを意味しています。
This makes Python expressive and flexible, but sometimes means that bugs of a rather trivial kind (like typos in method names) are found later than the developer would have liked.
選択的静的型付けとはなにか? - All Things Pythonic (opens new window)
What is Optional Static Typing? - All Things Pythonic
では、どこが使い分けの分解点になるのでしょうか? 安全面について言えば null 安全に関わる問題がきになるようになったら、使いどきなのかなと思います。
null 安全に関する問題について、一般に Python のような動的型付け言語では、 None を return するのではなく、例外を raise するように指示されています。
型ヒントを使えば null 安全に関する問題について、 Union 型あるいはOptional 型を使って対応することができます。
# 2.3. 書きやすさ
# メリット
型を書くのは、面倒です。 あまり大きい理由ではないのですが、 純粋に何かロジックを考えている時に int, str とか書いているのが面倒になります。
# デメリット
デメリットはコード補完が効かないことです。
コーディングの生産性についても、IDE前提の場合、静的型付け言語の方がコード補完やコード解析の恩恵を受けやすい。Rubyに限らないが、静的型付けでないスクリプト言語は、このあたりのメリットが薄い。
スクリプトの手軽さは、IDEが貧弱であったり、IDEを使わない状況では意味があったのかもしれないが、IDE前提で便利になった今、静的型付けの言語を使い、人・プログラム共にハイパフォーマンスが得られることが望まれているのではないだろうか。
5G、IoT、SaaS、AIに備えて高速・並行処理に有利なWeb開発用プログラミン言語の習得を - Qiita (opens new window)
型やIDEの支援なしにコードを育て続けるには、逆に高度なモジュール分割のノウハウや、状況に応じたストラテジーが必要になっています。
漸進的型付け言語の時代に必要なもの - mizchi's blog (opens new window)
とはいえ、カリー化のないJSで高階関数をやるのは型の支援がないと難しいので、 僕も Flow/TypeScript で型が保証されてる時にしかやりません。
オブジェクト指向の呪いと、その避け方 - mizchi's blog (opens new window)
# 2.4. 読みさすさ
# メリット
変数や関数名はできれば短く書きたいです。 長いと読みにくいコードになるからです。 それと同じようにして、既にわかりきっているなら、 無い方がスッキリしたコードになります。 例えば大学の授業でソートについてコードを書かれた時に、 リストを使うのにリストって明示されても冗長な感じがします。
def bubble_sort(lst):
n = len(lst)
for i in reversed(range(n)):
for j in range(i):
if lst[j] > lst[j + 1]:
lst[j], lst[j + 1] = lst[j + 1], lst[j]
lst = [3333, 5123, 9981, 1243, 7412]
bubble_sort(lst)
print(lst)
# [1243, 3333, 5123, 7412, 9981]
from typing import MutablSequence, List
def bubble_sort(lst: MutablSequence[int]):
n, i, j: int
n = len(lst)
for i in reversed(range(n)):
for j in range(i):
if lst[j] > lst[j + 1]:
lst[j], lst[j + 1] = lst[j + 1], lst[j]
lst: List[int] = [3333, 5123, 9981, 1243, 7412]
bubble_sort(lst)
print(lst)
# [1243, 3333, 5123, 7412, 9981]
# デメリット
ただ、問題はコードが大きくなった時です。 引数に何が返ってくるのかわからない。 ドキュメントを読めという話なんだけど、 ドキュメントは書式が統一されているわけでは無い。
pythonは何でも受け入れてくれるから、何を代入するかちゃんと判ってないと後々混乱するのよね…。 変数名でこれは配列かタブルかただの変数なのか、さらに文字列なのか、書いとかないと死ぬ。RT
— T-ボーンナム (@korokoro_bone) January 2, 2019
例えば、以下は Django のコードなのですが引数 request が何かもわかりませんし、 なにを返しているかもわかりません。 調べればいいという話ではあるのですが、文書化されていなかったりすると、最悪です。
def view(request):
context = {
'blog_title' : 'やる夫の日記',
'content' : 'ケーキを食べた。'}
return render_to_response(
'app_name/template_name.html',
context
)
型名が明示されていると
「あー、要求 HttpRequest
を受けて、応答 HttpResponse
を返しているんだな」くらいのことはわかります。
def view(request: HttpRequest) -> HttpResponse:
context = {
'blog_title' : 'やる夫の日記',
'content' : 'ケーキを食べた。'}
return render_to_response(
'app_name/template_name.html',
context
)
# 3. 漸進的型付けの使い分け
絶対に静的型付けを使わないといけないのでしょうか? PEP 484 では、型ヒントの温度感について、以下のように記述しています。
Python は依然として動的型付け言語のままです。 Python の作者たちは(たとえ規約としてであっても) 型ヒントを必須とすることを望んではいません。
PEP 484 -- 型ヒント (opens new window)
# 3.1. 型ヒントの使いどころ
答え: 大規模なものを堅牢に作らないといけないとき
個人で少数の時は使わなくていいかなと思ったりします。 Guido のいる DropBox について書かれています。Guido のブログです。
Dropbox においては 120 万行のコードにアノテーションをつけました (これは私たちの Python コードのおよそ 20% にあたります)、 これだけたくさん書いたのでアノテーションをつけることが、どれだけの作業になるか知っています。 大変でしたが価値はありました: 見返りは素晴らしいものです。
At Dropbox we’ve annotated over 1.2 million lines of code (about 20% of our total Python codebase), so we know how much work this can be. It’s worth it though: the payoff is fantastic.
Dropbox releases PyAnnotate -- auto-generate type annotations for mypy (opens new window)
Python に移行したそうです。 型ヒントがあるから Python を選んだわけでは無いと思うのですが、 堅牢なものを作ろうとするときは、1つの要因になるくらいに重要だったりする気配があります。
Pythonを選んだ理由としては以下の通りです。
(中略)
動的言語でありながら型定義による事前検査が可能
比較的新しいウェブフレームワークのコードを、理解は一切していませんが眺めていると、 型ヒントが付与されているのがわかります。 フレームワークを提供する側は、型ヒントを付与した方が良さそうな気配を感じます。
# 3.2. いつ使わなくてもいいの?
答え: 簡単なスクリプトを書くとき
小さい時は型なし、大きい時は型をつけて、ざっくりすぎる感じがします。 この辺りの温度感は2つの疑問について考えることで、 なんとなく雰囲気を共有できればと思います。
- なんで Python は動的型付けを採用したの?
- なんで Python は機械学習に強いの?
# 1. なんで Python は動的型付けを採用したの?
もともとは、簡単なスクリプトを書くために誕生したからのようです。
Bourne シェルがどのようなものかはわからないのですが、 どうも雰囲気的にはシェルスクリプトよりも、 もうちょっと書きやすいプログラミング言語を作りたいという思いだったようです。
私は CWI で Amoeba 分散オペレーティングシステムのグループで働いていました。 Amoeba のシステムコールインタフェースには Bourne シェルからアクセスしにくかったので、 C プログラムや Bourne シェルスクリプトを書くよりも良いシステム管理の方法が必要でした。 Amoeba のエラー処理の経験から、プログラミング言語の機能としての例外の重要さを強く意識するようになりました。 ABC のような構文と Amoeba のようなシステムコールを合わせ持ったスクリプト言語が必要だろうと思いつきました。
そもそも Python は何故創られたのですか? - Python よくある質問
シェルスクリプトをイメージすると、そこまで膨大なコードを書くことは多くはないのかなと思います。 そういった背景の中では、動的型付けの方が楽です。 しかし Python で作られたものが大きくなるにつれて、 型を使いたいという要望が大きくなり、型ヒントが導入されたということでしょうか。 再度 Guido 氏のブログを引用します。
Python の機能として、選択的な静的型付けが長い間要求されてきました。
Optional static typing has long been requested as a Python feature.
Adding Optional Static Typing to Python - All Things Pythonic (opens new window)
# 2. なんで Python は機械学習に強いの?
ネットを彷徨っていると、どうも昔から計算機用のライブラリを Python の言語として強く支援していたことが 恐らく最も大きいのかなと思います。
- NumPyの歴史 と Pythonの並行処理 - atsuoishimoto's diary (opens new window)
- Python がなぜデータ分野で強いのか?の真実 - Python 学習チャンネル by PyQ (opens new window)
tuple, range, def と言った数学の用語あるいは概念を多数導入し、 一貫性を重視している Python の言語仕様を見ていると、 Guido 氏自身の計算幾科学方面への理解もあったのかなと思ったりもします。
また大きな要因ではないですが Python の文法から見ていくと、 動的型付けでサクッと書けますよねというのもあるのかなという気がします。
私にとって重要なのは、どうすれば生産性の高い仕事ができるかなのです。”生産性”とは何か。 私の場合、分析は1回しか行いません(異なるアイデアのテストやデバッギングは別です)。ある特定のコードを24時間実行することもありません。私はエンドユーザのためのソフトウェアアプリを開発していません。私は”生産性”を定量化する際、(1)アイデアをコードで書き出す時にかかった時間、(2)デバッギングにかかった時間、(3)実行にかかった時間の合計時間を想定します。 私にとって”高い生産性”は”結果を出すまでにかかった時間”を意味します。
Python や機械学習、そして言語の競争について – 極めて主観的な見地から - POSTD (opens new window)
静的型付けへの確かに明確な傾向があります。 しかし、もし仮に Python が動的型付けでなかった場合、 型をコードに書かないといけなかった場合、 たとえ、どんなに Python がコミュニティとして計算機科学分野の方面を支援していたとしても、 今日のような興隆がえられなかったのではないかと考えています。
また、アルゴリズムのコードを書くのに適しているという指摘もあります。
アルゴリズムを一番書きやすい言語、な〜んだ?
— ogiwara (@designpatterngf) February 5, 2020
正確には擬似コードに最もよく似た言語ってなんだろ。やっぱPythonかな
Python は確かにコードは遅いのですが、アルゴリズムを考えるときはやりやすい気がします。 競技プログラミングでは、C++ が圧倒的な一位で Pytho が二位です。
# ◯ そのほか
あるいは FaaS のような環境でも、静的型付けの Java より動的言語である Python, JavaScript が、 好まれているようです。
それがですね、もともとLambdaってNode.jsからスタートしたこともありPythonに対応してからもしばらくLambdaといえばNode.js的な感じがあったんですよ。それが今や圧倒的にPythonなのです。ちなみにJavaはPythonと同時期に対応しましたが使う人が減っていったイメージですね。あくまでも肌感覚ですが
— Keisuke Nishitani (@Keisuke69) February 11, 2020
以下のリンクも参考になります。
- そもそも Python は何故創られたのですか? (opens new window)
- Python は何をするのに向いていますか? (opens new window)
- Python の動的型システム - The History of Python.jp (opens new window)
- Adding Optional Static Typing to Python (opens new window)
- Adding Optional Static Typing to Python -- Part II (opens new window)
# 4. 動的型付けの興隆と衰退
全体的に見れば静的型付け言語への揺り戻しが起こっています。 いまは静的型付けが好まれる傾向があります。
プログラミング言語の盛衰をたどってみれば、 最新の言語設計では静的型付けが好まれるという確かなトレンドがあることに気づくでしょう。
動的型付けの衰退 - ORACLE.COM/JAVAMAGAZINE (opens new window)
# 4.1. 興隆
2000 ~ 2015 年代中頃ごろは、以下の様な空気感でした。
型宣言はシステムの信頼性をたいして向上させない、というのが答えです。 型宣言のおかげで発見されるバグ(不具合)が存在するのは事実です。 例えば、a という変数が文字列型を持つと宣言されているのに、そこに整数型の値を格納する処理があれば、それは間違いです。 しかし、現代のプログラミングにおいては、この種類のバグはあまり生じないのです。
2008年11月23日 「Ruby は型宣言がないけど、ちゃんとしたシステムに使えるのか」という質問にどう答えるか (opens new window)
ここでは Perl や Java、C++ などの例を使い、 変数に型がないことの利点として「どのような型の値でも代入できる」 「記述量がとても短くなる」「変数に型がないと変更に強い」「関数のオーバーロードが不要になる」「複数の型を受け取りたいときに、 インターフェースを実装する必要がない」「C++ のテンプレートのような機能も必要がない」などが挙げられている。
2013年03月1日 「変数に型がない」はメリットなのか、それともデメリットなのか。宗教戦争勃発 (opens new window)
その熱狂たるや凄まじいもので、 Ruby を使えば必ず生産が上がるという主張もありました。 もちろん適材適所で使えばそうだとは思うのですが。
初めて Ruby という言語を知った時も、 完全に目がイッてしまった友人が「Ruby っていう素晴らしい言語があるんだよ」って言いながら、 自分に近づいてきたのが今でも印象的な記憶です笑
それに対する反論は当時からありましたが。2006 年の記事です。
# 4.2. 衰退 - コードの巨大化
また加えて、型推論の発達も大きく寄与しているように思います。
同意。静的型付き言語がダルいから動的型付け言語が流行ったけれども、 ダルさが軽減されたからアプリケーション開発でこれから動的型付け言語選ぶモチベーションは減ってる気がする。 https://t.co/NVxgDheAHD
— Inada Naoki (@methane) February 15, 2019
Web アプリケーションが小さいうちは、 型がなくても頑張って読めばだいたいわかりますし、 処理速度も高速さは必要ないですし、 安全面でもとりあえず動かして問題ないなら、 それで OK という感覚でやっていけばいいと思うのですが。
Web アプリケーションが大きくなるにつれて、 多少の煩わしさは許容しても型を明示した方が、 書きやすさ、読みやすさ、高速性、安全性の面で受けられる メリットが大きくなったからかなと感じたりもます。
同じ動的型付けである Ruby がよく槍玉に挙げられるのをみます。
感覚的には Ruby が使われるようになりだした直後にちらほら見かけて、 Ruby が普通に使われるようになると見かけなくなって、 Ruby で組まれたものが大きくなると、また出現したような感じです。
Ruby はダメだという話ではないと感じています。 それは単にいま作ってるものがでかくなり過ぎたという話かなと感じたりもします。
しかし、同じ動的型付け言語でも Python はそこまで槍玉に上がっていません。 型ヒントが導入されたということ以上に、 データサイエンスや機械学習等で使われているから、表立って言えないのかなと思います。
いずれにせよ 何にでも対応できる「銀の弾丸などない」 (opens new window) という話かなと思います。 型推論があっても型を書くのは面倒なので、月並みな結論ですが、適材適所でやっていくべきかなと思います。
# ◯ 適材適所ってなんだ?
表現力の高い JavaScript(あるいは TypeScript) は UI の作成に向いている気がします。 ウェブのフロントエンドにとどまらず Electron, React Native などにも進出しています。
反面サーバサイドではそこまでの表現力は要求されず Go 言語のような言語や、 あるいは FaaS なら Python のようなお堅い言語が適している気がします。
Ruby は表現力は高いですが、サーバサイドの言語なので、 その役割は JavaScript が担いつつあります。 どうしても復権は厳しいのかなという気がします。
また、一方で、Railsがオワコンと言われる原因としては、 技術的なところもあると思う。まず、SPAなどJavascriptでフロント(View)を作るようになると、RailsのMVCという構成やその機能だけでは、便利さを享受できなくなってきた。Rails6では、Webpackerが標準でインストールされるようにはなったが、そもそも、サーバサイドのフレームワークがそこまでする必要があるのだろうか?
5G、IoT、SaaS、AIに備えて高速・並行処理に有利なWeb開発用プログラミン言語の習得を (opens new window)
# 5. そのほか
# 5.1. なんで型を後置するようになったの?
型が後置の言語はコンパイラに優しい理由https://t.co/YJKaNV2dmc
— ゴリラ@技術書典Day1お11 (@gorilla0513) January 3, 2020
これは同様にして Python の括弧がないことが踏襲されないことも同じかなと思います。 インデントも多分面倒だと思う。 だから他言語は採用していない。 リンターで対応させれば良いのでは?
Pythonのインデントが言語仕様に含まれてるの、あれ、パーサー作るの面倒ちゃうの?
— HRK (@hncgu) January 21, 2020
Python のインデントによるブロックを作成する記法は、
比較的多くの方が好意的に評価している気がします。
しかし、後続の多くの言語、例えば Go, Rust, vlang, Elixir では {}
を有してしまっています。
# 5.2. 契約による設計
「型ヒント」で検査する方法とそれ以上に さらに強い制約をかそうという考えがありました。 「契約による設計」と呼ばれるものです。
流行らなかったらしいですが、たまに名著として紹介されます。 覚えておいても悪くない単語かなと思います。 流行らなかったのは面倒だったからかなと思います。
ちょうどこの記事を書くにあたりかなり参考にさせていただいた Tetsuya Morimoto さんという方が、 「契約による設計」の現在の立ち位置みたいなところ について説明されていたので、ご紹介します。
21 契約による設計 - forest book (opens new window)
この節はどうなんでしょうね? あらかじめ要件や仕様を厳密にしやすい業務系では こういった設計が定着しているのでしょうか? 私は経験がなくて実践的にこの手法が使われているのかどうか知りません。Web 業界ではまだまだここまで厳格な設計手法は 定着していないように私は思います。 また別の節にある表明プログラミングもそうですが、 厳密さを保証するためのオーバーヘッド (実行効率や保守性など) も かかることから敬遠されがちなところもあると思います。
僕も、ここで紹介されている書籍「達人プログラマ」で、 「契約による設計」という言葉を知りました。
ざっくり言えば引数と返り値を型だけを検査するのではなく、 もっと色んな条件を検査しようという設計あるいは考えです。
契約による設計に基づいたコードでは、 事前条件を満たした引数をあるオブジェクトのメソッドに与えた場合、 オブジェクトとメソッドの返り値は事後条件を満たし、 かつオブジェクトはメソッドの実行前後で不変条件を満たさなければなりません。
Python の疑似コードに 不変条件、事前条件、事後条件をコメントで書き込んで見ます。
# クラス不変条件
# メソッドを実行前後でソートが崩れない。
# 実行中は崩れててもいい
class SortedList:
def __init__(self):
self._list:List = []
def add(self, new_element):
# 事前条件
# 追加する要素はリストの中に無い
i = 0
while self._list[i] > new_element:
i = i + 1
self._list.append(element)
# 事後条件
# 追加した要素がリストの中にある
やろうとすると結構、面倒になりますね。 普及しなかったのも、なんかわかる気がします。 この辺の機能を言語的に取り入れるか、いれないかという匙加減が、 また言語設計の面白いところなのかもしれません。
「事前条件」は、いまでも引数チェックはしてるから、 そこまで必要ではない気はします。 「事後条件」と「クラス不変条件」は、良い方法が思いつかないですね。 しかもそれを満たしているかどうかを実行時に確認するのは、 かなりオーバーヘッドな気がします。
「事後条件」と「クラス不変条件」については、 とりあえず開発時には assert 文や __debug__ を使って表現して、 運用時には使わないのがベターな方法な気がします。
それでも Python の型ヒントと同じように、 ツールやあるいは言語レベルで契約の書式を統一してしまうことは、すごく魅力的には感じます。
型検査と同じように静的に契約を検査できれば、いいのかもしれませんが。 難しそうですね。 「事後条件」と「クラス不変条件」は、定義するところが、使いどころが なかなか思いつきません。
Tetsuya Morimoto さんの記事の中でこんなのもあるけど.. みたいな感じで紹介されている 契約による設計の紹介 (opens new window) という記事は、「型ヒント」を飛び越えて、いっきに「契約による設計」の話に進んでいます。
言い換えれば「型検査」で済む話を「契約による設計」で対応しようとしているので、 ちょっとありがたみが薄れてしまっているかもしれません。 個人的には、「契約による設計は、静的型検査以上のものを検査する」であるという方が 理解しやすいかなと思います。
とはいえ説明しようと思ったんですけど、大変なんですよね... 書籍「達人プログラマ」にサンプルも含めて乗っているので、 気になる方は、オススメです。 アノテーションとしての契約の記述のされ方が面白いです。 Java は iContract というツールを使っています。 ただサンプルコードは Java とあと Eiffel (opens new window) というあまり使われていない言語です。 iContract も使われていないツールだと訳注がありました。
上の記事の中では、この後、例外の話をしますが、なぜ例外の話をするために、 型ヒントの話をしたのか、というのがつながっていきます。 以上「型ヒント」による型検査のさらに上を求めてという趣旨で、 「契約による設計」をご紹介させていただきました。
# 5.3. 強い型付けと弱い型付け
「強い型付け」、「弱い型付け」という言葉があります。 「静的型付け」の方が強そうなので、 じゃあ「動的型付け」は「弱い型付け」なのかというとそういうわけでは無いらしいです。
C 言語は「静的型付け」で「弱い型付け」らしいのですが、 サンプルコードを探しているのですが、見当たらない... orz
C は鬼のように速いです。速い理由は型の判定をしないからです。 しないけど、君は int っていったよね。 問答無用で処理してくれます。
なぜ Python は動的言語でありながら強い型付けに分類されるのですか? (opens new window)
Why is Python a dynamic language and also a strongly typed languageよく強い型付けという言葉を、「静的型付け」と「強い型付け」の両方に対して使ってしまっています。 静的型付けというのは、変数宣言で明示された型が変数に代入される言語 -- より一般には、例えば型推論などで、プログラムを実行することなく、 変数がどちらの型を参照しているか判別することができるコンパイラを持つ言語を指しています。 強い型付けというのは、型を混同することを制限する言語を指しています。
People often use the term strongly-typed language to refer to a language that is both statically typed (types are associated with a variable declaration -- or, more generally, the compiler can tell which type a variable refers to, for example through type inference, without executing the program) and strongly-typed (restrictive about how types can be intermingled).そのため、もし動的型付けと強い型付けを直交する概念だと理解できるなら (訳注: 動的型付けと強い型付けが別のものだと理解できるなら)、 Python は動的型付けでもあり、強い型付けであると言えます。
So, if you look at dynamic typing and strong-typing as orthogonal concepts, Python can be both dynamically and strongly typed.
ただ完全、完璧に強いのかというとそうでもなさそうな感じがします。 Flask の作者 Armin Ronacher の記事です。
# 5.4. primitive と composite
プログラミング言語において、一般に型は2種類に分けられます。 primitive と composite です。 primitive というのは int, str のような1つしか値を持たない型です。 composite とは tuple, list, class 文で定義されるユーザ定義クラスです。
でも実際には Python は基本的に primitive の存在しない、すべて composite です。 例えば、int も str も次のような具合で属性や要素を参照することができます。
>>> a = 1
>>> a.real # 実部
1
>>> a.imag # 虚部
0
>>>
実際 int 型の CPython の実装を見てみると C 言語の構造体で 属性を持った composite として存在しています。 あまり、まだこの中身がどうなっているかは知りません。
struct _longobject {
PyObject_VAR_HEAD
digit ob_digit[1];
};
ここまでの話も基本的には composite に対する話で、 あるいはごくごく表面的な話をしていたと考えていただけると幸いです。
つまり、いままでオブジェクトとは、クラスとは見たいな話をしながら、 実際には 0, 1 のビット列で表現されたより低レベルな層の話は、 いままで一切してこなかったということです。
実は Flask の作者である Armin Ronacher の記事で Python は primitive だけだよ。 という箇所を見つけ若干自信がないのですが、 おそらく Julia の説明とあわせて、上記のような理解でいいかなと考えています。 おそらく別の視点で Armin Ronacher は primitive しかないと言っているのだと思います、信じています。
しかし、Python にはないものが1つあります。それは複合型です。 Python の型はすべて基本型 (primitive) です。 つまり、基本的には一度に1つの型のみが作用することを意味します。 基本型の反対は複合型 (composite) です。時々、別のコンテキストで Python の複合型を見かけるでしょう。
Revenge of the Types: 型の復 - Qiita (opens new window)
Julia の公式ドキュメントでもそういう説明なのでいいかなと感じています。 Python の公式ドキュメントも漁って見たのですが、 primitive と composite に関する記述は見当たりませんでした。
Primitive Types - Julia (opens new window)
primitive 型は、実体のある型で、たんに古くからあるビット列からなるデータを持ったものです。 primitive 型の典型的な例としては、整数や浮動小数点といったものがあります。
A primitive type is a concrete type whose data consists of plain old bits. Classic examples of primitive types are integers and floating-point values.
Composite Types - Julia (opens new window)
composite 型は、様々な言語でレコード、構造、またはオブジェクトと呼ばれています。 composite 型は名前のついた属性の集まりで、 その属性に束縛されたインスタンスはまとめて、1つの値として取り扱われます。 多くの言語では composite 型は、唯一ユーザが定義できる型で、 Julia においても同様に現在のところ最も一般的に使われるユーザ定義型です。
Composite types are called records, structs, or objects in various languages. A composite type is a collection of named fields, an instance of which can be treated as a single value. In many languages, composite types are the only kind of user-definable type, and they are by far the most commonly used user-defined type in Julia as well.
ちょっと文章が怪しいですが Wikipedia の記事がわかりやすいです。
プリミティブ型 - Wikipedia (opens new window)
理論計算機科学的に代数的データ型によって考えれば 「そのデータ型の定義の中に部分として他の[1]型を含まないような型」が プリミティブ型であるが...
より低レベルな型、つまり primitive の話については、 書籍「コーディングを支える技術」の 「第8章 型」が面白いかもしれません。
もう少し説明がないと厳しいかもしれませんが、 浮動小数点数の表現のされ方とかの雰囲気とかもわかります。
# 5.5. 動的言語と静的言語
# 5.6. 動的型付けと型推論
動的型付けと型推論は異なるものです。
動的型付けは、 コードを実行する時に 、インタープリタが、 型を判別して処理をすること、コードを実行することを指しています。
型推論は、 コードを実行する前に 、型検査機またはコンパイラが、 型を判別して処理すること、型検査あるいはコンパイルすることを指しています。
# 6. おわりに
このあと if 文では bool 型, for 文では Iterator 型と Iterable 型, try 文では Optional 型について見て行きます。