Rust なのか Go なのか..






ひとりごと。色々、漁ってると、こんな雰囲気。


並行処理で
もっと高速に書きたい
→ Go

シングルマシンで
もっと高速に書きたい
→ Rust

◯ 用途

Rustと競合? - Goへの誤解について
似てるのは例外機構を捨てたこととバイナリがポータブルなことぐらい。得意な用途は全く異なります。...

Go は非同期処理をシンプルに書きつつマルチコア性能を引き出すのが強みです。

Rust は徹底してオーバーヘッドを排除してシングルコアの限界性能を引き出せるのが強みです。... これらの強みが関係ない分野だけが競合しています(CLIツールなど)。

◯ Go が採用された例

隠れマルコフモデルという統計機械翻訳アルゴリズムを使ってC++で実装されていた機械翻訳エンジンを、 パフォーマンスを上げるために並列処理が得意なGo言語に書き換えるという仕事をいただきました。
サンフランシスコで創業したスタートアップを解散した話 - note

◯ Rust のゼロコスト

「徹底してオーバーヘッドを排除して」という例を2つを示します。

1. 代入、再代入などを厳しく制限します。

Rust は代入、再代入を厳しく管理しなければなりません。 Rust は、ガベレージコレクションでのゼロコストを狙っているためです。 ガベレージコレクションとは使わなくなったオブジェクトを破棄する機能です。 Go は使わなくなったオブジェクトを自動的に破棄してくれますが、この処理が意外と重いというわけです。 以下の記事は後半は厳しいですが、前半は割と読めます。

RustはGCなし(ゼロコスト)でリソースを自動的に解放する仕組みを持っていて、それを実現する概念が所有権です。
Rustの所有権に親しむ


あまり詳しいことはわかりませんが、副次的ば効果として副作用の範囲も限定的にしているという、 なかなか素敵な実装になっているらしいです。

2. 基本的に例外は変数の中に渡す。

Java で言う所の try 文がはなく、基本は全部 Optional で、もっと平たく言えば if 文で対応するような感じらしいです。 なぜなら try 文は if 文より重いかららしいです。 公式ドキュメントが鬼のようにしっかりしている。一切理解できていないけど。

以下、Optional の説明が続きます。 結構、長々と書いてしまったので、適当に読み飛ばしてください。




Optional ってなんだよって話ですが、例えば Python では標準ライブラリ re の search メソッドの返り値の型は Optional[Match] です。 検索対象の文字列があれば Match オブジェクトを返してくれますが、無ければ None を返します。

# 対話モード >>> に直接コピペできます。
# Match もしくは None = re.search(文字列のパターン, 検索対象の文字列)
import re

# (a) あれば re.Match を返してくれるけど
result = re.search('<(\d+)>.+"(\w+)"', '1v2<4649>90a"abcd"1a')

isinstance(result, re.Match)
# True

result.groups()
# ('4649', 'abcd')


# (b) ないときは None を返す。
result = re.search('<(\d+)>.+"(\w+)"', 'abcdefg')

isinstance(result, type(None))
# True


なにが言いたいかというと変数 result には Match クラスのオブジェクトがはいるかもしれないし type(None) クラスのオブジェクトがはいるかもしれないということです。 たいていの場合、1つの変数には1つのクラスのオブジェクトしか入りませんが Match.search メソッドは、2つのうちいずれかのクラスのオブジェクトを返します。 これを型アノテーションで表現すると変数 result は次のようになります。

# 対話モード >>> に直接コピペできます。
# from re import Match <- re から import しない, mypy で弾かれる http://bit.ly/2Wh2IU3
from typing import re
from typing import Union

result: Union[Match, None]
# 対話モード >>> に直接コピペできます。
# from re import Match
from typing import re
from typing import Optional

result: Optional[Match]


Python の型アノテーションと mypy については、以下の記事で書きました。現在サイト移転中です。



以下の文章を読んで try 文による例外処理は、きっと重いんだろうなくらいの理解をしています。

Rust におけるエラー処理 - Hacker News
Error handling in Rust - Hacker News

