2019年6月15日土曜日

イケてないのに人気がある golang vs イケてるのに人気がない Nim

勉強の為に転載しました。
http://wolfbash.hateblo.jp/entry/2017/10/02/163017

ここ最近 GCP を持ち上げて Google のポチと化していましたが、そのポチが今回は Google に噛みつきます。
だって golang 酷いんですもん。何かセンスがぜんっぜん Google っぽくない。
せっかく作るのなら、もっと良いものにして欲しい、そう言う願いも込めてこのエントリーを書きます。
前回 Rust と比較してみましたが、今回はその golang 版です。
golang は言語のシンプルさを追求していて、言語的な便利機能が片っ端からありません。
それは覚えることが少ないことを意味します。学習コストを低く抑える事を根本的なポリシーにしている言語です。
裏を返せば完全に人を小馬鹿にしているんですが、その分ドキュメントなどがかなり充実しています。
構文的には、ほぼ C と同じです。そらそうですね、C言語の開発者のケン・トンプソンが関わっていますから。
一方 Nim は Python に強い影響を受けていて文法は Python そっくりです。そのためスッキリ書けて非常に見やすいです。Python をブラッシュアップして、秘伝の悪魔のタレをかけたような感じです。
面倒くさがり屋たちが作っている言語で、面倒なことを片っ端からシンプルに出来るようにすることをポリシーにしています。
面倒くさいことを簡単にするために、覚えることはかなり増えます。覚えれば覚えるほどやる事が簡単になっていくとも言えます。
ですが、面倒くさがり屋たちが作っているが故にドキュメントが壊滅的に存在しません。
「コードに付いてるテストコードでも読んでりゃ解るだろ、そんなの。」ってなもんです。「人は賢いんだ」と信頼しきっています。

golang はそのシンプルさ故、学んだところで特に新しい発見はありません。
Go is a language stuck in the 70's. (Go言語は 1970年代でぶっ止まった言語だわ)
と揶揄されている程です。
その場しのぎでしょうがないから、と言うあきらめムードで使うのならまぁ良いのでしょうが、決して素晴らしい言語だと思って使わない方が良いです。
golang をやっていると言語的なセンスが現代からズレる事をきちんと認識した上で使いましょう。
ではさっそく golang のどこらあたりがイケてないのかを中心に Nim のどこらあたりがイケているのかを書いていきます。

Exception がない

血反吐を吐くレベルで致命傷です。「お?C かな?」と思うような発狂処理を書いて楽しむことができます。
package main

import (
  "fmt"
  "errors"
)

func main() {
  if err := genError(); err != nil {
    fmt.Println(err)
  }
}

func genError() error {
  return errors.New("ERROR")
}
返り値を受け取って null じゃなかったら的な。地獄のエラーハンドリングを楽しめますよ。良かったですね。
errors.New なんて気取っちゃったもん使っていますが「int か string で良いじゃん!」と思いますよね。
Exception みたいに複数のエラーハンドリングしたけりゃこうですよ。
package main

import (
  "fmt"
)

type ERROR int

const (
  ERROR_NONE ERROR = iota
  ERROR_AAA
  ERROR_BBB
)

func main() {
  err := genError()
  if err != ERROR_NONE {
    switch err {
      case ERROR_AAA:
        fmt.Println("ERROR: AAA")
      case ERROR_BBB:
        fmt.Println("ERROR: BBB")
      default:
        fmt.Println("Something wrong");
    }
  }
}

func genError() ERROR {
  return ERROR_AAA
}
列挙型使って、switch 文。いやーー C ですなぁー。これに goto とか使っちゃったりしてますます C 感出しちゃうともっとおしゃれだと思うので是非やってみてください!
Nim はこうです。
type
  ExceptionAAA = object of Exception
  ExceptionBBB = object of Exception

proc genException(s: string) =
  if s == "a":
    raise newException(ExceptionAAA, "AAA")
  else:
    raise newException(ExceptionBBB, "BBB")

try:
  genException("a")
except ExceptionAAA:
  echo "ERROR: AAA"
except: # 残り全てのException を捕まえる
  echo "Something wrong"
finally: # finally も当然ある
  echo "FINALLY"
