SuperColliderショートカットマスターへの道2 - Syntax Shortcuts -

前回の続き。ちょっとはsmalltalkオブジェクト指向に慣れて、今まで分からなかったコードも理解出来るようになった気がします。というわけで、Syntax-Shortcuts.htmlを紐解いてみたいと思います。

4. ショートカット shortcut

では、ショートカット記述について、helpのsyntax shortcutから学んでいきましょう。

メッセージの2通りの記述方法

1.4.2 謎の記述方法にて、レシーバを省略するとメッセージ内の第1引数がレシーバとして認識される、ということがわかりました。別に内部でどのように認識されているのか詳しく知る必要はないかと思います。helpのMethod-Callsを見てみると、バイナリオペレータのメッセージは、関数形式とレシーバ形式どっちでも書けるよ!とありますね。

	//functional notation
	do ( 2, { arg x; x.postln } );
	//receiver notation	
	2.do ( { arg x; x.postln } );

というわけで、メッセージの記述方法(レシーバの指定方法)は基本的に上記の2通りです(他にbinary operator syntaxとか?)。どちらが正しいとかショートカットだとかは特にないようです。ただし、1.4.2で解説したように、上の記述方法では、関数形式ですが”2”にあたる第1引数はSC内でレシーバとして認識されているようです。上の関数形式はC言語ライクに、do関数に2つの実引数を渡していると読めます。一方、下の記述方法は、smalltalk流のメッセージ式に乗っ取った記述方法で、2というレシーバにメッセージとして、セレクタはdo、引数は関数を送っているという解釈になります。(どちらも書けるようにしたのは、Cになれた人でも書けるよ!とかやりたかったのかなあ?)
メッセージが1つなら苦労はしませんが、実際のコードでは複数のメッセージを組み合わせて使いますよね。関数内に関数を書いたり、レシーバの戻り値をレシーバとしたり。2つのメッセージでも以下のようにややこしく書けたりします。

	//完全に関数形式。これはわかりやすいですね	
	max( min ( 10, 100 ), 1 );
	//minだけレシーバ形式に変換可能です。	
	max( 10.min( 100 ), 1 );
	//上のコードでのmaxの第1引数は10.min(100)、これはレシーバとして扱えるので”.”の前に出せます。このコードは全部レシーバ形式ですね。	
	10.min( 100 ).max( 1 );
	//minを関数形式に戻してみる。	
	min( 10, 100 ).max( 1 );	

以上のように、関数形式とレシーバ形式の両方で書け、さらに混在させることもできます。コード読むときには、どちらか好きな方に変換してみるとより理解しやすいかもしれませんね。

	//どっちが分かりやすいかはケースバイケースですね。myValをごにょごにょしたい場合は下のようにレシーバ形式にした方が直感的かなと。
	var myVal=100;	hypot( atan2( absdif( distort( myVal, 2), 1 ), 1 ), 20 );
	var myVal=100;	myVal.distort(2).absdif(1).atan2(1).hypot(20);
アクセサメソッド

クラスを書く際に、変数へのアクセスをメソッドで制限することがあります。アクセサメソッドには、セッター、ゲッターがあり、それぞれ変数へ値を代入する、変数の値を参照するメソッドのことです。helpのClassesにセッター、ゲッターの説明があります。ゲッターメッセージは変数と同じ名前でレシーバに送ったときに、その変数の値を返す、セッターメッセージは変数と同じ名前プラス”_”に加え引数にその変数に代入したい値を指定するわけですよ。とか書いてあります。セッターメソッドがなければ、変数に代入できませんし、ゲッターがなければ参照できないわけですね。
と、前置きはこのくらいにして、ヘルプファイルには、instead of writing: に謎の文が書いてありますが、これらは1行目に変数宣言、2行目にゲッターメソッド、3行目にセッターメソッドが定義されています。ぱっと見分かりづらいですが、^は返り値を示しているので、xメソッドはxの値を返す、x_メソッドは引数を変数xに代入する、という処理を行っています。クラスを書くとき、変数はvar 変数名と宣言します。

	ついでにセッターメソッドも定義したい場合は
		var >x;
	ゲッターメソッドも定義したい場合は
		var <x;
	どちらも定義したい場合は
		var <>x;
	と記述します。方向で処理を表しているんですね。