例外を使うことが許されない状況下で Rust を使わなければならない人がいる。 (なぜなら unwind table と cleanup code が大きすぎるからである)。 そのような人々は、実質的に全てのブラウザベンダやゲーム開発者が含まれる。
Some people need to use Rust in places where exceptions aren't allowed (because the unwind tables and cleanup code are too big). Those people include virtually all browser vendors and game developers.

さらには、例外は、この厄介な codegen tradeoff があります。 ソフトウェアを zero-cost にするか(例えば典型的に C++, Obj-C そして Swift コンパイラがそのように動作します)、 zero-cost の場合、例外を投げることは重たい処理になります。 さもなくばソフトウェアを non-zero-cost にします(例えば Java HotSpot や Go 6g/8g がそのように動作します)、 non-zero-cost の場合、たとえ例外が投げられなかったとしても、 各 try ブロックに対してパフォーマンスペナルティを喰らいます(Go の defer においては)。 RAII を持つ言語に対しては、デストラクタを伴う各 single stack object は 暗黙的な try ブロックを生成します、そのため例外は実質的に実用的ではありません。
Furthermore, exceptions have this nasty codegen tradeoff. Either you make them zero-cost (as C++, Obj-C, and Swift compilers typically do), in which case throwing an exception is very expensive at runtime, or you make them non-zero-cost (as Java HotSpot and Go 6g/8g do), in which case you eat a performance penalty for every single try block (in Go, defer) even if no exception is thrown. For a language with RAII, every single stack object with a destructor forms an implicit try block, so this is impractical in practice.

zero-cost 例外によるパフォーマンスへのオーバーヘッドは、理論的な問題というわけではありません。 私は、起動時に数千に及ぶ例外を投げていたために、 zero-cost 例外が使われた GCJコンパイルされた Eclipse が起動に 30 秒も要したのを覚えています。
The performance overhead of zero-cost exceptions is not a theoretical issue. I remember stories of Eclipse taking 30 seconds to start up when compiled with GCJ (which used zero-cost exceptions) because it throws thousands of exceptions while starting.

エラーが生じたときの経路と成功したときの経路の両方について考えたとき、 C 言語のエラー処理に対するアプローチは、例外に比べて素晴らしいパフォーマンスと code size story を持っています、 このことは C 言語のエラー処理に対するアプローチがシステムコードに圧倒的に適していることを示しています。 しかしながら C 言語のエラー処理に対するアプローチは人間工学に反しかつ安全ではありません、 Rust は Result でこれに対応します。 Rust のアプーロチは C のエラーハンドリングのパフォーマンスを獲得しつつ、 C 言語のエラーハンドリングが持つ煩雑さを取り除いています。
The C approach to error handling has a great performance and code size story relative to exceptions when you consider both the error and success paths, which is why systems code overwhelmingly prefers it. It has poor ergonomics and safety, however, which Rust addresses with Result. Rust's approach forms a hybrid that's designed to achieve the performance of C error handling while eliminating its gotchas.


「しかしながら C 言語のエラー処理に対するアプローチは人間工学に反しかつ安全ではありません」というのは、 C 言語は基本的に例外が無いので戻り値にエラーを示す値を代入して呼び出し元に戻します。

Python の例外はすぐにドカンと爆発してくれるので、見逃すことはないのですが、 C 言語の場合、変数にひっそりと代入して忘れてしまうと、あとあとになって爆発して、 この子は、どこかから来たんや?みたいなことになりデバックが大変になります。 「例外を握り潰す」という表現のされ方をよく見かけます。

C言語の場合エラー処理といえば、 「関数引数あるいは戻値にエラーコードを定義し、処理結果をif文で判定、上位ルーチンに戻す。」となります。
C言語開発での例外処理


でも結局 Rust も変数に Result 型の変数をいれてるから人間工学に反してるんじゃね? C と何が違うの?という話ですが、コンパイラで静的型チェックをして実行前に判定しているということかなと思います。

Python の標準ライブラリ re の search メソッドもパターンにマッチする文字列がなければ、 しれっと例外的な None を返して来ます。 これは上の記事の文章を借りれば「人間工学に反している」と言えます。 しかし、型アノテーションと mypy を組み合わせれば静的な型検査ができます。 実行する前に、型について間違っているところだけは、エラーを見つけることができます。

