オフィスアワーがそろそろ始まるよ!()

演算子

演算子は、算術演算や論理演算を行うためのものです。

Zenは単項演算子と二項演算子を持ちます。Zenの演算子の多くは、2つのオペランドを持つ二項演算子です。a / b の例を見てみると、 / は、 ab の2つのオペランドを持つ二項演算子です。aを左辺オペランド、bを右辺オペランドと本書では呼びます。

Zenの単項演算子は、オペランドの前に置くものと、オペランドの後ろに置くものがあります。!a!はオペランドの前に置く単項演算子です。一方、a.*.*はオペランドの後ろに置く単項演算子です。

ノート: Zen言語では演算子のオーバーロードはありません。

次の分類に従って、説明します。

比較演算子

比較演算子は2つのオペランド間で特定の関連性が成立するかどうかを確認し、その結果を論理値 (true / false) で返します。この結果は主に(ifwhileforといった)制御フローを決定するために使用されます。それ以外にも、変数に保存される場合があります。

構文 対象の型
a == b - 整数型
- 浮動小数点型
- ブール型
- 型 (type)
a != b - 整数型
- 浮動小数点型
- ブール型
- 型 (type)
a > b - 整数型
- 浮動小数点型
a < b - 整数型
- 浮動小数点型
a >= b - 整数型
- 浮動小数点型
a <= b - 整数型
- 浮動小数点型
a == null - オプション型

等価演算子 (==)

左辺オペランドと右辺オペランドが等しいかどうかを確認します。左辺オペランドと右辺オペランドが等しければブール型のtrueを返します。それ以外はfalseを返します。

1 == 1  // true
1 == 2  // false

注意: 等価演算子は代入演算子(=)とは異なるものです!

非等価演算子 (!=)

左辺オペランドと右辺オペランドが等しくないかどうかを確認します。

1 != 2  // true
1 != 1  // false

この演算子は、等価演算子(==)とは逆の結果を返します。

大なり (>)

左辺オペランドが右辺オペランドよりも大きな値を持つかどうかを確認します。

1 > 0  // true
1 > 2  // false

小なり (<)

左辺オペランドが右辺オペランドよりも小さな値を持つかどうかを確認します。

1 < 2  // true
1 < 0  // false

大なりイコール (>=)

左辺オペランドが右辺オペランド以上の値を持つかどうかを確認します。

1 >= 0  // true
1 >= 1  // true
1 >= 2  // false

小なりイコール (<=)

左辺オペランドが右辺オペランド以下の値を持つかどうかを確認します。

1 <= 2  // true
1 <= 1  // true
1 <= 0  // false

null等価演算 (== null)

左辺オペランドがnullかどうかを確認します。

examples/ch04-basic-syntax/src/operators_comparison.zen:72:75

test "==null" {
    const value: ?u32 = null;
    ok(value == null);
}

ビット演算子

ビット演算子は整数値に対して、ビット単位の演算を行います。

シンボル 演算子
& ビット単位論理積 (AND)
| ビット単位論理和 (OR)
^ ビット単位排他的論理和 (XOR)
~ ビット単位否定 (1の補数)
<< 論理左シフト
>> 論理右シフト

以下のプログラムは全てのビット演算子の使用方法を示しています。

ノート: 0bから始まる数値は、2進数表記です。

ビット単位論理積 (&)

左辺オペランドと右辺オペランドのビット単位の論理積を行います。

0b1010 & 0b1100 == 0b1000

ビット単位論理和 (|)

左辺オペランドと右辺オペランドのビット単位の論理和を行います。

0b1010 | 0b1100 == 0b1110

ビット単位排他的論理和 (^)

左辺オペランドと右辺オペランドのビット単位の排他的論理和を行います。

0b1010 ^ 0b1100 == 0b0110

ビット単位否定 (~)

右辺オペランドの論理否定を行います。

~@to(u4, 0b1010) == @to(u4, 0b0101)

論理左シフト (<<)

左辺オペランドを右辺オペランドで指定したビット数だけ左シフトします。右辺オペランドは、左辺オペランドのビット幅のlog2でなければなりません。例えば、左辺オペランドがu32であれば、右辺オペランドはu5です。

