公開日: 11/25/2021

リストと式

この節では、JSL式の詳細や、JSL式の操作に使用できるいくつかのJSL関数を紹介します。これらの関数は、JSLでプログラミングを行う際にリッチでパワフルな、メタプログラミングツールキットとして役立ちます。このようなツールキットの利点の一つは、他の新しいプログラミング言語にあるようなマクロシステムと似た強力なマクロシステム機能を備えている点です。

JSL式を理解するためには、JSLで起こることはすべて、関数呼び出しの結果であるということを念頭に置くようにしてください。関数呼び出しには、次の3つの種類があります。

Prefix

関数名が引数の前に置かれます(たとえば、Foo( m, n ) )。JSLのほとんどの関数呼び出しは、この形式を取ります。

Infix

関数名が引数の間に置かれます(たとえば、m + n)。数学的演算子で一般的に使用される形式です。

special

[ ](添え字演算子)などの特殊関数を指します。特殊関数は数が少ないため、この節では特に取り上げません。

関数呼び出しは、3種類あるものの、すべてprefix形式で書くことができます。JMPの内部ではprefix形式が使われているため、JSL式全般をprefix形式で考えるようにすると、効率的です。

これがどういうことなのかを理解するため、JSL式の構文構造を詳しく見てみましょう。JSL式は、すべて、きわめて単純な構文構造に従わなければなりません。どの式も、head(関数名)とゼロ個以上の引数のシーケンスで構成されます。次の例では、最初、infix形式で書かれたJSL式を、規定の構文構造に従ってprefix形式に書き換えています。

x = 100.1
m & n
1 <= z <= 20

prefix形式で等価な式は、次のようになります。

Assign( x, 100.1 )
And( m, n )
Less or Equal( 1, z, 20 )

最初の例のheadは、Assignで、引数はxおよび100.1です。2つ目の例では、headがAnd、引数がmおよびnで、3つ目の例はheadがLess or Equal、引数が1、z、20です。これらの例において、「=」、「&」、「<=」の演算子は、それぞれ「Assign」、「And」、「Less or Equal」をわかりやすく書き換えたものと言えます。

簡単な例をもう少し見てみましょう。

Assign( 100.1, x )
And( )
Less or Equal( z )

これらの例も構文構造の条件に従っていますが、前述の例とは異なり、評価時にエラーとなります。JSL式について考えるとき、ある区別が重要になってきます。それは、JSL式の構文構造という概念と、式の評価結果の区別です。

次の例を考えてみましょう。同じ式が理解しやすい形式とprefix形式の両方で書かれています。

x = 100.1 + Log( z )                   // 理解しやすい形式
Assign( x, Add( 100.1, Log( z ) ) )    // prefix形式

この例は、式の引数にも式を使用できることを示しています。

// 理解しやすい形式

x = 0.8;
x ||= Random Uniform( 0.2, 0.6 );
 

// prefix形式

Glue(
	Assign( x, 0.8 ),
	Concat To( x, Random Uniform( 0.2, 0.6 ) )
)

この例は、JSLスクリプトとみなされるのが一般的ですが、1つのJSL式であるとも言えます。head式は、Glueで、引数としてAssign( x, 0.8 )Concat To( x, Random Uniform( 0.2, 0.6 ) )の2つの式を持ちます。この例から、2つの重要なことがわかります。

1. ;」記号は、Glue関数を読みやすく書き換えたものです。

2. JSLスクリプトとJSL式には、厳密には区別がありません。

以降、この節では、式を変数に割り当て、必要に応じて式を操作または実行する方法について解説します。JMPがJSL式を評価する際に名前をどのように解決するかについては、名前解決のルール節を参照してください。

式のクォートと非クォート

いつ式を評価するかを制御する演算子は、ExprEvalです。これらは、評価を遅延させる演算子と評価を促す演算子とみなすことができます。Exprは、引数を評価するのではなく、式としてそのままコピーします。Evalはその反対の処理をします。つまり、引数の値を求めてから、その結果を使って式全体を評価します。

ExprEvalは、JMPに対していつ式として認識し、いつ式の評価結果を戻すかを指示する、クォート演算子と非クォート演算子とみなすことができます。

以下のすべての例では、次の2つが割り当てられているものとします。

x = 1; y = 20;