例えばこのコードを...

import re

# re.Match クラスのオブジェクトしか受け付けない関数
def groups(result):
    return result.groups()

result_1 = re.search(r'<(\d+)>.+"(\w+)"', '1v2<4649>90a"abcd"1a')
print(groups(result_1))

result_2 = re.search(r'<(\d+)>.+"(\w+)"', 'abcdefg')
print(groups(result_2))  # <- コピペで実行するとエラーになります。
>>> print(groups(result_2))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in groups
AttributeError: 'NoneType' object has no attribute 'groups'
>>> 


アノテーションをつけると次のようになります。 長い...

import re
from typing import Match  # <- 本当に落とし穴
from typing import Optional
from typing import Sequence
from typing import Any
from typing import cast


# re.Match クラスのオブジェクトしか
# 受け付けない関数
def groups(result: Match[str]) -> Sequence[Any]:
    return result.groups()

# 1)
result_1: Optional[Match[str]]
result_1 = re.search(r'<(\d+)>.+"(\w+)"', '1v2<4649>90a"abcd"1a')
# groups 関数で受けられるようにキャストする。
cast_result_1 = cast(Match[str], result_1)
print(groups(cast_result_1))

# 2)
result_2: Optional[Match]
result_2 = re.search(r'<(\d+)>.+"(\w+)"', 'abcdefg')
# groups 関数で受けられるようにキャストはしない(None が返されるので)。
# cast_result_2 = cast(Match[str], result_2)
print(groups(result_2))


これを mypy で型検査すると、次のように怒られます。 つまり実行する前に「型に関する間違いだけ」は見つけることができたということです。

$ mypy sample.py 
sample.py:22: error: Argument 1 to "groups" has incompatible type "Optional[Match[Any]]"; expected "Match[str]"
$


ただ C 言語もいちおうコンパイル言語で "弱い型付け" ではありますが、 静的型検査しているはずなので、C はダメで Rust は大丈夫の感覚がまだ正直あまり掴めていません...


Go には、例外はありません。Rust が1つの変数に「正常な値」か「例外」を詰め込むのに対して、 Go は2つの変数、「正常な値のための変数」と「例外のための変数」を用意して対応します。


Go ではこれに加えて、例外的なことが起こった時に、defer, panic, recover が実質的に例外と同等の機能をになっています。 上記引用した記事によると、これがオーバーヘッドになっているそうです。

Python の例外が「ズドンと爆発」して try 文で捕まえるイメージなのに対して、 Go の 「panic を起こすと巻き戻」って、それを recover で回収するイメージです。 The Go Blog を翻訳しました。


あまり関係ないですが JavaScript は undefined が多用されてて、辛いです。 Python の None は JavaScript の null です。null は有り無しの「無し」を表現しているのに対して、 undefined は「未定義」を表現しているオブジェクトです。

Python では re.search みたいに、未定義を表すために None を返すのはあまり多くないんじゃないかなと感じています。 例外を返されると try 文で包めなくなって、リスト内包表記, ジェネレータ式, lambda 式を引数に取る高階関数(例えば map, filter など)で使えなくなるので、 一概にどちらがいいとは言えないと思うのですが...

Python は None を返すくらいなら例外が返されることが多いような気がします(書籍『Effective Python』Item 14: Noneを返すよりも例外を発生させよう)。 ざっくり言えば変数に格納されるオブジェクトの型が2種類になるくらいなら、 Optional になるくらいなら、例外が返って来ます。

なぜこのようなことになっているかというと、Python は基本は、静的型検査をしないことが前提になっているからだと思っています。 個人的には型検査をしない Python の場合は、例外を投げるか None 返すか、どっちがいいか迷ったら、 例外を投げる方がベターかなと、思ったり思わなかったりします。

◯ Rust の非同期

最近 async, await というのが来たらしいです。

Before delving into the current status, it’s worth taking a moment to recap the core motivations for async/await, and its special importance for Rust.
Async in Rust, circa 2018



過去に Rust が非同期サポートを標準から切り離した流れが書かれている。 いくらか、苦闘した形跡が見られます。