まぁ普通こうですよね。golang がおかしいんです。
ついでに Nim の宣伝も兼ねて Exception のパターンをちょっとだけ書いておきます。
# ExceptionAAA をキャッチしつつそのままそいつを throw
try:
  genException("a")
except ExceptionAAA:
  echo "ERROR: AAA"
  raise

# ExceptionBBB がスローされてきたら catch していないのでそのまま throw される
try:
  genException("b")
except ExceptionAAA:
  echo "ERROR: AAA"

try:
  genException("b")
except ExceptionAAA:
  echo "ERROR: AAA"
except: # 残り全てのException を捕まえてそのまま throw
  echo "Something wrong"
  raise

# Exception のメッセージを取り出したい場合
try:
  genException("a")
except ExceptionAAA as e:
  echo "ERROR: " & e.msg

# こうやっても取れるけどなんだか長い
try:
  genException("a")
except:
  var e = getCurrentException()
  echo "ERROR: " & e.msg
  var msg = getCurrentExceptionMsg()
  echo "ERROR: " & msg
ま、Exception 周りで困ることはなんもないです。凄くシンプルに書けますよ。

main と package を毎度書かされ、Printf も長い

package main

import "fmt"

func main() {
  fmt.Println("Hey Golang")
}
Java よりはマシですが非常に鬱陶しい感じではあります。
fmt. をイチイチ書かなきゃならんのが非常に面倒です。
ここだけ見ると C の方がマシに見えます。
#include <stdio.h>

int main() {
  printf("Hey C\n");
}
こうあれよ!と言う形、それは Nim にあります。
echo "Hey Nim"

ガミガミうるさく言われる

使っていない変数があるだけで文句を言われコンパイルできません。
使っていない import も同じく文句を言われコンパイルできません。
開発中は、このくだらない縛りのお陰でコメントアウトをしまくる羽目になり超絶イライラさせてくれます。
これだけでこいつを投げ捨てる価値が十分にあります。
「なんのメリットがあるんだよこれ!せめて warning にしろよ!」と誰しも思っていますが golang 開発陣は無視し続けています。
Nim はコンパイル時に Hint として出てくるだけです。こんなもんその程度で良いんです。
書き方もイチイチうるさいです。
この書き方は、コンパイルエラーになります。
if i == 1 {
  fmt.Println("HOGE")
}
else if i == 2 {
  fmt.Println("FUGA")
}
こう書かされます。
if i == 1 {
  fmt.Println("HOGE")
} else if i == 2 {
  fmt.Println("FUGA")
}
そこに選択の自由はありません。
コーディング規約が必要無くなるメリットよりもコーディングに不愉快さを持ち込むこう言った思考には昔から賛同しかねます。
そもそもこの書き方嫌いですしね。
条件を削りたい、足したい、順序を入れ替えたい、となった時に鬱陶しいんですわ、この書き方。
先頭と2番目をひっくり返したい時とか「ギィイィィーーーー!」となって結局改行を入れて入れ替えて、それから改行を潰す羽目になります。
ただ単に行を潰したい割にはデメリット多すぎですって、この書き方は。
Nim の場合はもちろん書き方に変な縛りはありませんし、随分と綺麗に書けます。
if i == 1:
  echo "HOGE"
elif i == 2:
  echo "FUGA"
クリーンでしょ。書きやすいわ見やすいわでノンストレスです。
if i == 1: echo "HOGE"
elif i == 2: echo "FUGA"
こんな感じに行を潰すこともできます。パーフェクトです。

immutable 変数がない

何故か無いです。Nim は当然あります。
let n = 10 # immutable
var m = 10 # mutable

演算子オーバーロードがない

あいたたたったたたた。これが無いばかりにクソみたいなコードを書くことになります。
下は構造体を加算するコードです。
package main

import (
  "fmt"
)

type Point struct {
  x, y int;
}

func Add(left Point, right Point) (Point) {
  return Point{ left.x + right.x, left.y + right.y }
}

