タイトル通りのシンプルな質問なのですが、調べてみたものの、
しっくりくる回答がなかったため、質問いたします。
C/C++やJava, C#など静的型付け言語のうち比較的レガシーなものは、
変数定義時の型は前置が主流だと思います。
cpp
1int x = 3; 2int add(int x, int y) { 3 return x + y; 4}
しかしながら、TypeScript, Rust, Goなどのモダンな言語では後置を採用していることが多いです。
これだけ揃っていると偶然とは思い難く、何かしらのメリットがあるのだろうと想像します。
一体どんなメリットがあるのか、ご存知でしたらご教示頂けたら幸いです。
TypeScript
1function add(left: number, right: number): number { 2 return left + right; 3}
Go
1type Circle struct { 2 radius float64 3}
Rust
1fn factorial(i: u64) -> u64 { 2 (1..=i).product() 3}
よろしくお願い致します。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答10件
0
ベストアンサー
StackOverflowでも同じような質問をされている方がいます。
回答を大雑把にまとめると、
(1) 型推論のある言語では、型宣言をしなくても問題ない場合がある
TypeScript
1var myNumber = 1 2// 機械はmyNumberをnumberとして評価できる
(2) 必ずしも必要でない記述を右側にもっていくことで、ソースコードを解釈するときの曖昧性を軽減し、パフォーマンスを向上できる
TypeScript
1number var myNumber = 1
上の記述はイメージですが、機械の立場で言うと、numberの後のvarまで到達しないと、変数宣言かどうかわかりません。変数宣言に必ず必要な「var」と「変数名」が左にくれば、パースのパフォーマンスがあがります。また、(回答者によれば、議論のよちはありますが)人間にとっても可読性があがります。
C++などではオプショナルなものも左に書きますが、int* a, b;
ではa
はポインターですが、b
はそうではありません。現在では、C++にもauto x = int{ 4 };
のような後置の型の構文があるようで、いくつかの利点があるようです。
投稿2019/05/11 17:24
総合スコア1260
0
これらの言語では型が省略可能だからではないでしょうか。
私はTypeScriptやGoはよく知らないので間違っているかもしれませんが、TypeScriptでは関数の引数の型注釈を省略して以下のように書けたと思います。
TypeSciprt
1function add(left, right) { .. }
Rustは関数の引数と戻り値については型を省略できない仕様になっていますが、クロージャなら省略可能です。
rust
1// クロージャで型を省略した場合 2let factorial = |i| (1..=i).product(); 3 4// クロージャで型を明示した場合 5let factorial = |i: u64| -> u64 { (1..=i).product() };
また変数を導入するlet
文でも上のfactorial
変数のように通常は型注釈は省略されます。しかし明示もできます。
rust
1// factorial変数の型を明示 2let factorial: fn(u64) -> u64 = |i| (1..=i).product();
型注釈が省略できる言語では、後置にすれば、省略してもしなくても引数名や変数名の位置は同じになります。文法に統一感が出るので人間にとって読みやすくなりますし、言語処理系にとっても構文解析がしやすくなるというメリットがあると思います。
投稿2019/05/11 17:25
編集2019/05/11 17:30総合スコア2046
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
まず前提から整理すると、C/C++で変数の型を記述する方法は前置ではありません。
c
1#include <stdio.h> 2 3int add(int x, int y) { return x + y; } 4int mul(int x, int y) { return x * y; } 5 6int main(int argc, char **argv) { 7 int (*functions[2])(int, int) = {add ,mul}; 8 9 printf("functions[0](2, 3) = %d\n", functions[0](2, 3)); 10 printf("functions[1](2, 3) = %d\n", functions[1](2, 3)); 11 return 0; 12}
上記のコードで functions
は「int型を2つ取ってint型を返す関数へのポインタの配列」です。
このように、元のCの変数宣言の仕様は複雑な型の場合には破綻します。
(これが訓練せずとも一般に読み書きしやすいとは言えないでしょう)
JavaやC#などでは前置を採用し、更にこのような複雑な型をそもそも書かせないようになっていました。
型が複雑になる前にインターフェースにしてしまうことで回避しますが、言語組み込みではないので、どのようにインターフェース化するかを統一しないと汎用性が下がってしまいます。
(近年では公式に java.util.function.Function
などができて統一されました)
きちんと関数の配列のようなものを言語仕様レベルでサポートしたい場合、完全に前置にするか後置にすることでこの問題を回避できます。
go
1package main 2 3import "fmt" 4 5func add(x, y int) int { return x + y } 6func mul(x, y int) int { return x * y } 7 8func main() { 9 var functions [2]func(int, int) int 10 functions[0] = add 11 functions[1] = mul 12 13 fmt.Printf("functions[0](2, 3) = %d\n", functions[0](2, 3)) 14 fmt.Printf("functions[1](2, 3) = %d\n", functions[1](2, 3)) 15}
完全に前置にする場合、長い型が出てくると変数名がかなり後ろになってしまいますし、パース時も型がどの部分なのか判定しづらいので、後置にするという選択がされたのだと考えています。
投稿2019/05/13 00:16
編集2019/05/14 07:27総合スコア148
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/05/14 06:10
2019/05/14 08:20
2019/05/19 07:10
0
(以下は全部根拠のない私見です)
Cコンパイラは、今では珍しい事に1パスのコンパイラであり
前から後ろに読み取っていき、基本的に戻りません。
多分、初期のコンピュータの記憶デバイスがテープだった事やメモリが貧弱だった事と関係があったと思っています。
そのため、(配列と関数ポインタを除き)型を前置することが自然でした。
言ってしまえば前置だったのはコンピュータ側の都合です。
型推論を行うようになった理由を考えると
C++においては
c++
1Hoge* f = new Hoge(); 2std::unorderd_map<std::string, std::vector<std::vector<unsigned long long>>> vec = f->list;
のように
- 1つの変数に対し同じ型名を2度書く場面がたまにあること
- とてもとても長い型名がSTLを使うと日常的になったこと
が理由だったと推測します。
そして、型推論を得た結果
c++
1auto f = new Hoge(); 2auto vec = f->list;
今度は人間が型が分からなくなる事態が発生しました。
それはそれで困ります。
しかし、長い型を読むのは辛いですしどこが変数名なのかが分からなくては困ります。
そういった事を考えた結果、最近の言語では後置にすることで変数名をわかりやすくしているのかと思います。
投稿2019/05/13 01:18
編集2019/05/14 00:34総合スコア15149
0
前置は型宣言
後置は型注釈で概念が異なります
質問者様が挙げられている関数のシグネチャの例では前置だろうが後置だろうがどっちでもよくで
文法の違いに過ぎないと思ってしまいますが
型注釈の場合は、(変数宣言だけではなく)任意の式に対して注釈をつけられます
Scala
11: Long 25 * 6: Long
型注釈というのは型推論が前提になっていて
型宣言がなくてもコードを書ける、だが推論に必要な付加情報を任意の場所に埋め込めるようにしよう
という設計思想だと思います
その場合後置のほうが文法的に都合がよかったのではないでしょうか
変数に対する注釈だろうがリテラルに対する注釈だろうが書き方が一緒で一貫性があるのもいいですね
型宣言型の言語だと一応キャストの文法があるけど括弧の数が増えちゃうから
型推論型の言語では採用されなかったのではないでしょうか
投稿2019/05/16 15:05
総合スコア16
0
関数でも変数でも、
評価後の結果としてどのような値が取れるか?という事を考えてみると直感的に後置なると思いました。
関数型言語での表記(ramdajsのドキュメントにも見られる)とも一致しています。
https://ramdajs.com/docs/#append
a → [a] → [a]
投稿2019/05/14 13:59
総合スコア47
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
理由は統一のためです。
「前置」だという言語でも、関数引数のカッコ(=関数型であることを意味する修飾)は、関数名の識別子に対して後置にするものが多いですが、前置と後置がごっちゃになっています。
変数の型修飾も、関数に対するものと同じ後置にすることで初めて統一がとれ、統一性を重視する言語設計者を満足させます。
もちろん、関数としての型修飾も含めて、すべて前置に統一することも可能ではあるでしょうし、実際にそんな言語も存在するのかもしれません。
こんな感じでしょうか。
int (int arg)func {...
}
でも、恐らく人気はでないのではないでしょうか。
なお、後置の型修飾は関数以外に配列の[]などでも同じ議論ができます。Cや初期のJavaでは前置後置混在になりました。Javaではある時点で前置になりましたが、違和を感じるひともいたのではないでしょうか。
投稿2019/05/15 13:00
編集2019/05/15 20:52総合スコア16
0
重複送信したので削除
投稿2019/05/15 12:56
編集2019/05/15 13:10総合スコア16
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/05/12 01:19
2019/05/14 06:24