Rustは昨年に非同期サポートを標準から切り離しました。事実上システム記述用途に特化していく模様です。
Rustと競合? - Goへの誤解について


  • 2018 年にもなって非同期 IO で疲弊している
  • Rust は 2006 年に開発が始まった
  • node.js も go も 2009 年には存在していた
  • 今まで Rust は何をやっていたのか?
    Rust と非同期 IO の歴史 - Qiita



Go は非同期処理を強く意識しているみたい...

Goの場合、アプリケーションは普通の同期プログラミングをするだけで、 非同期プログラミングのメリットを享受することができます。
Go が他の多くの言語での非同期プログラミングよりも優れている理由 - Qiita


非同期が苦手だからマルチコアも苦手なのかなって思ってたのですが、その理解は間違ってるようでした... Rust は、どういうアプローチをとっているんだろう...。 ご指摘いただき、誠にありがとうございます。

◯ 速度

速度は Rust の方が出るみたい

The Computer Language Benchmarks Game
There really isn’t any argument: Rust is faster than Go. In the benchmarks above, Rust was faster, and in some cases, an order of magnitude faster.
Should I Rust, or Should I Go














あるいは
Python, Ruby, Perl, PHP
もっと高速に書きたい
→ Go

C/C++
もっと安全に書きたい
→ Rust



別にそういうわけではないと思うのですが、自分のごくごく狭い観測範囲 4~5 人の人を Twitterネットストーキング 非公開リストにいれて1日中尊敬の眼差しで眺めていると C++ をさわっている人は Rust 触っってるし、Python さわってた人は Go をさわっている印象があります(※あくまで個人の感想です。)。 なぜかはわかりません。


ただ Flask という有名な Pythonウェブアプリケーションフレームワークの作者の Armin Ronacher は Rust に移動したみたいです。


Armin Ronacher は Python の型システムが、ゆるゆるなんだよこの野郎!と切れていたので、 型システムが結構ガチガチにしっかりしているらしい Rust に移って行ったのはなんか気持ち的にわかる気がします。

Go の型システムは、いくらか緩いみたいです。

JavaGenericsは言わばただのLint。インライン化できず動的ディスパッチなのでくそ遅い。 JavaやGoにまともな型システムをいれるのは手遅れで今後も遅いままなのでそこに納得できない人は素直に他の言語を使用しましょう。
megumin1 氏のコメント2017/03/10 00:04


動的ディスパッチというのは、どのメソッドを起動するかを実行時に判定するということ。 型が緩いとその都度、起動するメソッドを選択するために、どの型か判定する処理が走る(ということを言っていると思っています)。

◯ 用途

システムプログラミング言語 - saneyuki_s log
Go は Compiled Pythonともいうべき立ち位置な気がする。PythonとかPerlとかRubyとかシェルスクリプトとか以上C未満な箇所を、JavaScalaよりももっとスマートに置き換える、そういう意味での「システム」開発言語。

対する?Rust は、カーネルとかブラウザエンジンとかゲームエンジンとか、ハードウェアに近いエリアの計算機資源をがしがしと叩きまくるための言語。C/C++の面倒くさい因習やエクストリームな部分をうまく隠蔽しつつ、時々必要になったらunsafeブロックで例外的に許容する。その安全性の担保として、コンパイラを使った静的チェックをCPUとメモリにものを言わせてブイブイとかけまくり、機械さまに網羅的にチェックしてもらう。これはこれで「システム」言語。

と考えると、一部の急進派Pythonista(RubyPerlの人も含む)やGo信奉者がなぜRustをdisったり揶揄するのかよくわからない。上手く住み分けできそうなものではあるけど。

◯ Go が採用された例

もともと Python で書いていたものを Go で書き直す。

今年の初め、我々は Stream の主要言語を Python から Go に切り替えました。
Why we switched from Python to Go

◯ Rust が採用された例

もともと C で書かれていたものを Rust で書き直す。

少し調べてみると、私はすぐに納得しましたが、BINDの一番大きな問題は「Cで書かれていること」というのが明らかとなりました。
Rust と DNS の1年 - POSTD