Exprを使ってx+yを式として囲み、この式をaに割り当てた場合、aが評価されるときは、xyの現在の値を使って式を計算し、結果を戻します。(例外として、ShowWrite、およびPrintのユーティリティがあります。これらは、引数として名前だけを指定した場合、その名前に割り当てられている式を評価しません。)

x = 1; y = 20;
a = Expr( x + y );
a;

21

評価された式の結果を求めるのではなく、変数に保存された式そのものが得たい場合は、NameExpr関数を使います。結果ではなく保存された式を読み込むを参照してください。

x = 1; y = 20;
Show( Name Expr( a ) );

NameExpr(a) = x + y

式のクォートを入れ子にした場合、aが評価されるときに1階層だけが評価され、結果は式x+yになります。

x = 1; y = 20;
a = Expr( Expr( x + y ) );
Show( a );

a = Expr(x + y)

式の値を求めるときは、Evalを使ってすべての層の式を評価します。

x = 1; y = 20;
Show( Eval( a ) );

Eval(a) = 21

この操作は、次に示すようにどのレベルでも実行できます。

x = 1; y = 20;
a = Expr( Expr( Expr( Expr( x + y ) ) ) );
b = a;

Expr( Expr( x + y ) )

c = Eval( a );

Expr( x + y )

d = Eval( Eval( a ) );

x+y

e = Eval( Eval( Eval( a ) ) );

21

式の扱いの詳細については、Morgan (2010)を参照してください。

式を文字列としてクォート

JSL Quote()関数は、式の内容を、引用符で囲んだ文字列として戻します。文字列内のコメントと空白はそのまま維持されます。

次の式はその例です。

x = JSL Quote( /* クォートの開始 */
For( i = 1, i <= 5, i++,

// iの値を出力

Print( i );
);

// 式の終わり

);
Show( x );

出力では、JSL Quote()関数の内容が引用符で囲まれます。

x = " /* クォートの開始 */

For (i = 1, i <= 5, i++,

// iの値を出力

Print(i);

);

// 式の終わり

";

式のクォート

Expr()関数は、一般にJSL変数に式を保存するときに使用します。これをマクロのようなものと考えることもできます。次のスクリプトを見てみましょう。

dist = Expr( Distribution( Column( :"身長(インチ)"n ) ) );

次のように指定すると上記の式を実行できます。

dist;

式をループの中で使用して、実行することもできます。

For( i = 0, i < 3, i = i + 1, dist );

このループにより、身長に関する「一変量の分布」プラットフォームのレポートが3つ生成されます。

式の評価を明示的に指定するには、Eval()を使用してください。

Eval( dist );

ただし、列計算式における列に対しては、eval()は思い通りに動作しないことに注意してください。たとえば、

Formula( Log( Eval( Column Name( i ) ) ) );

はエラーになります。代わりに、次のように指定します。

Formula( Eval( Substitute( Expr( Log( xxx ) ), Expr( xxx ), Column Name( i ) ) ) );

また、

Formula( Eval( Column Name( i ) ) + 10 );

でもエラーが生じます。計算式において列が評価されているためです。代わりに、次のように指定します。

Formula(Eval(Substitute(Expr(xxx+10), Expr(xxx), column name(i))))

結果ではなく保存された式を読み込む

変数を評価(実際にプラットフォームを起動)するのではなく、変数に保存されている式(上の例のdistに格納されている式Distribution(Column(:"身長(インチ)"n))など)が必要な場合は、どうするのでしょうか。そのときは、Name Expr関数を使います。Name Exprは、評価せずに引数を式として読み込みます。

Exprが引数そのものを戻すのに対して、Name Exprは引数に保存されている式を検索します。Name Exprは式を得るために一番上の層だけを「開封」するのであり、すべての階層を開封するのではありません。

たとえば、変数に式を保存していて、その式を編集する必要がある場合などに、この関数を使うことになります。

popVar = Expr( Summation( i = 1, N Row(), (y[i] - Col Mean( y )) ^ 2 / N Row() ) );

Summation( i = 1, N Row(), (y[i] - Col Mean( y )) ^ 2 / N Row() )

 
unbiasedPopVar = Substitute( Name Expr( popVar ), Expr( Wild()/N Row() ), Expr( (y[i] - Col Mean( y )) ^ 2 / ( N Row() - 1 ) ) );

Summation( i = 1, N Row(), (y[i] - Col Mean( y )) ^ 2 / (N Row() - 1) )

