Rust なのか Go なのか

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




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



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

概観

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

引用記事 1

Go は文法がとてもシンプルです。 誰が書いても同じようなコードになるようなことを意識しているのかなと思われます。 それが元でよく Go を貶す記事が見受けられます。

Rust はコードが複雑になっても、ガシガシ書き込むような感じらしいです。 その分だけ Go よりも高速に動作します。

Rust と Go は競合しない
Rust and Go are not competitors

Go は、アプリケーションの厳格なシンプルさを通して、 規模の大きなチームが効率的にプログラミングできるようにすることを目標にしています。 ー Go は、シンプルでないもの、あるいは 非直交性 を招く恐れがあるアイディアを採用しません。
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 は、安全でないメモリ参照やランタイムオーバヘッドを許容せずにプログラミングできるようにすることを目標にしています (Go もまた安全でないメモリ参照を許容していません、新しいメモリ不安全な言語に需要があるとは思えません)。 ー Rust は安全でない、あるいはオーバーヘッドが生じるようなアイディアを採用しない か、 少なくともそのようなアイディアを言語の中心部に据えるようなことはしません。

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.

引用記事 2

Go は非同期処理が得意らしいです。 並列分散を土台に据えたブロックチェーンなどで採用されています。 対して Rust は非同期処理の導入に向けていくらか苦闘した形跡が見受けられます。

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

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

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

引用記事 3

Python を触っていた人は Go を。C++ を触っていた人は Rust を触っている印象があります。 自分が Twitter でフォローしている 4, 5 人程度のごく狭い観測範囲の話です。

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

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

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

用途

◯ Go

ブロックチェーンとかの分散処理系は Go が強いのかなと... この方がどういう方かはあまりよくわからない...


並列処理が得意という認識の中で Go が採用されたりするらしいです。

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

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

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

あと並列処理では無いですが仮想コンテナを作る Docker も Go で実装されているらしいです。

◯ Rust

WASM のようなクライアントでシングルに動かすような場合は、 Rust が強そう。

megumin1 氏は、はてブ読んでると、とても怖そうな方ですが、 仰られてることはとても勉強になります。

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

@mizchi 先生は、JavaScript 界隈でとても、とてもすごい人らしいです。 @mizchi 先生の Twitter を追っていると Rust に流れていきそうな雰囲気を感じます。

速度

速度は Rust の方が、出るみたいです。 Go の方が言語的な機能を削っているから、速度が出るのかなと思っていたのですが、違いました。 Rust の方が、ガリガリに書き込むようなイメージです。

Benchmark of Rust and Go

議論の余地は全く無い: Rust は Go より速い。 上記のベンチマークでは Rust は Go より速かった、 いくつかのケースでは段違いに速かった。
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.
The Computer Language Benchmarks Game

今後、Go言語がC/C++の役割を置き換えられるかというと、筆者はそうは思いません。 Go言語はスクリプト系言語よりは遥かに高速であるとはいえ、 C/C++と比べると性能は落ちますし、バイナリサイズもかなり大きくなります。
Goで覗くシステムプログラミングの世界

 オーバーヘッドが生じるようなアイディアを採用しない  例を、以下に2つご紹介いたします。

1. 代入を制限する。

Rust は代入、再代入を厳しく管理しなければなりません。 Rust は、ガベレージコレクションでのゼロコストを狙っているためです。 ガベレージコレクションとは使わなくなったオブジェクトを破棄する機能です。

Go は使わなくなったオブジェクトを自動的に破棄してくれますが、この処理が意外と重いというわけです。 以下の記事は後半は門外漢には厳しいですが、前半は割と読めます。

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

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

2. 例外が無い。

Python から来た方に向けて言えば、try 文を使わず if 文で例外を処理します。 Java から来た方に向けて言えば、 例外を投げずに Optional を返します。

Optional は検査例外と同じ考えです(Item 71)、 Optinal を使えば、値が何も返されないかもしれないという事実を API に直視させます。
項目 55: Optional を慎重に返す - Effective Java
Optional are similar in sprit to checked exceptions(Item 71), in taht they force the ensure of an API to confront the fact that there may be no balue returned.
Item 55: Return optional judicitously - Effective Java

if 文の例えよりも Optional の方が、より近いかなと思います。 実は Python にも Optional の概念があります。 Python から来た方に向けて、いま頑張って書いています。

以下の文章を読んで 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 言語のエラー処理に対するアプローチは人間工学に反しかつ安全ではありません という文言についても、 try 文ってなに? の中で説明する予定です。

Go の例外

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

ただ実際のところ Go も Rust も panic という例外と似たような機能を持っているらしいです。 Rust も Go も panic は基本使わない方向らしいですが。

ただ、上記引用した英語の記事によると、Go の場合は、 たとえ例外が投げられなかったとしても、各 try ブロックに対してパフォーマンスペナルティを喰らいます(Go の defer においては)。 とのことです。これがオーバーヘッドになってしまうそうです。

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

Rust の方の panic は、全くよくわかっていません。

例外は不要なのか?

Go にも Rust にも例外はありません。 では例外は不要なのでしょうか。そうでは無いと思います。 例外には、欠点もあれば長所もあります。 以下の記事で例外を使ってコードが綺麗になる例を示してくれています。 本文は読まないでコードだけ流し読みしました。