この著者が Go よりも Rust が素晴らしいと感じたのは、著者が扱っていたものがどちらかというと C とか Rust が取り扱うものに近しいからかなと思ったりもします。

私は何かを書くたびに古くて濁ったプログラミング知識の膜が自分の目からキレイに拭き取られていくのを感じました。その光を見てしまったらこの約束の地に降り立ってしまったらもう引き返すことはできません。
Rust と DNS の1年 - POSTD





◯ 用途

Go と Rsut は全く用途が違うそうです。なので、自分の用途に合わせて選ぶことになるかなと思いますが...

Rust と Go は競合しない
Go は、アプリケーションの厳格なシンプルさを通して、規模の大きなチームが効率的にプログラミングできるようにすることを目標にしています。 ー Go は、シンプルでないもの、あるいは 非直交性 を招く恐れがあるアイディアを採用しません。

Rust は、安全でないメモリ参照やランタイムオーバヘッドを許容せずにプログラミングできるようにすることを目標にしています(Go もまた安全でないメモリ参照を許容していません、新しいメモリ不安全な言語に需要があるとは思えません)。ー Rust は安全でない、あるいはオーバーヘッドが生じるようなアイディアを採用しないか、少なくともそのようなアイディアを言語の中心部に据えるようなことはしません。

Rust and Go are not competitors
Go is focused on making large teams of programmers efficient, though rigid application of simplicity — ideas which are not simple or lead to non orthogonal corner cases are rejected.

Rust is focused on a class of programs which cannot tolerate unsafe memory access (neither can Go, I don’t think there is any appetite for a new unsafe programming languages) or runtime overhead — ideas which are unsafe, or come with overhead, are rejected or at least pushed out of the core language.

RedMonkプログラミング言語ランキング
そういったなかには、バックエンドシステム言語として急上昇していたGoがほぼ横ばいになっていることも含まれている。 Goは2015年6月期のランキングから15位を保っていたが、今回TypeScriptが躍進した結果、初めて順位を落として16位となった。 O'Grady氏はGoがバックエンドの言語としての役割を超え、例えば Java のような言語ほど多くの用途に使われるようになるとは考えていない。

Go はランタイムを肥大化させるデザインをとってしまったので、 C, C++, Rustに比べて wasm では根本的に不利。 確実に負債になるといわれていた Go の欠点が wasm ではっきりと表面化しているので、 あまり期待しないほうがよいです。 (どういうデザインなんだろう...)
"Go言語がWebAssemblyをサポートへ " に対する megumin1 氏のコメント




そもそも、用途が違うので、比較すること自体あまり意味がないかもしれません。正直、あまり非同期処理を意識して書いたことがないので、もう少し違いを探ってみたいと思います。

Google トレンド

技術的なトレンドとかはよくわからない。Go は登場してから Rust に比べれば急激に人気を増やしたような気配がある。後発の Rust はじっくり、じんわりな感じだ。

Go が一気に Rust を突き放して駆逐しそうに見えるけど、ここ1年に限定すれば Rust と Go は横ばい。数字だけ見ると、このまま安定してお互い共存しそうな気配がある。


Rust はとても素晴らしい言語だと聞くのですが、結構、難易度が高いらしく、使おうと思う人が少ないのかなと感じます。 あまりよくはわからないのですが...。

技術的には、優れているものよりも簡単なものが、受け入れられやすい気がします。 例えば通信の規格でガチガチな ATM, SONET/SDH がなくなって、 とりあえず送ってから考えるわ、ポチっとな、っていう Ethernet が受け入れられたように(ベストフォワード感の無い表現...)。

それでもプログラミング言語は、別にインフラではないですから、どちらかが一方を駆逐するということはないかなと思います。 適材適所で Rust が適している箇所は Rust が Go が適している箇所は Go が使われ、 どちらでもいい場合は、好き方を Go を使ったりしてるのかなと思ったり思わなかったりします。

難易度の高いものは、難易度が高く、大抵破綻しやすい気がするのですが(なんだこの日本語)。 その難易度の高いものを綺麗にまとめ上げたという噂を聞く Rust に、どこか魅力を感じたりもします。

◯ 文法

