ChucK : Language Specification > Operators

ChucK : 言語仕様 > 演算子

version: 1.2.x.x (dracula)

ChucK - [Language Specification : Operators & Operations]

演算子と演算

あるデータに対する演算は演算子を通して行なわれます。この節では、各種のデータ型に対して演算子がどのように振る舞うのか解説します。C や Java 等の他のプログラミング言語における演算子についてご存知ならば、それらのうちのいくつかは ChucK でもそのまま使うことができます。ここでは、まずは ChucK における特別な演算子から解説を始めます。

演算子の使用例についてはサンプルコードもご覧ください。

=> (ChucK 演算子)

ChucK 演算子 (=>) は非常に多くの機能を持った演算子であり、引数に与えられた型に応じた様々な動作を行ないます。この演算子は、常に左から順番に処理が行なわれるよう制約が課されており、複数の演算子を重ねることで連続した処理の流れを表現することもできます。ChucK 演算子は、ある時点までにどの処理までが完了しているのかを表しています。また、ChucK 演算子にはいくつかの類似の演算子が存在します。

=> (基本 ChucK 演算子)

まずは最も基本となる ChucK 演算子 (=>) から始めましょう。この演算子は左結合であり (これは ChucK の全ての演算子に共通です)、データや処理およびモジュール (unit generator や外部の機器) などを処理したり接続したりする順番について指定することができます (左から右の順番に接続されます)。=> という演算子が持つ意味は、それが記述されたコンテキストによって異なります。意味は演算子の左辺に位置する要素 (the chucker) と右辺に属する要素 (the chuckee) によって決定され、また時には、要素の種類 (変数であるかどうかなど) によって決定されることもあります。

いくつかの例:

  // unit generator のパッチ - 見たまんま
  // (この例では、=> は 2 つの unit generators を接続しています)
  SinOsc b => Gain g => BiQuad f => dac;

  // foo に 4 を足して、結果を 'int' 型の変数 'bar' に代入します
  // (この例では、=> は変数 (int) への代入を意味しています)
  4 + foo => int bar;

  // 関数への値の引き渡し == 関数の呼び出し
  // (Math.rand2f( 30, 1000) と同じ意味)
  ( 30, 1000 ) => Math.rand2f;
@=> 明示的な代入のための ChucK 演算子

ChucK には他のプログラミング言語に見られる一般的な代入演算子 (=) は存在しません。変数への代入は ChucK 演算子によって行ないます。先ほどの例でも、=> を代入のために使っていました。

  // foo に 4 を代入
  4 => int foo;
  
  // bar に 1.5 を代入
  1.5 => float bar;
  
  // 100ミリ秒の期間を duh に代入
  100::ms => dur duh;
  
  // "今から 5 秒後" を later に代入
  5::second + now => time later;

@=> という代入演算子は、プリミティブ型 (int, float, dur, time) に対しては通常の ChucK 演算子と同じように働きます。しかしながら、=> がプリミティブ型 (int, float, dur, time) の代入にのみ適用可能であるのに対して、@=> はオブジェクトへの参照の割り当て (objects and classes を参照のこと) にも使用することができます。

  // プリミティブ型に対する @=> は => と同じ意味です
  4 @=> int foo;
  
  // bar に 1.5 を代入
  1.5 @=> float bar;
  
  // (@=> を使わないとオブジェクトへの参照の割り当てはできません)
  
  // moe から larry へオブジェクトの参照を割り当てる
  // (つまり moe と larry は同じオブジェクトを指している)
  Object moe @=> Object @ larry;
  
  // 配列の初期化
  [ 1, 2 ] @=> int ar[];
  
  // new を使ってみる
  new Object @=> moe;

このような方法はとても奇妙に見えるかもしれませんが、代入式 (@=> と =>) と等価式 (==) の混乱をなくすことができるという意味で良いものであると考えています。*1 以下の例は ChucK ではエラーになります。

  // ChucK の文としては不正です!
  int foo = 4;
+=> -=> *=> /=> など (算術的 ChucK 演算子)

これらの演算子は既存の変数 ('int' や 'float') と共に使われ、計算と代入を同時に行ないます。

  // foo に 4 を足して結果を foo に代入
  foo + 4 => foo;
  
  // foo に 4 を足して結果を foo に代入
  4 +=> foo;
  
  // foo から 10 を引いて結果を foo に代入
  // この結果は (foo-10) であり、(10-foo) ではないことを忘れずに
  10 -=> foo;
  
  // foo に 2 を掛けて結果を foo に代入
  2 *=> foo;
  
  // foo を 4 で割って結果を foo に代入
  // この結果は (foo/4) であり、(4/foo) ではないことを忘れずに
  4 /=> foo;

