演算子とステートメント
(この記事は Qiita に投稿した「Go 言語の ++
や --
は演算子ではない - Qiita」を大幅に修正して再構成したものです)
あるプログラミング言語を習得する際に最も早道なのは「たくさんの(他人の)コードを読むこと」であり「たくさんのコードを(コピペではなく自分で)書く」ことである。 これは間違いない。 しかし,その言語の仕様をきちんと把握してないとコードを読んでも間違って理解するかもしれないし,何より実際に自分でコードを書く際に躓く原因になる。
というわけで,少なくとも学ぶ言語の言語仕様を一度は眺めておくことをお勧めする。 Go 言語の場合は以下のページで言語仕様を見ることができる(“A Tour of Go” の後で読むと頭に入りやすいかもしれない)。
- The Go Programming Language Specification - The Go Programming Language
- Goプログラミング言語仕様 - golang.jp : 日本語だが内容が古いので注意
今回は「つまみ食い」的に演算子(operator)とステートメント(statement)1 について軽く紹介してみる。
ステートメント
Go 言語においては「ステートメント」は以下のように定義されている。
まぁ名前で何か大体わかると思う。
ここでは SimpleStmt
(simple statement) に絞って紹介しよう。
Empty Statements
文字通り空のステートメント。
Expression Statements
式(expression)を表すステートメント。 関数呼び出しや受信操作のコンテキスト内に記述できる。
さらに式は以下のように定義される。
(PrimaryExpr
(primary expression) については割愛する。詳細は「言語仕様」で確かめてみてください。ここでは Expression
を構成する要素にはステートメントが含まれないことに注目)
binary_op
, rel_op
, add_op
, mul_op
, unary_op
は演算子である。
演算子については後述する。
Send Statements
channel 送信のステートメント。
IncDec Statements
インクリメント(increment)およびデクリメント(decrement)のステートメント。
C/C++ のように ++x
みたいな記述はできないので注意。
ちなみに IncDecStmt
は次の代入ステートメントの以下の記述と同じである。
IncDec statement | Assignment |
---|---|
x++ |
x += 1 |
x-- |
x -= 1 |
Assignments
代入。
add_op
, mul_op
は先ほど出た Expression
の演算子を指す。
定義だと assign_op
は演算子っぽく見える。
そもそも代入を “assignment operation” と表記しているのだ。
どうなんだろう。
まぁ,いずれにしろ代入自体は間違いなくステートメントであり式の中には含められない。
ちなみに ExpressionList
は Expression
を列挙したものである。
これにより代入の左辺・右辺を組(tuple)で記述できる。 たとえば2つの変数の値を入れ替える場合は以下のように記述する。
x, y = y, x
Short Variable Declarations
変数宣言の短縮表現。
var
キーワードを使った以下の表現と同じ。
IdentifierList
は identifier
を列挙したもので
これにより identifier
で記述される複数の変数をまとめて宣言・初期化できる。
identifier
の定義は以下の通り
ちなみに変数名となる identifier
は全ての Unicode 文字を許容する。
なので日本語交じりでこんな書き方もできる。
package main
import "fmt"
func main() {
わーい := "わーい! たのしー!"
fmt.Println(わーい)
}
演算子
さて,式と演算子の定義を再び掲げる。
Go 言語で式に使える演算子はここに挙げられているものが全てである。
このうち二項演算子(binary_op
)には優先順位が付けられている。
Precedence | Operator |
---|---|
5 | * / % << >> & &^ |
4 | + - | ^ |
3 | == != < <= > >= |
2 | && |
1 | || |
なお単項演算子(unary_op
)は二項演算子よりも高い優先順位で機能する。
したがって全体としてはこんな感じだろうか。
Precedence | Operator |
---|---|
6 | unary_op |
5 | mul_op |
4 | add_op |
3 | rel_op |
2 | && |
1 | || |
インクリメント/デクリメントは演算子ではない
たとえば C 言語の演算子と比較すると Go 言語ではインクリメント(++
)/デクリメント(--
)が演算子として扱われていないことに気付く2。
Go 言語ではインクリメント/デクリメント(および代入)はステートメントである。
これはどういうことかというと,たとえば C 言語のコードに似せて
package main
import "fmt"
func main() {
i := 1
fmt.Println(i++)
}
と書いてコンパイルしようとしても
syntax error: unexpected ++, expecting comma or )
とコンパイルエラーになるということである(式を構成する要素にステートメントは含まれないことを思い出してほしい)。
これはコードを,以下のように,代入に置き換えたほうが直感的で分かりやすいかもしれない。
(この場合も「syntax error: unexpected +=, expecting comma or )
」でコンパイルエラーになる)
package main
import "fmt"
func main() {
i := 1
fmt.Println(i+=1)
}
私見で申し訳ないが,私は「式中の演算子は変数の状態を変えるべきではない」と考えている。
たとえば C/C++ では ++
, --
演算子を前置にすべきか後置にすべきかというのでよく議論になる3。
しかし,これはそもそも ++
, --
演算子が式の中で対象の変数の状態を変えてしまうことに問題があるのだ。
Go 言語ではインクリメントやデクリメント(あるいは代入)といった変数の状態を変える操作をステートメントとし,式の中に埋め込むことを禁止することでこの問題を回避しているように見える。 式の中で変数の状態が変わらないのであれば副作用を気にすることなく安全にコードを書くことができる。
ただし例外がある。
channel 操作
channel 操作では,送信はステートメントだが受信は <-
単項演算子を使う。
したがって,こんな記述もできる(意味があるかどうかはともかく)。
ch2 <- <-ch1
channel 受信を含んだ式では channel 変数の状態が変わる副作用(特に deadlock 関連)に注意を払う必要がある。
とまぁ,こんな感じで
手を動かしながら「言語仕様」を眺めていくと,いろいろ発見があって楽しいと思う。
ではまた。
ブックマーク
- web制作者にもわかる、Swift 3が++と–を削除した理由 - Qiita : Swift 3 では
++
,--
演算子を仕様から削除したらしい - else ifにも代入文が書ける #golang - Qiita : if ステートメントに関する話
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。