Go は色々機能を削って作られた言語らしい。Google はサービスの機能を意図的に削除したりする傾向にある。GAE は JOIN を削ったし、Chrome は綺麗さっぱりいろんなボタンをブラウザから削った、検索サイトのトップ画面も Yahoo! やら msn とは違う、検索欄だけのページを設計したりした。*1

リファクタリングしていて関数やらコードを削れたときは、結構気持ちいいものだし、誰が書いても同じようなコードになるというもちろんそれはすごく惹かれる..

けど、やや辛い時がどうしても生じてくる気がする.. GAE みたいに。Python から Go に書き直すみたいに、ある程度先が、開発するものの全体像が見えていれば採用もできるかもしれないけど。

Go はいろんな機能が削られているので、たまに "物凄い" 貶され方をしたりするのを、たまに見かける。


  • Goは何も新しいことをしない。
  • Goは全てにおいてうまく設計されているとは言えない。
  • Goは他のプログラミング言語から退化したものである。

Go言語がダメな理由 -POSTD


こんな "物凄い" 貶され方をする理由は、 その人たちのアイデンティティを挑発してるからっていうのは、面白かった。

それって、Goのシンプルな言語哲学が、ML系言語好きのアイデンティティを挑発しちゃってるからじゃないの?
[翻訳]なんでGoってみんなに嫌われてるの? - Qiita


ただ、高機能で抽象化して簡潔にできることは、必ずしもいいことだけではない。 色々な書き方ができてしまうと言うのは、それなりに弊害もあったりする。

コードが理解しづらい
  4. コメントなしに低レベルの最適化が施されている
  5. コードが賢すぎる
コードが追いかけづらい
  4. 全てが抽象化されすぎている
まずコードの可読性を最適化しよう


例えば Python は、「インデントを強制して誰が書いても同じような」と主張されてるよりは、意外といろんな書き方ができる。もちろん、どんな言語だって書こうと思えばいろんな書き方ができる。「同じような」なんて言うこと自体若干怪しい。

ただ、意外といろんな書き方ができる一つの要因としては Python が高機能であることが挙げられると思う。これらの機能を使うことで、抽象化を上げて、より簡潔な記述で記載することができる。反面、その機能を使うか使わないかで、書き方に幅が出てくる。

実直に and で全部書き上げてしまのかとか、それとも all 関数で条件文を書くのか。 実直に 関数で処理するのか、それともメソッドで処理するのか。

"""矩形の等号 == を表現する書き方について考える."""


def sample_code():
    # 書き方1 実直に..
    assert eq(Rectangle(1, 1, 2, 2), Rectangle(1, 1, 2, 2))
    
    # 書き方2 Python の機能をフルに使って..
    assert Rectangle(1, 1, 2, 2) == Rectangle(1, 1, 2, 2)


# こんな矩形のクラス
class Rectangle(object):
    def __init__(self, x1, y1, x2, y2):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2


#
# 書き方1 実直に..
#
def eq(rectangle_a, rectangle_b):
    return \
        rectangle_a.x1 == rectangle_b.x1 and \
        rectangle_a.y1 == rectangle_b.y1 and \
        rectangle_a.x2 == rectangle_b.x2 and \
        rectangle_a.y2 == rectangle_b.y2


#
# 書き方2 Python の機能をフルに使って..
#     1. 演算子オーバロード __eq__
#         all 関数, zip 関数, ジェネレータ式
#     2. イテレータ __iter__
#         yiled 文
#     3. メソッドの動的な追加
#
def __eq__(rectangle_a, rectangle_b):
    return all(a == b for a, b
                    in zip(rectangle_a, rectangle_b))


def __iter__(rectangle):
    yield rectangle.x1
    yield rectangle.y1
    yield rectangle.x2
    yield rectangle.y2


Rectangle.__eq__ = __eq__
Rectangle.__iter__ = __iter__


#
# Execute the sample code.
#
if __name__ == '__main__':
    sample_code()


書き方2は、本来 iterable 出ないものを無理やり iterable にして抽象化の度合いを上げてるので読み辛い。ここでは可読性は置いておいて、書き方に幅があることだけを考察。適切に抽象化したものは、表現も簡潔になり可読性も上がる。yield 文, generator なんかも、うまく使えばコード数を半分以下にすることができる。 * ジェネレータ関数から別のジェネレータ関数を呼び出したい - Python のジェネレータってなに?