次のスクリプトを実行してから、xExpr(x)NameExpr(x)、およびEval(x)を比較してみます。

a = 1; b = 2; c = 3;
x = Expr( a + b + c );

表8.1 Eval、NameExpr、Exprの比較

コマンドと結果

説明

x;

6

xa+b+cと評価し、さらにその式を評価して結果の6を戻す(外側の2つの層を開封する)。

Eval( x );

6

単にxを呼び出しているのと同じ。

xa+b+cと評価し、さらにその式を評価して結果の6を戻す(外側の2つの層を開封する)。

NameExpr( x );

a+b+c

xに格納されていた式a+b+cを戻す(1番外の層を開封する)。

Expr( x );

x

xを戻す(1つの層に封入する)。

JSLには式にアクセスして式全体を調べる関数も用意されており、これらの関数はすべて、引数として名前または文字式のどちらかをとります。以下の例では、expressionArgは、変数の名前、または、変数と演算子などから構成される複合式のいずれかです。

NArg(expressionArg)は、expressionArgの中の引数の数を調べます。

expressionArgは、式を含む変数の名前、評価結果が式になるような式、またはExpr()で囲まれた式のいずれかです。

NArg(name)は、nameから式(評価されていない状態)を取り出し、引数の数を戻します。

NArg(expression)は、expressionを評価し、引数の数を戻します。

NArg(Expr(expression))は、式expressionの引数の数を戻します。

たとえば、aExpr = {a+b,c,d,e+f+g};の場合

NArg(aExpr)4

NArg(Arg(aExpr,4))3

NArg(Expr({1,2,3,4}))4となります。

Head(expressionArg)は、演算子を使わないで表した式の先頭の関数(引数なし)を戻します。式が特殊文字の二項演算子、接頭演算子、接尾演算子であるときは、それらと等価な関数が戻されます。

expressionArgは、式を含む変数の名前、評価結果が式になるような式、またはExpr()で囲まれた式のいずれかです。

たとえば、aExpr = expr(a+b);の場合

r = Head(aExpr)Add()

r = Head (Expr(sqrt(r)))Sqrt()

r = Head({1,2,3}){}となります。

Arg(expressionArg,indexArg)は、式の中から指定の引数を抽出し、式として戻します。

たとえば、Arg(expressionArg,i) expressionArgのi番目の引数を抽出します。

expressionArgは、式を含む変数の名前、評価結果が式になるような式、またはExpr()で囲まれた式のいずれかです。

Arg(name,i)は、nameに含まれた式(評価されていない状態)を取得し、i番目の引数を検索します。

Arg(expression,i)は、expressionを評価し、i番目の引数を検索します。

Arg(Expr(expression),i)は、expressionのi番目の引数を検索します。

別の例としてaExpr = Expr(12+13*sqrt(14-15));の場合を示します。

Arg(aExpr,1)12

Arg(aExpr,2)13*sqrt(14-15)

Arg(Expr(12+13*sqrt(14-15)),2)13*sqrt(14-15)

式の中の引数の引数を抽出するには、Argコマンドを入れ子にします。

Arg(Arg(aExpr,2),1)は、aExprの第2引数の中の最初の引数、つまり13を戻します。

Arg(Arg(aExpr,2),2)Sqrt(14-15)

Arg(Arg(Arg(aExpr,2),2),1)14-15

Arg(Arg(Arg(aExpr,2),2),3)Empty()

以下に、最後の式がどのように開封されていくかを説明します。

1. 内側のArgステートメントが評価されます。

Arg(aExpr,2)

13 * Sqrt( 14 - 15 )

2. 次に、内側から2番目のArgが評価されます。

Arg(Arg(aExpr,2),2)
// これはArg(Expr(13 * Sqrt( 14 - 15 ) ), 2)と等価

Sqrt( 14 - 15 )

3. 最後に、外側のArgが評価されます。

Arg(Arg(Arg(aExpr,2),2),3)
// これはArg (Expr(Sqrt( 14 - 15 ) ), 3)と等価

Empty()

Sqrt式の要素は1つだけなので、第3引数への要求に対してEmpty()が戻されます。Sqrt式内の2つの引数にアクセスするには、次のようにします。

Arg(Arg(Arg(Arg(aExpr,2),2),1),2);

15

HeadName(expressionArg)は、式の先頭の関数の名前を文字列として戻します。式が特殊文字の二項演算子、接頭演算子、接尾演算子であるときは、それらと等価な関数の名前が戻されます。