@to(u32, 0b0001) << @to(u5, 3) == @to(u32, 0b1000)

論理右シフト (>>)

左辺オペランドを右辺オペランドで指定したビット数だけ右シフトします。右辺オペランドは、左辺オペランドのビット幅のlog2でなければなりません。例えば、左辺オペランドがu32であれば、右辺オペランドはu5です。

@to(u32, 0b1000) >> @to(u5, 3) == @to(u32, 0b0001)

符号付き整数へのビット演算は避けるべきです。なぜなら、符号付き整数の符号ビット (最上位ビット) は特別な意味をもつため、符号付き整数に対してビット演算子を適用すると、結果が予期せぬ値になる可能性が高いためです。

特にシフト演算では注意が必要です。

ビットマスク

ビット演算子は主に、マスキングで利用されます。マスキングとは、任意のビットを変数から抽出することです。マスキングに使われるオペランド(定数または変数)は マスク(mask) と呼ばれます。

マスキングを行う方法は多岐にわたります。以下のコードは変数のビットパターンを抽出するためにマスクを使用します。

// マスク`mask`とマスクを適用する対象の整数`x`
const mask: u32 = 0x0000_ffff;
var x:u32 = 0x1234_5678;

新しい変数の残りの部分は0で埋め、与えられたビットパターンの一部を新しい変数にコピーします。ビット単位論理積 (&) を使用します。

// `u32`の上位16ビットは`0`で埋め、下位16ビットをコピー
(x & mask) == 0x0000_5678

新しい変数の残りの部分に1を埋め、与えられたビットパターンの一部を新しい変数にコピーします。ビット単位論理和 (|) を使用します。

// `u32`の上位16ビットをコピー、下位16ビットは`1`で埋める
(x | mask) == 0x1234_ffff

元のビットパターンの残りの部分を新しい変数内で反転させながら、与えられたビットパターンの一部を新しい変数にコピーします。ビット単位排他的論理和 (^) を使用します。

// `u32`の上位16ビットをコピー、下位16ビットは反転させる
(x ^ mask) == 0x1234_a987

算術演算子

算術演算子は、左辺オペランドに右辺オペランドを算術演算した結果を返します。交換法則を満たす演算子もあります。例えば加算や乗算は交換法則を満たしますが、減算や除算は満たしません。

構文 対象の型
a + b - 整数型
- 浮動小数点型
a - b - 整数型
- 浮動小数点型
a * b - 整数型
- 浮動小数点型
a / b - 整数型
- 浮動小数点型
a % b - 整数型
- 浮動小数点型
-a - 整数型
a +% b - 整数型
a -% b - 整数型
a *% b - 整数型
-%a - 整数型
a ++ b - 配列
a ** b - 配列

加算演算子 (+)

左辺オペランドと右辺オペランドを加算します。

2 + 5 == 7  // true
2 + 5 == 8  // false

減算演算子 (-)

左辺オペランドから右辺オペランドを減算します。

5 - 2 == 3  // true
5 - 2 == 4  // false

乗算演算子 (*)

左辺オペランドと右辺オペランドを乗算します。

2 * 5 == 10 // true
2 * 5 == 12 // false

除算演算子 (/)

除算演算子 (/) は左辺オペランドを右辺オペランドで除算します。除算のオペランドが両方とも整数型の場合、整数値を返し、余りは破棄されます。 (剰余演算子を用いれば、余りを取得できます。) オペランドのいずれか一つでも浮動小数点型の場合、結果は小数の近似値になります。

10 / 5 == 2 // true
10 / 5 == 3 // false

剰余演算子 (%)

剰余演算子 (%) は整数型のオペランドにのみ適用できます。これは左辺オペランドを右辺オペランドで除算した時の余りを計算します。

10 % 3 == 1 // true
10 % 3 == 0 // false

単項マイナス演算 (-)

単項マイナス演算 (-a) はaの符号を逆転します。

-1 == 0 - 1

ラップアラウンドする加算演算子 (+%)

左辺オペランドと右辺オペランドを加算します。オーバーフロー発生時、2の補数表現のラップアラウンド動作を保証します。

@to(u32, std.math.maxInt(u32)) +% 1 == 0

