この記事は朝活 Advent Calendar 2021の11日目の記事です。
僕なりにこれまで触ってきた言語に対して感じていることなど書こうと思った。
- 解像度低めなので、読んでも面白くないと思う。
- コード例みたいなのが出てくるけど、試してはないからもしかしたら間違っている。
- マサカリ自体はどんとこい
- 中学生 ... VBか何かを触った覚えはあるけど、Hello Worldレベルでやめた
- 高校生 ... PHPでスクリプト書いて遊んでた
- 専門学校 ... ゲーム開発にプログラマとして携わりたくて入った
- 今 ... ゲーム開発には携わっていない(社会人2年目)
専門学校に入った際に学習しだした。なんだかんだずっと書いているので、今年で6年目になる。 僕の書ける言語の中では一番理解しているとは思うけど、言語に対する率で言えば1割すら怪しいかも知れない。 今でも勘違いだったんかあれは、みたいな気づきがあるし、新たに知ることも多い。
C++のここが好き。みたいなことを書ければ良かったんだけど、後発のRustが盛り上がっていることも大きくてあまり好きな点が出せない。 テンプレートが好きというかとても面白い機能だとは思っている。あれのおかげで当然コード量は減らせるし、不正なコードをコンパイル時に気付けたりできる(テンプレートだけじゃないけど)。
try {
foo(); // どんな例外を出すのかドキュメント見ないとわからない
bar(); // どんな例外を出すのかドキュメント見ないとわからない
baz(); // どんな例外を出すのかドキュメント見ないとわからない
throw std::exception {};
} catch (std::exception & e) {
puts(e.what());
} catch (...) { // 本当に何が出るんだよ
puts("なんかわからんけどエラー出たらしい");
}
C++にはCと違って例外機構が存在するけど、キャッチするべき例外の型が何かわからないし、キャッチしなくてもコンパイルは通ってしまうのが嫌い。あと、呼び出したスコープと例外のキャッチを行うスコープはもう別になっちゃっているのも嫌かも。
std::optional<int> a {};
if (a) { // aが有効な値を持っているか
const auto a = a.value(); // だるくないか?
}
const auto a_ = a.value(); // これも別にコンパイルは通る
C++でも、テンプレート使ってEitherみたいなのを作れなくはないけど、結局言語機能がそういうのと相性悪くて満足のいく形にはならない。
std::optional
を例に出すと、Eitherを作ってもどのみち上みたいなコードが発生して、ウニャってしまう。
auto func = [](int input, int & result) -> int {
if (input < 0) {
return -1;
}
return input;
};
int a {};
const auto r = func(-1, a);
if (r < 0) {
puts("error");
return;
}
printf("value: %d¥n", a);
結局これした方が、単純で良くない?(これはこれでもちろん好きではない)
どうしてか、最近はC++11縛りをやるハメになっており、余計に苦しい。
C++20のことは僕自身追えていないので、わからないが、僕の欲しいものは大体C++14,C++17で標準化されたものであって、C++11なんてものは最低の最低限しかない。emplace_back
があるくらいでは喜んでられない。構造化束縛を使わせろ。
多分C++の次くらいに書いている言語。僕がNode.jsかブラウザでなんかやろうと思った段階では既に流行っており、JavaScriptをほぼ触らずにTypeScriptからスタートした。
この言語自体は別に好きというわけではなく、以前はJavaScriptで開発していたようなことを静的な型のある世界でコードが書けるという点がデカ過ぎるんだと思っている。
よくJavaScriptで表現されていたものを型に落とし込むためにリテラル型とか共用体があるみたいな話が流れてくるけど、割と他の言語でも同じようなことがしたくなることがあり、僕はこの機能が結構と好きなんだと思う。まあ、Rustのタグ付き共用体みたいなのがあれば、それの方がいいけど。
この言語もエラー表現は例外か、コールバックを呼び出す際に結果とエラーを渡すみたいなのが多い印象がある(これはTypeScriptではなくJavaScriptの話だろうけど)。こういうのが嫌で一時fp-tsという関数型プログラミングをTypeScriptに拡張するライブラリを導入してEitherなどで表現するようにしていた。
declare const result: Either<T, E>;
if (isLeft(result)) {
console.error(result.left);
return;
}
console.log(result.right);
大体こんな感じのコードになってたと思う。Rustのパターンマッチのように値を取り出すことができれば良いのに、とよく思う。ただ、そういうのは結局言語機能に備わっていないとどうにもならなくて、素直に例外使った方がいいなと最近は思う。
仕事で書くようになった言語。それまでは、動的型付けだとかPython2,3系でややこしかったり、インデントベースの構文がキモくて避けてきた。 しばらく書いていると案外そうでもない。幸いなことにPython2系は本当に死んだし、型ヒントは導入されたし、インデントベースもやっているうちに慣れてきた。
型ヒントもヒントでしかないし、ガッチリ作り込みたいようなものをPythonで書きたいとは思わないが、シバンに/usr/bin/python3
を指定して適当なスクリプト書いておくと結構便利だし、勝手がいい言語だと思う。なんだかんだどの環境にも入っているし(僕の観測している環境がそうなだけかも)、外部モジュールに依存していなければ大体どこでも動くし、案外やるねといった感想。
# a.py
import ctypes
class Foo(ctypes.Struct):
pass
# b.py
import a
foo = a.Foo() # Fooは使いたい
ctypes = a.ctypes # ctypesにアクセスしたいわけではない
適当に不満点を出すとするなら、こういうのでムーっとなることがたまにある。ctypes
が欲しいわけじゃないんだよなあ。from Foo import a
しろというのは、そらそうなのではあるけど。まあ、それももしかしたらPythonらしさなのかもと諦めているところはある。
最近の発言、これなのだけど、こういうのは多分Pythonに影響されて湧いてきているんだと思う。僕のPythonの解釈が正しいのかはわからないが...。
アクセス修飾子ってメタで、アプリケーションを書く上ではprivateにしたいかどうかってノイズでしかないのではと思うことがある
— remokusu (@remokusu) December 8, 2021
専門学校の在学中に触りだした記憶がある。 僕のレベルとしてはなんとなく書ける程度なので、Arcがどうのとかは全くわからない。ライブラリが使えというので使っているだけなんですゥ...という状況。
式試行、パターンマッチング、タグ付き共用体、好きなものがいっぱいあるというか、この言語がそれを教えてくれたという気がする。 あと、cargoが好き。これがあるお陰で外部のパッケージを簡単に導入できるし、自身のコードもモジュールが分割しやすくて助かる。
個人的にはシャドウイングが同じスコープでできるのが結構良いと思っている。
let mut buf_reader = BufReader::new(File::open("foo.txt")?);
let mut contents = String::new();
buf_reader.read_to_string(&mut contents)?;
let contents = contents;
// ここからはcontentsという同じ名前でimmutableとして扱えるの良くないか?
C++だと別の関数にするとか、contents_
みたいな名前をつけるとかしようとするんだけど前者は場合によっては無駄だし、後者はムーブしてようが元のミュータブルな変数名にアクセスするコードはかけてしまうしであまり気持ちは良くない(わざわざ_
つけるような命名もキモい)。なので、大変気に入っている。
他に地味に好きなところを言うと、
void func(const int & value);
int main() {
const int a { 123 };
func(value);
// ...
}
C++だとこう書くコードが、Rustだと
fn func(value: &i32) { /** なんかする **/ }
fn main() -> i32 {
let a: i32 = 123;
func(&a);
// ...
}
のように書くと思う。どちらも参照を取る関数を呼び出しているコードになるけど、C++であれば参照を渡しているのか、コピーを渡しているのかわかりにくくて、その点Rustだとfunc(&a)
と書いて参照を渡しているのがわかりやすくていいなと思っている。だからと言って、C++でfunc(&a)
して、仮引数の型をポインタにしようとは思えないが...。
不満点を強いていうと、Result型に包んでエラー情報を呼び出し元へ呼び出し元へ回されると、大元のエラーはどこが原因なのか、呼び出し元からわかりにくいなと思う。これも僕が使いこなせていないだけなのだろうけど、C++の例外はスローされた地点がどこか比較的わかりやすいと思っている。なんというか、例外の再送出をやられまくっている感じ。誰か正しいやり方を教えてくれ〜。
あとは、C++だとかだと関数のオーバーロードができるけど、Rustだとジェネリクスを使う必要があるし、同じような感覚ではできないのがまだ慣れない。
もっぱらSpigotのプラグインを開発するのにしか書いたことがない。これはScalaではなくJVM自体の良さをあまり感じれていないからからだと思う。LLVMあればどこ向きにでもコンパイルできるんだろ?と思ってしまっている。
大前提として、Scala3系のことは何も追ってないのでわからない。ここでいうScalaは2のことを指す。
implict val
とか僕がこれまでに触ってきた言語にはないものだったので、面白かった記憶がある。
コンストラクタの書き方が特定のルールに沿ったメソッドを定義するわけではなく、クラスのボディー(?)に直接書いていくのがわかりやすくてむしろ単純で良いと思っている。
あとは、完全には乗りこなせていないけど、for式はとても良い。flatMap
をチェーンさせているとコードめちゃくちゃ読みにくくなっちゃうしで、簡潔にかけて良すぎるね。
シェルスクリプトを書くとなったら、大体Bash前提で書いている。
これもScala同様、Spigotのプラグインを開発するのにしか書いたことがない。
特に覚えていることがない。何か思い出したら追記する(しない)
直近だと、アルバイトでちょっとだけ書いていたけど、それも3年ほど前だしでわからん。
インターンでちょろっと書いただけなので、わからん 戻り値の代入先に型を指定しておけば、オーバーロードの解決とかをやってくれてそれが面白かった記憶がある。
guard let
とか、protocol、computed propertyが良かった記憶もある。細かいことはわからん。