expressionArgは、式を含む変数の名前、評価結果が式になるような式、またはExpr()で囲まれた式のいずれかです。

たとえば、aExpr = expr(a+b);の場合

r = HeadName (aExpr)"Add"

r = HeadName (Expr(sqrt(r)))"Sqrt"

r = HeadName ({1,2,3})"List"となります。

JMPのこれまでのバージョンでは、ArgNargHead、およびHeadNameがそれぞれArgExprNArgExprHeadExpr、およびHeadNameExprとして実装されていました。基本的に働きは同じでしたが、引数の評価を行わない点が異なります。旧バージョンの形式は、今後のマニュアルには記載されなくなる予定です。

文字列の部分置換

Eval Insertでは、文字列の中に含まれる式を評価することによって、文字列の部分置換を行えます。

Eval Insertでは、式の前後の文字を指定すると、その間の式が評価、展開されます。

結果を返す関数と、値を置き換える関数の2通りがあります。

resultString = EvalInsert( 式が埋め込まれた文字列,開始区切り文字,終了区切り文字 )
EvalInsertInto( 式が埋め込まれた文字列を持つ変数,開始区切り文字,終了区切り文字 )

区切り文字の指定はオプションです。デフォルトの開始文字は"^"、デフォルトの終了文字は、開始文字と同じ文字です。

xstring = "def";
r = Eval Insert( "abc^xstring^ghi" );  // "abcdefghi"を戻す
 
r = "abc^xstring^ghi"; // 値を置き換え
Eval Insert Into( r ); // rが"abcdefghi";になる
 

// 区切り文字の指定

r = Eval Insert( "abc%xstring%ghi","%" );  // "abcdefghi"を戻す
 

// 開始値と終了値が異なる例

r = Eval Insert( "abc[xstring]ghi","[","]" );  // "abcdefghi"を戻す

数値にロケール固有の表示形式が含まれている場合は、<<Use Locale(1)オプションを含めます。次の例は、小数点の代わりにカンマを使用するロケール環境で実行したものです。

Eval Insert( "^1.2^", <<Use Locale( 1 ) );

1,2

リストの中の式を評価する

Eval Listは、リスト内の式を評価し、その結果をリストにして戻します。

x = { 1 + 2, 3 + 4 };
y = Eval List( x );   // yに{3,7}を戻す

Eval Listは、Column Dialogまたは引数Modalを使用したNew Windowから戻された、ユーザの選択によるリストをロードするのに便利です。

式の中の式を評価する

Eval Expr()は、内側の式だけを評価し、その結果を含む式を戻します。これと比較して、Evalは内側の式を評価し、結果を用いて式をさらに評価します。

データテーブルにX3という名前の列があるとしましょう。ここで、Eval Expr()を使って内側の式を最初に評価する例を紹介します。

x = Expr( Distribution( Column( Expr("X"||Char( i ) ) ) ) );
i = 3;
y = Eval Expr( x );   // Distribution( Column( "X3" ) )を戻す

結果をさらに評価するには、その後のステップで結果を呼び出すか、Eval Expr()Eval()で囲む必要があります。以下の例では、「一変量の分布」レポートを作成します。

// 2つのステップを取る方法

x = Expr( Distribution( Column( Expr( "X" || Char( i ) ) ) ) );
i = 3;
y = Eval Expr( x );
y;
 

// 1つのステップで済む方法

x = Expr( Distribution( Column( Expr( "X" || Char( i ) ) ) ) );
i = 3;
Eval( Eval Expr( x ) );

最初にEval Exprを実行せずに、直接xEvalを使おうとした場合にどのようなことが起こるかについては、表8.3 を参照してください。

文字列を式に、式を文字列に変換する

構文解析とは、プログラム言語の式として文字列を取り込むことです。JSLの式を文字列として保持しており、その文字列から式を構築したいとします。Parse関数は式を戻します。その式を評価するにはEval関数を使います。

x = Parse( "a=1" ) ;  // 現在xには式a=1が入っている
Eval( Parse( "a=1" ) ); // 現在aには値1が入っている

この逆をするには、式を文字列に変換するChar関数を使います。通常、Charは引数を評価してから文字列に変換するため、Char関数の引数をExpr関数(または変数のNameExpr関数)にします。

y = Char( Expr( a = 1 ) ); // yは「a=1」という文字値になる
z = Char( 42 ); // "42"を戻す