狂気の沙汰だけど Python でも実行速度を求め出すと、稀に低レベルに記述したりし出したりする。関数呼び出しのオーバーヘッドを削るためにベタがきするとか。

$ # 関数を使ったコード
$ python -m timeit "max(2, 1)"
1000000 loops, best of 3: 0.209 usec per loop
$
$ # ベタ書きのコード
$ python -m timeit "2 if 2 > 1 else 1"
10000000 loops, best of 3: 0.0395 usec per loop
$ # 関数を使ったコード
$ python -m timeit -s "import math" "math.floor(4 / 3)"
10000000 loops, best of 3: 0.169 usec per loop
$
$ # ベタ書きのコード
$ python -m timeit "4 // 3"
100000000 loops, best of 3: 0.0132 usec per loop
$ # 書き方1 実直に..
$ python -m timeit -s "from rectangle import Rectangle,eq" "eq(Rectangle(1, 1, 2, 2),Rectangle(1, 1, 2, 2))"
1000000 loops, best of 3: 1.94 usec per loop
$
$ # 書き方2 Python の機能をフルに使って..
$ python -m timeit -s "from rectangle import Rectangle,eq" "Rectangle(1, 1, 2, 2)==Rectangle(1, 1, 2, 2)"
100000 loops, best of 3: 4.33 usec per loop
$


処理を速くしようとして、こんなこととか、 for 文で書こうかなとか while 文で書こうかなとか、そんなことを気にしだしたらいっそ思いっきり Java などの静的言語に切り替えを検討する時だと思う、あるいは Rust か Go か。

なぜなら、抽象化の一貫性を犠牲にしてまで工夫しても、部分的に見れば何倍も速くできるけど、全体から見れば高が知れていて数%も速くできるかどうか..。でも、言語を切り替えれば何倍も速くなる。

Python は、適切に抽象化具合をあげて短く書くことを前提にしている。何故なら、Python がそういった機能を提供しているし、PEP 8 - Style Guide for Python Code でも 1 行最大 79 文字とごく短く制限されているから。
Python で 1 行を最大 79 文字以内に抑える方法とその理由

低レベルに書き込むことは、PEP 20 - The Zen of Python の言う、たったひとつだけあるはずのストレートなやり方ではないと思う。

何かをやるには、ストレートなやり方がひとつ、
たったひとつだけあるはず
There should be one-- and preferably only one --obvious way to do it.
Python にまつわるアイデア: PEP 20 | Life with Python


Rust がネックになるのは、抽象化の一貫性にばらつきが生じてしまう可能性があること。Go がネックになるのは、どうしても可読性に欠けてしまうこと。それに耐えられなくなるったある日、突然 Rust の光を浴びてしまうのかもしれない。ちなみに Rust は抽象化のコストも0コストらしいです。Python は、抽象化するといくらかオーバーヘッドがあります。

ただ、学習するっていう意味で言えば、Go と Python という組み合わせは、両極にあっていいのかもしれない..。Go と Python で切り替えて気分転換しながらみたいな。人間、抽象具合をあげて簡潔に書きたい時もあれば、ガリガリ低レベルに書きたい時もある。



でも、もっと Go と対極にあるのは Ruby だと思う..。

昔、PythonRuby を比較して Ruby は、自由に書けるから遊びやって just for fun や見たいな感じで YouTube でディスってる人がいて、炎上してる動画を見たことがある。

でも、楽しいってすごく大事だと思う。もし気の合う人とだけで少人数でプログラミングができるなら、きっと Ruby は楽しい言語だと思う。Rails が出てきて Web サービス組むことに対して Python に比して1日の長が得られたのもそういった所にあるのかなと思う。

Python の framework を探してたときに、ほんの少しだけ CakePHP をかじってたから、当然、インデントを強制して誰が書いても同じようにと歌っている Python の framework も当然、設定より規約的なものだろうと思っていたけど、現実はそうではなかった。