func main() {
  var p1 = Point{ 10, 20 }
  var p2 = Point{ 30, 40 }
  var p3 = Point{ 50, 60 }
  var p4 = Add(Add(p1, p2), p3) // <- zZZzzZzZzzZzzz
  # X: 90, Y: 120
  fmt.Printf("X: %d, Y: %d\n", p4.x, p4.y)
}
3つ足すだけでこんなダッサい見た目になります。
他の演算も混じってしまうともう Add(Sub(Add(Sub ともうワケワカメですよ。
演算子オーバーロードがあればスッキリ書けます。
type Point = ref object
  x: int
  y: int

proc `+`(left, right: Point): Point =
  Point(x: left.x + right.x, y: left.y + right.y)

var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 30, y: 40)
var p3 = Point(x: 50, y: 60)
var p4 = p1 + p2 + p3 # <- パーフェクト
# X: 90, Y: 120
echo "X: ", p4.x, ", Y: ", p4.y
演算子オーバーロードの有る無しでコードの可読性が飛躍的に上がることがわかってもらえるかと思います。
Python の生産性を」とか言っている割になんでこんな大事なものを欠落させてくるのかさっぱり意味がわからないです。
Nim の方は Python よりもわかりやすい Ruby に近い直感的な見た目で定義出来るようになっていて素敵な仕上がりを見せているのに。
golang の FAQ によると
演算子オーバーロードに関しては、絶対に必要というより、あれば便利という程度であり、採用しないことでシンプルさを保ちました。
らしいですが、言語なんて「あれば便利」の塊であってこの言い訳をするならもう C だけやってりゃ良いって話になりません?
根底の考え方がおかしいんですわ。
そのシンプルさって、書き手のシンプルさではなくて、コンパイラを作る側として面倒な処理を避けてシンプルにしたいって話でしょうね。

イテレーターがない

ため息しか出ません。痛々しすぎます。いつの言語なんすかマジで。
Nim には当たり前のようにあります。
iterator ite(init: int): int =
  var res = init
  while res <= 30:
    yield res
    res += 10

# 10 20 30
for i in ite(10): echo i

ジェネリクスがない

はい、致命傷です。ご臨終です。同じようなコードを型毎にガンガン書けますよ。良かったですね。
package main

import "fmt"

func Add(a, b interface{}) interface{} {
  switch a.(type) {
    case int:
      return a.(int) + b.(int)
    case float64:
      return a.(float64) + b.(float64)
    default:
      return nil
  }
}

func main() {
  fmt.Println(Add(1, 2))              // 3
  fmt.Printf("%f\n", Add(1.0, 2.0))   // 3.000000
}
Nim には当然あるので同じようなコードはガンガン書けません。悲しいですね。
proc add[T](v1, v2: T): T =
  return v1 + v2

echo add(1, 2)      # 3
echo add(1.0, 2.0)  # 3.0

C の関数を使おうとすると死ねる

さぁ C の関数を持ってくるぞ!となったらこれがまぁー酷いです。
package main

/*
#include <stdio.h>
#include <stdlib.h>
*/
import "C"
import "unsafe"

func main() {
  str := C.CString("Blah")
  C.puts(str)
  C.free(unsafe.Pointer(str))
}
何やらガチャガチャ書かされます。
コメントの部分も必須要素ですからね。include をコメントで書いてやらないといけないんです。コメントに大事なものを書くとか無いでしょ。checkconfig だの insserv 用のスクリプトだの shebang だのはスクリプトを外側から読むからしょうがないにしろ、言語の実装としてこうなったらいかんでしょ。どうにかならんかったんかと。
値も C 用に変換させられます。
そして、悪夢の1行もありますね。
C.free(unsafe.Pointer(str))
はい来た。フリー。golang 開発者たちの責任範囲から無事フリーされ、全責任が書いた本人に降り注いできます。
ウザさ爆発です。
Nim ならこうです。
proc puts*(s: cstring) {.header: "<stdio.h>", importc: "puts".}
let str = "Blah"
puts str
free も変換もいらないです。
比べ物になんないですね。
Rust 版を見てもらった人は何故 puts? と思ったかもしれません。あっちは printf でしたからね。
可変引数の渡し方がさっぱりわからないのと調べるのが途中でバカバカしくなったので puts にしてます。

継承がない?

golang には継承がない!とか書いていたりしますが、あります。
気持ち悪い書き方が出来てとっちらかっててシンプルから程遠いところにいるとびっきりカオスなやつが。
interface がどこで使われているのか把握するのも一苦労で、一体何がどうなっているのやらわからなく出来る素敵なやつです。
おとなしく extends、impliments と書かせろ、と思う事請け合いの悲壮感漂う代物です。
なんか、こっち系のシンプル屋が大っ嫌いであろう多重継承も不思議と出来ます。
詳しくやるとやたら長くなるので別の記事で書いています。

実行速度は大して早くない、メモリ使用量・ファイルサイズはデカい

いまいちパッとしません。
コンパイル後のファイルサイズは話にならないレベルで比べ物になりませんし。
項目NimGo
実行速度0.33s0.59s
ファイルサイズ82K2.2M
メモリ356K728K
こんな感じです。

結局どうなの golang

頭が固くて古臭い、今の言語をまるで知らない人が作ったような言語だと思います。
そこは世界中の golang を批判している人々が抱いている嫌悪感と全く同じものを感じます。
数々発展してきた言語の考え方を丸ごと打ち捨て「シンプル」という旗印を掲げているものの、そのシンプルさは「C言語ってシンプルだろぉ!?」と押し付けてくるような強引さに非常によく似ていると思います。
言語機能が欠損している = シンプル
この考え方は非常によろしくないです。
結局は、これまたよく言われるように Google が推進しているから有名になっているだけ、です。
同じ Google が最近推している言語に Kotlin がありますが、断然こっちの方が良いです。比較になりません。

Kotlin に全てを奪われると予想

今は Android の開発言語としての位置づけである Kotlin ですがそのうちサーバサイドでも盛んに使われるようになっていきます。
既に Ktor などの Webフレームワークがあったりしますし。
Android のエンジニア達は Java で書くよりも楽に楽しく書ける Kotlin に必ず移行していきます。ぬるぽもなくなりますからますますです。
そうなってくると、アプリ側とサーバ側の言語を統一する事が開発現場や採用の観点から重要視されていきます。
Go Mobile なんて中途半端でよくわからないものは一切流行らないので、この流れを止めることは出来ません。
golang があるにも関わらず Android の開発言語として他社の Kotlin を正式採用するというのは Google 内部に golang は微妙だと思っている人たちが少なからずいると言う証拠でしょう。
言語として
golang <<<<<< java << kotlin
こんな感じですからね。
え?Nim はどうなのですって?
そりゃーこうですよ。
kotlin <<<<<(膜の向こうの別の宇宙)<<<<< Nim
ですが、一切流行っていませんし流行る気配も・・・。別の宇宙に行っちゃってるもんだから多くの人類に見つけてもらえないんでしょう。
まぁ、そこが良いんですけどね。
そもそも全く流行っておらず、全くの無名なので今より下に行くことがなく、上に行くしか無いですから(馬鹿みたいにポジティブ)。
モノは良いんですから。先行投資だと思って習得しておくのは決して間違いではありません。
そして、言語センスとして良いエッセンスを吸収するという意味でも触っておく価値があると思います。
「怠け者とはこうあるべきだ!」と言うとても大事な事を Nim は我々に教えてくれますよ。

神々よ Plan 9 に真剣に取り組んでくれ

言語としての考え方が改めてガッツリ変わらない限り golang は居場所が良くわからない妙ちきりんな存在としてフェードアウトして行くと思います。
微妙な感じでフェードアウトしている Plan 9 の開発者ロブ・パイクも設計に関わっていますし。
個人的に Plan 9 は期待していたんですよ。ケン共々、こんなくっだらない言語なんて作らずに Plan 9 に注力してくれ、と本気で思っています。彼らは言語を作るセンスは無いけれど、OSを作るセンスはズバ抜けているんですから。C 言語は言語的に最低最悪ですが、UNIX は神がかっています。Plan 9 もコンセプトは神がかっています。golang は貧乏神臭いです。C 言語ではメモリリークバッファオーバーフローにより人類の膨大な時間を不毛に失いました。それほどではないにしろ不毛に時間を奪う代物だと思います。
C の時代は他に逃げ道がありませんでしたが、今は golang じゃなくても代替の代物がたくさんあります。Nim だってその1つです。しかし、Plan 9 の代替はありません。ごちゃごちゃした OpenStack 等で誤魔化しきれません。Google の旗印の元で Plan 9 を盛り上げていくべきだと思います。神様、お願いしますよ、ほんと・・・。言語なんて下々のものたちでいくらでも開発しますんで。

0 コメント:

コメントを投稿