ラップアラウンドする減算演算子 (-%)

左辺オペランドから右辺オペランドを減算します。オーバーフロー発生時、2の補数表現のラップアラウンド動作を保証します。

@to(u32, 0) -% 1 == std.math.maxInt(u32)

ラップアラウンドする乗算演算子 (*%)

左辺オペランドから右辺オペランドを乗算します。オーバーフロー発生時、2の補数表現のラップアラウンド動作を保証します。

@to(u8, 200) *% 2 == 144

ラップアラウンドする単項マイナス演算 (-%)

右辺オペランドの符号を逆転します。オーバーフロー発生時、2の補数表現のラップアラウンド動作を保証します。

-%@to(i32, std.math.minInt(i32)) == std.math.minInt(i32)

配列の連結 (++)

配列の連結 (a ++ b) は配列のオペランドにのみ適用できます。これは左辺の配列と右辺の配列を連結します。

examples/ch04-basic-syntax/src/operators_arithmetic.zen:54:61

test "array concatenation" {
    const array1 = [_]u32{ 1, 2 };
    const array2 = [_]u32{ 3, 4 };
    const together = array1 ++ array2;
    
    equalSlices(u32, &together, &[_]u32{ 1, 2, 3, 4 });
}

配列の積 (**)

配列の積 (a ** b) は、左辺の配列のオペランドab回連続する配列を生成します。

examples/ch04-basic-syntax/src/operators_arithmetic.zen:63:66

test "array product" {
    const pattern = "ab" ** 3;
    equalSlices(u8, pattern, "ababab");
}

アクセス演算子

構文 対象の型
.  - 構造体
- 列挙型
- 共用体
- インタフェース
&a - 全ての型
a.* - ポインタ

メンバアクセス演算子 (.)

構造体インスタンス、列挙型インスタンスまたはインターフェースインスタンスのメンバにアクセスします。アクセスされたインスタンスのメンバの値を返します。

examples/ch04-basic-syntax/src/operators_access.zen:4:11

const Int32 = struct {
    value: i32,
};

test "." {
    var x = Int32{ .value = 42 };
    ok(x.value == 42);
}

アドレス演算子 (&)

与えられたのアドレスを返します。ここで、アドレス演算子に与える式は、変数など、アドレスが割り当てられているものでなければなりません。

const x: u32 = 1234;
const ptr = &x;

間接参照演算子 (.*)

ポインタを間接参照します。

ptr.* == 1234

代入演算子 (=)

右辺オペランドの値を、左辺オペランドの変数に代入します。

いくつかの算術演算は複合代入演算子を持ちます。

a += b  // equal to: a = a + b
a +%= b // equal to: a = a +% b
a -= b  // equal to: a = a - b
a -%= b // equal to: a = a -% b
a *= b  // equal to: a = a * b
a *%= b // equal to: a = a *% b
a /= b  // equal to: a = a / b
a %= b  // equal to: a = a % b
a &= b  // equal to: a = a & b
a |= b  // equal to: a = a | b
a ^= b  // equal to: a = a ^ b
a <<= b // equal to: a = a << b
a >>= b // equal to: a = a >> b

複合代入の重要な特徴は、左辺オペランド (a) が1度しか評価されないことです。

次のコードにおいて、ptr.* = ptr.* + 2;ptr.* += 2;とは、少なくともデバッグビルドでは異なるアセンブリを出力します

var x: u64 = 0;
const ptr = &mut x;

ptr.* = ptr.* + 2;
ptr.* += 2;

ptr.* = ptr.* + 2;は、次のようにptr.*が2回評価されています (rdiraxとにptrのアドレスがロードされる) 。

        mov     rdi, qword ptr [rbp - 24]
        mov     rax, qword ptr [rbp - 24]
        mov     rax, qword ptr [rax]
        add     rax, 2
        mov     qword ptr [rdi], rax

対して、ptr.* += 2;は、ptr.*が1回しか評価されません。

        mov     rax, qword ptr [rbp - 24]
        mov     rdi, qword ptr [rax]
        add     rdi, 2
        mov     qword ptr [rax], rdi

論理演算子

構文 対象の型
a and b - ブール型
a or b - ブール型
!a - ブール型