Rust に例外が無いのは、ゼロコストを狙っているからです。 Go に例外が無いのは、例外で書かせるのか、それとも if 文で書かせるのか、 という判断をプログラマにさせて疲れさせないためだと思っています。

以下、再度引用します。

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

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

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

人気

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

Trend of Rust and Go for years

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

Trend of Rust and Go for a year

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

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

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

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

Go の型については、いくらか緩いらしいという話があります。

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

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

自分のごくごく狭い 4, 5 人程度の観測範囲だと C++ を触っていた人は Rust を触っていて Python 触っていた人は Go に流れたような印象があります。 それも Twitter でフォローしているだけですが。

C++ をさわっている人は Rust 触っってるし、 Python さわってた人は Go をさわっている印象があります(※あくまで個人の感想です。)。 なぜかはわかりません。

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

Armin Ronacher は Python の型システムに対して、緩いって怒っていたので、 型システムが結構ガチガチにしっかりしているらしい Rust に移って行ったのはなんか気持ち的にわかる気がします。

非同期

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

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

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

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

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

最近 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 は、どういうアプローチをとっているんだろう...。 ご指摘いただき、誠にありがとうございます。

文法

◯ 抽象化

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

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

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

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

Go言語がダメな理由 - POSTD

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

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

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

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

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

Google Python スタイルガイド
強力な機能
このような機能は避ける。

定義:
Python はとても柔軟な言語であり、 メタクラス、バイトコードへのアクセス、高速コンパイル、動的な継承、オブジェクトの親の変更、インポートハック、リフレクション、内部システムの変更 など多くの素敵 (変態的) な機能があります。

例えば 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 なんかも、うまく使えばコード数を半分以下にすることができる。

Go は、こういった抽象化の度合いを悩まなくて済みそうです。

◯ 多値

Python は tuple にするのかそれともユーザ定義クラスにするのか、というところで悩んだりします。 Python の tuple は、元々は ABC 言語の Compounds 型に由来しています。 tuple はデータ型の一種です。

以下の方が言う Go の多値周りが扱いにくいというのは、 そういったことを考える煩雑さから解放してくれる Go の長所だと、個人的に思います。 この方の記事を全部読んだわけではないのですが...

Go言語には多値という機能*1が言語に組み込まれています。 しかし、これが中途半端なうえ、 多値っぽい構文で多値ではないものがいくつかあるため、初心者を混乱させる原因になっています。
Go言語のイケてない部分

◯ ジェネリクス

あと、よく議論をされているのを見かけるのが、ジェネリクスがないことに対する指摘。 ジェネリクスとか、正直、全然よくわかっていないです。

ちなみに Google の Python のコーディング規約では、 単純な場合にだけジェネレータ式、言い換えれば map, filter を使うことを指示しています。

単純なら使ってもいいよ!
Okay to use for simple cases.
2.7 Comprehensions & Generator Expressions

Go では map, filter を使うか for 文で書くのか考えず、 男は黙って for 文で書くというのを意図しているのかなと思ったりします。

いや、むしろ何でもかんでもループで書くから、 「ちょうどいい関数が無い時」に書き方を変える必要すらないんですよ。 golang と Generics と吾 - Qiita

ただ、この男は黙って for 文というアプローチは、 バックエンドでは良さそうですが、 フロントエンドや、スマホのアプリの開発とは相性が良くなさそうな気配を感じます。

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

なぜ「多くの用途に使われるようになるとは考えていない」のか、 教えて欲しいです...

ジェネリクスに対する優先順位は低そうです。 なんで低いのか、Go の文法に対する設計背景みたいなのを探したのですが、 まだ見つけられずにいます。

どこかの時点で、ジェネリックは追加されることでしょう。 我々は、ジェネリックを急いで実装する必要はないと思っていますが、 一部のプログラマにとって必要であることは理解しています。
Generic型がない理由は?

ただ Go をのぞいていると、 色々な書き方をできないようにしている雰囲気を感じます。 しかし、ここでも megumin1 氏はキレッキレ...

この筆者に限った話ではないですけど、 ジェネリクスについて語るときに「Javaのジェネリクスもどき」という 極めて不適切な例を出しそれ以外の例を出せない人は、 ジェネリクスのことを全く理解していないど素人です。
megumin1 氏のコメント

◯ 間違った高速化

狂気の沙汰だけど 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 文字とごく短く制限されているから。

低レベルに書き込むことは、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

Go のリスクは、抽象化ができ無いことが原因で可読性を欠いたコードになってしまう可能性があること。 それに耐えられなくなるったある日、突然 Rust の光を浴びてしまうのかもしれない。

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

Rust のリスクは、抽象化の一貫性にばらつきが生じてしまう可能性があること。 ちなみに Rust は抽象化のコストも0コストらしいです。 Python は、抽象化するといくらかオーバーヘッドがあります。 Python はもともとが遅いので、あまり気にはなりませんが。

Ruby

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

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

昔、Python と Ruby を比較して 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 は、どこか日本語的な言語だなと思ったりする...








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

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

Last Updated: 5/29/2019, 1:48:24 AM