引数が数値の場合、Char関数では、フィールド幅と小数桁数の引数を指定できます。デフォルトでは、フィールド幅は18で、小数桁数は99です([最適]の形式)。

Char( 42, 5, 2 ); // "42.00"の文字値を戻す

数値にロケール固有の形式を維持するには、次の例のように、<<Use Locale(1)オプションを含めます。

Char( 42, 5, 2, <<Use Locale(1) ); // フランスのロケールで実行した場合、"42,00"になる

Charの逆は、それほど簡単ではありません。文字列を式に変換するときはParseを使いますが、文字列を数値に変換するときはNumを使います。

Parse( y );
Num( z );

表8.2 式の保存と計算をする関数

関数

構文

説明

Char

Char(Expr(expression))

Char(name)

expression(式)を文字列に変換する。式はExprで囲む必要があります。囲まない場合、その評価結果が文字列に変換されます。

string = char(number, width, decimal)

number(数値)を文字値に変換する。widthdecimalは、形式を指定するためのオプションの引数です。デフォルトでは、width(フィールド幅)は18で、decimal(小数桁数)は99です。

Eval

Eval(x)

xを評価し、次いでxの結果を評価する(非クォート)。

Eval Expr

Eval Expr(x)

xの中の式すべてを評価して、式を戻す。

Eval List

Eval List(list)

リスト(list)内の式を評価した後のリストを戻す。

Expr

Expr(x)

引数を評価しないまま戻す(式のクォート)。

NameExpr

NameExpr(x)

xに格納されている式を評価せずに戻す。NameExprは、xが1つの変数名の場合、名前xを評価せずに戻すのではなく、その変数xに保存されている式を評価せずに戻します。その点を除けば、NameExprExprと同じです。

Num

Num("string")

文字列を数値に変換する。

Parse

Parse("string")

文字列をJSLの式に変換する。

まとめ

表8.3では、xを使った評価制御関数のさまざまな使い方を比較しています。データテーブルにX3という名前の列があり、xiが割り当てられているとしましょう。

x = Expr( Distribution(Column( Expr("X"||Char( i ) ) ) ) );
i = 3;

表8.3 評価を制御する関数

コマンドと結果

説明

x; // または

Eval(x);

見つかりません。 'distribution'へのアクセスまたは評価 , 不正な引数( {"X" || Char( i )} ), distribution( Column( Expr( "X" || Char( i ) ) ) )

Eval(x)xの呼び出しは同じ。

distribution( column( expr( "X" || Char( i ) ) ) )を評価します。結果はエラーとなります。列名は、Expr()関数によって囲まれているため、"X"||Char(i)のように認識されます。

Expr(x);

x

xを戻す(追加の層によりxを包みこむ)。

Name Expr(x);

Distribution(Column(Expr("X" || Char(i))))

xに保存されている式は、
Distribution(Column(Expr("X" || Char(i))))です。

y=Eval Expr(x);

Distribution(Column("X3"))

内側の式を評価するが、外側の式は評価しないため、yDistribution(Column("X3"))

y; // または

Eval(Eval Expr(x));

一変量の分布[]

Eval(eval expr(x))yの呼び出しは同じ。

Distribution(Column("X3"))を評価し、プラットフォームを起動します。

z = Char(nameexpr(x));

"Distribution(Column(Expr (¥!"X¥!" || Char(i))))"

式全体を文字列としてクォートする。必要に応じて、¥!"というエスケープ文字を追加します。

Char(x)と指定すると、最初にxを評価しようとしてエラーが発生するため、欠測値を引用符で囲んだ"."が戻されることに注意してください。

Parse(z);

Distribution(Column(Expr("X" || Char(i))))

文字列を解析(パース)して式を戻す。

a = Parse(Char( NameExpr(x)));

Eval(EvalExpr(a));

一変量の分布[]

とても極端な例。

この例は、少なくともこれを2つのステップに分解する必要があることに注意してください。これを1つの大きなステップに組み合わせると、Eval Expr関数が、Parse層以下の内容を評価せずにそのまま残してしまうため、別の結果となります。

Eval(
	EvalExpr(
		Parse(
			Char(
				NameExpr(x)))));

Distribution(Column(Expr("X" || Char(i))))

より詳細な情報が必要な場合や、質問があるときは、JMPユーザーコミュニティで答えを見つけましょう (community.jmp.com).