論理積 (and)

両方のオペランドが false でなければ true 返す、論理演算の AND を実行します。

false and false // `false`が返されます.
false and true  // `false`が返されます.
true and false  // `false`が返されます.
true and true   // `true`が返されます.

論理和 (or)

いずれかのオペランドが true であれば true を返す、論理演算の OR を実行します。

false or false // `false`が返されます.
false or true  // `true`が返されます.
true or false  // `true`が返されます.
true or true   // `true`が返されます.

論理否定 (!)

オペランドの論理否定を返します。論理否定演算子はオペランドが true かどうかを確認し、もしそうなら false を返します。それ以外は true を返します:

!true     // `false`が返されます.
!false    // `true`が返されます.
!(1 == 1) // `false`が返されます.

論理演算の短絡評価

可能な時は条件式(if/ while/ ...)の評価を省略します。例えば、a and b を演算する場合、afalseと評価された時点で、式全体がfalseになることが確定します。そのような場合に、bの評価は省略されます。

examples/ch04-basic-syntax/src/shortcircuit_evaluation.zen:2:10

pub fn main() void {
    var a: i8 = 5;
    var b: i8 = -3;
    // ここでは `a == 42` は `false` なので、
    // `b == -3` の式は評価されません。
    if (a == 42 and b == -3) {
        std.debug.warn("ここはプリントされない!\n", .{});
    }
}

andor に共通する重要な特性は以下の通りです。

  • 右辺オペランドが評価される前に、左辺オペランドが評価されることが保証されます。
  • 最も重要なことは、短絡評価されることです。

これは次のことを意味します。

  • 左辺オペランドの値が true となる場合、or の右辺オペランドの値は評価されません。
  • 左辺オペランドの値が false となる場合、and の右辺オペランドは評価されません。

その他の演算子

構文 対象の型
a orelse b - オプション型
a .? - オプション型
a catch b - エラー共用体
a || b - エラー型

orelse

左辺オペランドが null ならば右辺オペランドを返します。null でなければ、左辺オペランドを返します。

examples/ch04-basic-syntax/src/operators_others.zen:4:8

test "orelse" {
    const value: ?u32 = null;
    const unwrapped = value orelse 1234;
    ok(unwrapped == 1234);
}

.?

a.?a orelse unreachable と同じ意味です。

examples/ch04-basic-syntax/src/operators_others.zen:10:13

test ".?" {
    const value: ?u32 = 5678;
    ok(value.? == 5678);
}

catch

左辺オペランドがエラーならば右辺オペランドを返します。

a catch |err| b の時、 err はエラーのことで式 b のスコープ内となります。

examples/ch04-basic-syntax/src/operators_others.zen:15:19

test "catch" {
    const value: anyerror!u32 = error.Broken;
    const unwrapped = value catch 1234;
    ok(unwrapped == 1234);    
}

||

エラーをマージする演算子です。

examples/ch04-basic-syntax/src/operators_others.zen:21:35

const A = error{One};
const B = error{Two};
const C = A || B;

fn handlerMergedError(b: bool) C!void {
    return switch (b) {
        true => error.One,
        false => error.Two,
    };
}

test "||" {
    std.testing.err(C.One, handlerMergedError(true));
    std.testing.err(C.Two, handlerMergedError(false));
}

演算子の優先順位

演算子の優先順位は下表の通りです。上に行くほど優先度が高くなります。

演算子 説明
x() x[] x.y 関数呼び出し、要素アクセス、メンバアクセス
a!b エラー共用体
!x -x -%x ~x &x ?x 前置の単項演算
x{} x.* x.? 初期化、デリファレンス、アンラップ
* / % ** *% || 乗算、除算、エラーの合成
+ - ++ +% -% 加減算
<< >> シフト
& ^ | orelse catch ビット演算、null判定、エラー判定
== != < > <= >= 比較演算
and or 論理演算
= *= /= %= += -= <<= >>= &= ^= |= 代入

Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

Chapter 7

Chapter 8

Chapter 9

Chapter 10

Chapter 11

Chapter 12

Chapter 13

Chapter 14

Chapter 15

Appendix

Error Explanation

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.