Django は大きすぎて触ってて違和感があって。色々と自由に作れるように、ちゃんと設定してねっていう感じ。でも、そこまで作り込むなら Java なりなんなりに逃げると思うし。別にそんなに大きいもの作らなくていいから、程よく小さいものが作れるのが欲しかった。

だから Rails どうなんだろうって思ってコード見たときは、Rails の光が差し込んできて慌ててドアを閉めた記憶がある。自分の人間的な余裕がなくて Ruby を触れないから..

でも、面白いのは Ruby は自由にプログラミンを楽しもうって言ってたのに、出てきた framework は設定より規約で、反対に Python はそうではない。Django がデカすぎるからって出てきた flask とかは、今度は小さすぎて、自分で好きなものをツギハギして自由に書いてねって感じになっていて、そうじゃないって思った。

お互い歩み寄ってるような感じがして、心地よくプログラミングができるって言うのは、単純にこっちがいいとは言えず、繊細さがあるのかもしれない。極端に規約とかに縛られるのが好きな自分は、やっぱりキチガイなのかもしれない。

プログラミング言語に限らず、自然言語も楽しさとかを元に変化したりする。例えば黒人英語とかはその最たるものだと思う。文法の一貫性よりも音感の良さを取り入れて変化してる。*2
言語変化
黒人英語

さらに言えば人は使う言語によって、思考に影響を受けたりもするらしい。難しくてよくわからない。
言語的相対論
使う言語が「世界の見え方」を決めている:研究結果
言語は思考にも影響を及ぼす、だからRuby開発を選んだ
あんな姉と妹の区別もつかない劣等言語でエロゲができるか

日本語も主語とか目的語を省略したりするから、黒人英語が音感を大事にするのとは違うけど、相手とのなんとなくのフィーリングを共有する、会話そのものを楽しむ言語だと思う。そう言う意味ではプログラミングそのものを楽しむって言う Ruby は、どこか日本語的な言語だなと思ったりする...

結局 Rust か Go かと言うのは、そう言った個人の繊細さの問題なのかもしれない。抽象化して簡潔に書きたいなら Rust だし、低レベルにガリガリ書きたいなら Go みたいな。 (2018/05/18: 削除)

Go

Goはオブジェクト指向言語だろうか?
実装継承ってあかんのやな..
Go(golang)についての雑感 | Qiita
Java 使いが Go言語を一日使って思ったこと | Qiita
オブジェクト指向言語としてGolangをやろうとするとハマること | Qiita

ポジティブな記事

Why we switched from Python to Go
または私は如何にして例外するのを止めて golang を愛するようになったか
(go report) Goが本当はすごかったので, 謝罪する

ネガティブな記事

Go言語がダメな理由

Rust

Rustとは何か。どんな言語か。- κeenのHappy Hacκing Blog
Rustは何が新しいのか(基本的な言語機能の紹介)
立ち位置とか

ポジティブな記事

RustとDNSの1年

ネガティブな記事

見当たらない





*1:SNS系のシステムについては、余分な機能を削り必須の機能に絞ることが、必ずしもいい結果をもたらすとは限らない。蛇足と思われる機能を付け加えたり(mixi の足跡)、あるいは本来必要なはずの機能を削ったりして(Twitter の文字数の制限, 増田の匿名)、コミュニケーションの活性化を測ることができる。SNS では、何をコミュニケーションするかよりも、どうコミュニケーションをするかの方が重要なのかもしれない。Google が何故か SNS 系のサービスだと苦戦する傾向があるのは (Blogger, Google+)、こういったところにあるのかなと思ったりする。

*2:全然話は違うけど中国語の動詞には過去形がなかったりする(時制, テンス)。中国 4,000 年の歴史とは一体何だったのか。かわりに、昨日(昨天)とか時間を表す単語で過去を表現したりする。でも、過去形ではないけど、完了形(了) は存在する(相, アスペクト)。単純に動詞に活用がない孤立語だからという話でもあるけれど。相があるなら時制だって補語、助動詞として存在していいはずだと思うけど。同じ孤立語でも、チベット語タイ語には、それに近いものがあるらしい。それが無いということは、中国の人たちは、いまという歴史の中に生きているのかなと思ったりする。