【WIP】プログラミング言語に関して思う雑記
ActiveBasicからプログラミングに入門しC/C++を学びOCamlを経て紆余曲折し今はメインにSwift,Rust,サブでTypeScriptに落ち着いた人間の私見です.
このスクラップを書き始めたきっかけはNimです.
優秀なのに無名のNimです.
Cにトランスパイルすることで改めてC言語の最強っぷりを改めて見せつけているNimです.
ただの虎(C)の威を借る狐(Nim)と思いきや言語仕様もそこそこ良く,何より人が書くより賢いCを吐いてくれます.
ただ結論から書くと,現状のNimではどうしてもCでなければならない要件を書く場合にBetterな方法として採用する程度に収まると感じています.
最近はWebのフロントエンドでJavaScript以外の言語も動くようになってきましたが,それでもJavaScript一強には違いありません.Dartのように無理矢理フロントエンド言語を作ってもスタンダードにならなければ廃れます.結局はJavaScript一色です.
かといって特殊な理由もなくJavaScriptをそのまま書くのはやめましょう保守できません.代替案としてはAltJSのTypeScriptやCoffeeScriptで書くべきでしょう.
Nimも同じです.C一強の世界がありCを書くべき状況は少なからず存在する.が,生のCを書くのは馬鹿らしいのでNimを書きましょう.そんな位置付けです.きっとVも同じ.
さてそんなNim.
プログラミング言語は言語自体の表現能力と出力結果(≒バイナリ)の性能という2点で評価できます.その観点で見ると,Nimは出力結果は良いものの表現能力が他より劣っていると思います.
これは一重にCを吐くからでしょうか.シンプル故のCの性能は得られるものの,Cにない仕様はランタイムで実装することになります.具体的にいうとCにvtableはないので動的ディスパッチは泥臭い中身になります.
JSを忘れられないWebフロントエンドのようにCを忘れられない世界があり,その世界で使われるためにCを吐くのはありですが,表現力を狭めていると思います.
SwiftやRustが豊かな表現を効率よく獲得したのはllvm-asmを吐くからでしょう.基本スペックがC++と同等の表現力です.
Nim(とRust)の比較記事をよく見かけるのですが,そこだと実行速度とバイナリサイズの小ささで比較されています.Nimは実質Cでしかないので,常用traitがランタイムとしてくっついて実質C++なRustよりバイナリサイズが小さいのは当たり前です.
もちろんコード量が増えてくるとその差は縮まるでしょう.
ただ,容量の小さいマイコンに焼きたい,wasm用にバイナリを小さくしたい,AWS Lambdaのようなサーバレスを効率よく利用したいといった状況以外でバイナリサイズをゴルフすることはほぼ無意味でしょう.
(少し脱線)
言語自体の表現能力という観点だとモダンな言語の中ではSwiftが最も優れていると考えています.Copy-on-Writeで値中心な言語というだけでも革新的ですがそれだけではありません.言語の表現能力の真価はメタプログラミングに現れると考えています.
既存のプロトコルを拡張して機能を追加できる,独自のオペレータを実装できる,といった機能を提供しているモダンでマーケットシェアの大きい言語は少ないです.
protocol Twicer {
assosiatedtype Result : BinaryInteger
var currentValue: Result { get }
}
extension Twicer {
func getTwiceValue() -> Result {
2 * self.currentValue
}
}
上記のように,あるプロトコルに基づくその派生メソッドを定義しておくと,
下記のようにそのプロトコルを継承するだけで派生先のメソッドも使えるようになるのはRust,Nimにはできません.
プロトコルでcomputed propertyとして求められるものをstored propertyで実装することもできません.
struct SomeTwice : Twicer {
let currentValue: UInt32
}
let hoge = SomeTwice(currentValue: 10)
let fuga = hoge.getTwiceValue() //
応用としてはこんなものが考えられます.
今のRustだと以下のようにtraitにメソッドを追加で生やす書き方はできません.
trait Twicer {
fn get_current_value() -> Int
}
impl Twicer {
fn get_twice_value(&self) -> Int {
2 * self.get_current_value()
}
}
Nimもできません.
他にもプログラミング言語の持つ表現機能のみでDSL(SwiftUI)を実現したり,Existential Typeによって動的ディスパッチなく型情報を隠蔽したり,他の言語が霞むほどにSwiftの表現能力は高いです.
一方でSwiftの実行性能はRust/Nimに劣っています.
Swiftは頭ゆるゆるな子なのでunsafeなポインタ処理もゆるゆるに許し,メモリ処理もかなり無茶なこともできます.が,それでも性能はRust/Nimには及びません.この辺りは暗黙のCopy-on-Writeが足枷になっています.
言語の表現能力という評価だとRustが次点に挙がります.
言語自体の表現というよりは,コンパイラの担える範囲を広げたようなイメージです.
メモリの確保・解放・排他利用という手続きを所有権という形で言語仕様に含め,メモリの管理を抽象化しつつも厳格な管理に持ち込んだ点が進化だと考えています.
(もちろん,trait・マクロによるメタプログラミングもNimに優っていると思います.)
ただ所有権という概念はメモリ管理を厳密にするために導入されたもので,必須なものではありません.少なくとも私は暗黙に動くGCより明示に動くRCの方が好きですが,そこに関心を持つプログラマはそう多くないでしょう.問題を解くことやサービスを提供することがプログラムの目的ですが,その目的に対してメモリの管理なんて些細な問題です.(解く問題の大きさや提供するサービスのスケールによる)
正直に言うと "将来的に使われないメモリの解放" はOSが担うべきタスクと思っています.もっと言うと "解放の必要なメモリの提供" というレガシーな枠組みに捉われないコンピュータを作るべきでしょう.ある意味リザバーコンピューティングは後者の枠組みの特殊例だと思います.
いずれにせよRustのもたらした所有権の概念は大きな転換点で,GC一強の時代に一石を投じたとは思っています.が,間違いなく銀の弾丸ではありません.メモリ管理の一挙一投足全てをプログラマに指示させるのは正直ノイズだと思っています.部下の細かい仕草まで指示したい上司はそう多くはないのと同じです.
私はRustも好きですが,所有権より指示量の少ない,Rustより優れた概念が出てくると思っています.Rustのようにランタイムに使われるリソースをコンパイルタイムに寄せるだろうという見立ては この記事に同意です.
もう一つこのスクラップに書こうと思った記事がこちらです.
私もこの記事に全面同意です.ムーアの法則の限界が来たためか,CPUのマルチコア化は著しいです.
こんなに早く50コア以上のCPUが民生に降りて来るとは予想していませんでした.
加えてOpenCLの頃から謳われていたヘテロジニアスなコンピューティングというのも当たり前になってきました.名前を上げるならばApple社のM1 Chipです.ARMのCortexを主軸に添えるためベクトル演算ができ,高効率コアと省電力コアの強調によってタスクを分配し,GPUとニューロチップを駆使してあらゆるタスクに臨む化け物チップです.(今のところニューロチップのAPIは公開されていません(CPUを使ったBNNSかGPUを使ったMPSしかない)が,将来的には公開されると思っています.ただしモデルをロードして走らせるだけの汎用には程遠いものと思います.)
このように,メインCPUの多コア化は当たり前で,加えて様々な計算資源を効率よく使うようにコードを書く.そんな時代が目の前どころか既に訪れています.
現状ではCPU用のコードとGPU用のコードを分けて書きます.そもそもアーキテクチャの異なる計算資源を同一のコードで管理するなどパフォーマンスの犠牲しかありません.(OpenACCから目を背けながら)
……何の話だっけ
Nimを触って感じたプログラミング将来像を書こうと思ったら脱線しまくってた.
小休止.
読み返すと恥ずかしくなってきたから記録だけ残して閉じようかな.
そういえばNimのバックエンドをObjcにした時ってどういうGCになるんだろ?
Rustはリソースの管理を徹底的に実装者に委ね,その通りのバイナリしか吐かない.書き手には楽しいものの,ほとんどの場合はNimで生成されたCの方がスマートなリソース管理デザインになってるんだろうなと思う.
Cで昔ながらの難読な人力最適化を施すより,分かりやすく冗長なコードを書いてコンパイラに最適化させた方がステップ数の少ないアセンブリになるとかと同じ話がRustとNimの間にあると思う.
Swiftの暗黙Copy-on-Writeは便利だけど困ったちゃん