減算/除算を行なう代入演算子 (-=> や /=>) を使う場合、これらの演算は交換可能でないため、値と変数の関係性についてよく理解しておく必要があります。

  // foo を % で割って結果を foo に代入
  T %=> foo;
  
  // 0xff と bar のビット積を求め結果を bar に代入
  0xff &=> bar;
  
  // 0xff と bar のビット和を求め結果を bar に代入
  0xff |=> bar;

妙なことしようとしなければこれくらいで十分だよね... *2

+ - * / (算術演算子)

足し算、引き算、かけ算、割り算… 君は全部できる? ChucK ならできるよ!

  // 除算 (と代入)
  16 / 4 => int four;
  
  // 乗算
  2 * 2 => four;
  
  // 加算
  3 + 1 => four;
  
  // 減算
  93 - 89 => four;
型変換 (キャスト)

ChucK では、float を期待している文脈に int が与えられた場合、暗黙の型変換が行なわれます。しかし、他の型ではこのような型変換は行なわれません。データの損失が発生する型変換を行ないたい場合には、明示的な型変換を行なう必要があります。

  // float と int の加算は float になります
  9.1 + 2 => float result;
  
  // flot を int に型変換したい場合はキャストが必要する
  4.8 $ int => int foo;  // foo == 4
  
  // 2 つの float を期待している関数
  Math.rand2f( 30.0, 1000.0 );
  
  // 暗黙の型変換が行なわれるためこれは ok
  Math.rand2f( 30, 1000 );
% (余剰)

余剰演算子 % は整数値、浮動小数点数、期間、時間/期間に対する余剰の演算を行ないます。

  // 7 mod 4 (3 になる)
  7 % 4 => int result;
  
  // 7.3 mod 3.2 浮動小数点数の余剰 (.9 になる)
  7.3 % 3.2 => float resultf;
  
  // 期間の余剰
  5::second % 2::second => dur foo;
  
  // 時間と期間の余剰
  now % 5::second => dur bar;

最後の例 (時間/期間の余剰) はシュレッド間で動的な同期を行なうための方法の一つです。examples にある otf_01.ck から otf_07.ck にあるサンプルでは、on-the-fly で様々なパーツを同期させるためにこの方法を用いています。シュレッドが VM に追加されるタイミングについては気にする必要はありません。

  // 周期の定義 (いくつかのシュレッドで共有される)
  .5::second => dur T;
  
  // 現在のシュレッドのための余剰を求め、
  // その時間の分だけ時間を進める
  T - (now % T) => now;
  
  // ここまで来たらもう T の周期と同期できています
  
  // 残りの部分
  // ...

この方法は、ChucK で時間の流れを制御するための方法の一つです。意図している機能によっては異なる解決方法が必要な場合もあります。がんばって!

&& || == != > >= < <= (論理演算)

論理演算 - どれも 2 つの引数を取ります。返値は整数値の 0 または 1 になります。

  • && : and
  • || : or
  • == : equals
  • != : does not equal
  • > : greater than
  • >= : greater than or equal to
  • < : less than
  • <= : less than or equal to
  // 真との比較を行なう
  if( 1 <= 4 && true )
  <<<"horray">>>;
>> << & | ^ (ビット演算)

これらの演算子は int 値のビット単位での演算を行ないます。ビットマスクの計算などの場合に使用します。

  • >> : ビット右シフト ( 8 >> 1 = 4 )
  • << : ビット左シフト ( 8 << 1 = 16 )
  • & : ビット単位 AND
  • | : ビット単 OR
  • ^ : ビット単位 XOR
++ -- (インクリメント / デクリメント)

変数の後ろに ++ や -- をつけることで値をインクリメント/デクリメントすることができます。

  4 => int foo;
  foo++;
  foo--;
! + - new (単項式)

これらの演算子オペランドの前に配置します。

  // 論理否定
  if( !true == false )
  <<<"yes">>>;
  
  // 反転
  -1 => int foo;
  
  // オブジェクトの生成
  new object @=> object @ bar;

*1:いや変だし。余計な混乱を産むだけで意味ないし。

*2:"That's probably enough operator abuse for now..." の意味がよく分からん。