instead of writing: みたいに書く人は皆無だと思いますので、<>の記述が基本だと思いますよ。

セッターメソッドの呼び出し。セッターメッセージの記述方法

上でセッターの定義の記述方法について述べました。では、セッターメソッドの呼び出し方法について。instead of writing: ではセッターメソッドをx_、変数名_で記述していました。ということは、instance.x_(引数) のx_メッセージで変数xに引数を渡すことができるわけです。変数名_が基本かどうかは不明ですが、より分かりやすい記述方法があります。

	//myInstanceクラスがあるとして、var <>x;を宣言していると仮定してください。
	//変数xに1を代入
	myInstance.x_(1);
	//変数xに1を代入
	myInstance.x = 1;

これもどちらが良いというのはなく、下の記述方法はCライクでわかりやすいです。上の記述方法は、あるインスタンスの複数の変数に値を代入する際に役立ちます。

	myInstance.x_(1).y_(2).z_(3);

とかですね。SC140で役立つと思いますよ。たぶん。

バイナリオペレータとしてセレクタを使う

なんでしょうねこの記述方法は。

	min(1,2);	//functional notation
	1 min: 2	//?

1がレシーバ、minはセレクタで2が引数を表しています。smalltalkにこの記述方法があります。(参考)http://d.hatena.ne.jp/sumim/20100207/p1セレクタ

コレクションの要素にアクセス

これはたまに見かけますね。コードは短くなりますが、大抵の人は読めなくなるでしょう。

	z=[1,2,3,4]; x = z.at(0); y = z.at(1); x.postln; y;
	z=[1,2,3,4]; #x, y = z; x.postln; y;
	zコレクションの要素に頭からアクセスできます。
(
	var a,b,c,z;
	z = [1, 2, 3];
	//これが基本記述。直感的に理解できますね。
	//a = z.at(0); b = z.at(1);	c = z.at(2);
	//見ての通り、かなり短く書けます。でも、この書き方されると解読不可です。
	#a, b, c = z;
	a.postln;b.postln;c;
)
Environmentの変数

Environmentクラスなんざ知らん!で構わないと思いますが、ちょい解説をしておきます。
Environmentの変数に代入

	//envirSetじゃなくて、Putね。
	'globalValue'.envirPut(100);
	//おなじみの〜と同じこと
	~globalValue1 = 100;
	~globalValue2 = 200;

Environmentの変数を参照

	//シンボルにenvirGetメッセージを送る
	'globalValue'.envirGet;
	//~変数名での参照と同じこと
	~globalValue1;
	~globalValue2;

	currentEnvironment
	topEnvironment

envirPutとかcurrentEnvironmentとかなんのこっちゃですが、どうやら”~”と関係があるようです。では、Environmentのヘルプを見てみましょう。IdentifyDictionaryをスーパークラスとして、どっからでもアクセス可能にしたよ!的なことが書いてあります。よくわかりませんが、どうやらSCを立ち上げると、topEnvironment変数、currentEnvironment変数をもったEnvironmentインスタンスが作成されるっぽいです。topEnvironmentは、インタプリタ値(?)a,b,c...と同様などっからでもアクセスできるコレクションです。SCのコンパイラはcurrentEnvironmentのショートカット記述として"~"をもってます。と書いてあります。

	//これらは同じ
	~myvariable = 888;
	currentEnvironment.put('myvariable', 888);
	//ということは上で出てきたこれも同じ?
	'myvariable'.envirPut(888);
	
	//こっちも同じ
	~myvariable;
	currentEnvironment.at('myvariable');
	//こいつも同じ?
	\myvariable.envirGet;

envirGet,envirPutはどちらもsymbolクラスのメソッドです。symbol.scをみると、

	envirGet {
		_Symbol_envirGet
		^currentEnvironment.at(this)
	}

となってます。currentEnvironmentの値を参照していますね。envirPutメソッドの方も

        envirPut { arg aValue;
		_Symbol_envirPut
		currentEnvironment.put(this, aValue);
		^aValue
	}

currentEnvironmentのインデックスを指定して引数を渡していることがわかります。Syntax-Shortcutsでは、currentEnvironmentを省いているため、なにやっているのか不明ですが、実際はEnvironmentインスタンスのcurrentEnvironmentに値を代入したり、参照したりしているだけです。なるほど、今まで~でグローバル変数だ!とか思っていたのは、実はEnvironmentオブジェクトの変数だったわけですね。ま、グローバル変数ですけど。

(おまけ) IdentifyDictionary:連想配列のこと。連想配列とは、int型以外の型を要素のインデックスとして扱える配列のことで、辞書とも言われます。普通の配列では、a.at(1)とかインデックスはint型ですが、連想配列ですと、a.at('index')といった具合にインデックスをシンボルにすることができます。ちょっと例を

	a = ();
	a.put(0,10);			//0番目の要素に10を代入
	a.put('index', 100);	//’index’の要素に100を代入
	a.put("test", 1000);
	a.at(0);
	//インデックスとして、'シンボル'はおk、"文字列"は駄目?
	a.at('index');
	a.at("test");

連想配列は分かったでしょうか?Environmentクラスは連想配列のサブクラスという位置づけですので、インデックスにシンボルを用いることが可能です。つまり、'a'というシンボルに100という値を代入することができます。かつ、'a'というインデックスには、どこからでもアクセスすることができます。

	//再度下記を実行してみてください
	currentEnvironment;
	//見事に連想配列ですね!しかもどこからでも、アクセスできる連想配列ですよ!
インスタンス作成

これは使いますね。

	//クラスからインスタンスを作成するときは、newメソッド
	Point.new(1, 2);
	//newメソッドは省略可能
	Point(1, 2);
	
	//引数はnullでもおk
	a = Point();
	a.set(1,1);

newメソッドはあっても、なくてもどっちでもインスタンス作成できます。

コレクションの要素に代入かつ作成

これもよく使います。ヘルプではSetになってますが、Setは順番関係なしになるので、Arrayの方が分かりやすいかと。

	//newメソッドで定義してからaddメソッドで要素に代入しています。
	Array.new.add(1).add(2).add(3);	
	//newメソッドもすっとばして、いきなり要素に代入。
	Array[1, 2, 3];
	//インスタンス作成のショートカットをつかって書くとこう。特に意味はないですけれど。
	Array().add(1).add(2).add(3);
引数のブロックなくしてもいけるよ!

セレクタの後に続く引数は()で括る必要がないということでしょうか。意味不明。誰得。

	//通常の関数形式記述
	x=1; if ( x<3, {'abc'}, {'def'} );
	//()を省略してもおkらしい
	x=1; if (x<3) {'abc'} {'def'};
	
	//通常のレシーバ形式
	x=1; (x<3).if( {'abc'},{'def'});
	//こちらも()省略できます
	x=1; (x<3).if {'abc'} {'def'};
	
	//helpより
	z=2; z.do({arg x; x.postln });
	z=2; z.do { arg x; x.postln }	//shortcut
	a=1; while( { a<200 }, { a = a*2; a.postln } );
	a=1; while { a<200 } { a = a*2; a.postln };	//shortcut

なんでしょうかね、この記述方法は。smalltalkあたりにあるのでしょうか?

引数をより短く

通常はargで定義しますが、||で囲んでもおkです。このとき、セミコロン;はいらないので付けないようにしましょう。人によっては、これ一択ですね。

	//通常は、argで引数を定義(仮引数ね)
	{ arg x; x=2 }.value;
	//argの代わりに||で囲ってもおk。このとき;はつけないように注意
	{ |x| x=2 }.value;	
	//もちろん初期値の代入も可能です	
	{ |x=2| x }.value;	
	
	f = { |x, y| x.postln;y.postln }; f.value(1,2); f.value(x:3,y:4);

使う人が多いので、覚えておきましょう。Rubyでもこの記述方法はあります。

valueメソッドの略

関数から引数に代入するvalueメソッドは略せます。

	a = { arg x; var y=10; x+y};
	//基本的な引数への値の渡し方
	a.value(1);
	//valueメソッドは省略可能
	a.(1);

これは知っておかないと、え?となりますね。

シンボルの記述

どっちがショートカットか知りませんが、一文字短く書けます。

	'symbol'
	\symbol

色付けると付属エディタのデフォルト値は'緑'になりますので、ぱっと見で分かると言えばわかります。

Ref

よくわからないのでパス。ショートカットは" ` "バッククォートです

コレクション、配列のインデックス

これも基本より使われています、どちらも書けた方が良いかと。

	a = Array[10, 20, 30];
	//インデックス指定はatメソッド
	a.at(0);
	//[]でインデックス指定できます
	a[0];

コレクションインスタンス作成のショートカットとごっちゃにならないように気をつけましょう。

コレクション、配列の要素へ代入

インデックス指定と同じです。

	a = Array[10, 20, 30];
	//インデックスで要素指定して、値を代入するにはputメソッド
	a.put(0, 100);
	//[]でインデックス指定して、そこの要素に代入
	a[0] = 200;
連想配列、辞書作成

連想配列は、インデックスを数値じゃなくて、文字とかで定義できる配列のことです。SCで連想配列を多様する方がどのくらいいるか不明ですが。Rubyのハッシュリテラルと同じですよ。

	//省略を使わないで連想配列を作成
	z = IdentityDictionary.new.add(\a->1).add(\b->2);
	//newメソッドなどは省略可能
	z = IdentityDictionary[ \a->1, \b->2 ];
	z.at(\a).postln; z[\b];		//結果

	//index: 値で連想配列を作成できます
	z = (a: 10, b: 20);
	z.at(\a).postln; z[\b];		//結果

使うひとはいちいちIdentityDeictionaryなんて書かないでしょうから、下の記述法は知っておいた方が良いかと。

等差階級配列

こちらもRubyライクです。*.seriesメソッドはArrayCollectionクラスのメソッドで、引数は( size, start, step )となってます。下の書き方のseriesはSimpleNumberクラスのメソッドで、引数は( this, second, last )になっていて、stepはnext-thisで算出されます。seriesメソッドでも引数が異なるので注意しましょう。ショートカット記述は一番下です。大分シンプルになりますね。

	//ArrayCollectionのseriesメソッドを使う
	Array.series( 10, 1, 1 );
	//SimpleNumberのseriesメソッドを使う
	series( 1, nil, 10 );
	//省略形
	( 1 .. 10 );

	//ArrayCollectionのseriesメソッドを使う
	Array.series( 10, 1, 3 );
	//SimpleNumberのseriesメソッドを使う
	series( 1, 4, 28 );
	//省略形
	( 1, 4 .. 28 );	
配列のコピー

配列のインデックスを範囲で指定して別配列にコピーします。

	a = Array.new.add(1).add(2).add(3).add(4);
	a = [1,2,3,4];
	
	//配列aのインデックス0から2まで配列bにコピーします
	b = a.copyRange( 0, 2 );	
	//配列のインデックス0から2までをbに代入
	b = a[ 0 .. 2 ];
	
	//配列aのインデックス2から最後まで配列bにコピーします
	b = a.copyToEnd(2);
	//省略形
	b = a[ 2 .. ];	
	
	//配列aのインデックス0から2まで配列bにコピーします。
	b = a.copyFromStart(2);	
	//省略形
	b = a[ .. 2 ];

別にコピーメソッドは知らなくて良いと思います。参考までに、ArrayクラスのcopyRangeメソッドはスーパークラスのSequenceableCollectionクラスのメソッドを継承しています。そのまま継承しています。SequenceableCollection.scみるとcopyToEnd,copyFromStartはcopyRangeを使用して実装していますね。

performList呼び出し

performListってなに?

	//performListメソッドというものがあるらしい
	a.performList(\postln);
	//これと同じこと?
	a.postln;

Object.scには以下と定義されていますね。

	performList { arg selector, arglist;
		_ObjectPerformList;
		^this.primitiveFailed
	}

メソッドを呼び出すメソッドってことかな?

partial application(部分適用)

もう、いやだ。リンク張ってごまかす。
http://supercollider.jp/modules/bwiki/index.php?Partial-Application

以上

結局こうも書けるよ程度にしかならないですね。さらに、下に行くほどだれてます。SCが多様な構文を許可している理由は不明ですが、推測するに、他の言語に合わせたよ!取っ付きやすいでしょ!じゃないでしょうか。逆効果で、混乱する人が多数発生しましたけど。

次は基礎文法を修正したいと思います。そのあと、前回のオブジェクト指向を修正する予定です。最後にこの文を修正します。