最近の更新 (Recent Changes)

2014-01-01
2013-01-04
2012-12-22
2012-12-15
2012-12-09

Wikiガイド(Guide)

サイドバー (Side Bar)

--
← 前のページに戻る

3. Closure Basicの仕様

Clossure Basicは、名前の通り、Basic言語の一種です。 しかし、昔の行番号を振って、goto文で飛び回るようなところは改善してあり、構造化した構文を使えるようにしてあります。

また関数をクロージャとして定義できるようにして、現代的なプログラミングにも対応できるようにしました。

なお、このClosure Basicでは、昔のBasic言語と異なり、大文字と小文字は厳密に区別されます。

3.1. 手続き処理の仕様

3.1.1 変数の定義

変数は、特別な型を持たず、浮動小数点数、文字列、またはクロージャを代入できます。 一度値を代入された変数であっても、別の型の値を代入しなおすことができます。 たとえば、数値10.23を入れた変数に対して、文字列"abc"を入れなおして使えます。

Closure Basicの変数は、特別に定義することなく使うことができます。

変数を参照または代入するときに未定義だった場合には、その場で作成されます。 また、未定義だった変数を参照した場合には、値は数値の0となります。 このような変数は、手軽なプログラミングに向いています。 この仕様の是非は本来考えないといけないのですが、ここでは元となったBasic言語に敬意を払ってこのような仕様としました。

特別な定義が不要な変数ですが、配列についてはその大きさを事前に宣言しておかないと使えません。 宣言はdim文で行います。


dim 配列名[配列の大きさ]

上記で定義される配列は、0から配列の大きさまでのインデックスを持ちます。 C言語やJAVAなどのように、最大のインデックスが(配列の大きさ - 1)ではないことに注意してください。

3.1.2 文の区切り

C言語やJAVAのように、文末を" ; "セミコロンで区切る必要はありません。

また、一つの文の中に複数の文を書くときには、" : "コロンで区切ってください。セミコロンでないことに注意してください。


文 : 文 : 文

a = a + 1: b = a*2: c = b

文の区切りについても、Basic言語と同じ仕様です。

しかし、Closure Basicでは、if文などでも、複数の文を実行文として制御できるのであまり使う機会はないかもしれません。


if文の例

  if a = b+1 then
     c = a*2
     d = d + 1
  else 
     c = 0
  end

3.1.3 コメント

" ' "シングルクォートから、行末までをコメントとします。


コメントの例

   ' コメント内容

3.1.4 代入文

代入文で使える演算子は、以下の通りです。


  単項+, 単項-
  *, /
  +, -

優先度は下から上に向かって高くなる。

演算の対象は、浮動小数点数、変数、ユーザー定義関数、組み込み関数です。

ユーザー定義関数はクロージャです。

組み込み関数には、乱数を返すrandom(最大乱数値)があります。

変数への代入記号は"="を使います。


代入文の例

  olive = 3.14 * (tomato + random(3)) - func1(jam)

3.1.5 条件判定

if文は以下のような構文です。


  if 条件式 then
     複数の実行文
  else if 条件式
      複数の実行文
  else
     複数の実行文
  end

else ifとelseは、省略可能です。 else ifは複数を連続して書くことが可能です。 elseは最後に一つだけ書けます。

if文の最後には必ずend文を置かなければなりません。

昔のBasic言語とは異なり、条件が合致したときの実行文は複数行書くことができます。

条件式は、比較の演算子として、=, ==, >, >=, <, <=, <>が使えます。

また、複数の条件式を、andやorで論理積や論理和をとることもできます。

一致の判定する比較演算子としては、==と=が使えます。 不一致の判定をする比較演算子としては、<>が使えます。(C言語やJAVAのような!=は使えません)


if文の例

  if i = 1 then
     a = 0
  else if (i = 2) and (j <> 1) then
     a = 1
     b = b + 1
  else if (i = 3) or (j > 2) then
     a = 2
     b = b + 3
  else 
     a = 3
  end

3.1.6 ループ処理

Closure Basicのループ処理には、for文を使う場合と、while文を使う2通りの方法があります。

for文は、繰り返し回数が決まっているものに使います。

while文は、条件が一致するまで処理を繰り返す場合に使います。

まずは、for文から説明しましょう。

for文は以下のような構文です。


  for 変数 = 初期値 to 終了値
     複数の実行文
  next

for文については、ほとんど昔のBasicと同じです。しかし、異なるところもあるので注意してください。

変数が初期値から終了値まで+1されていきながら、内部の実行文を実行していきます。

ループする条件には、初期値も終了値も含まれるのに注意してください。

たとえば、for i= 0 to 10の場合には、0から10までの11回のループが実行されます。

なお、Closure Basicでは、while文の構文もあるので、for文はあまり高機能にはしていません。

初期値は必ず終了値より小さくなければ、ループは実行されません。

ステップ(増分)は指定できず、必ず1ずつ変数が増やされていきます。

nextは、最も近く実行されたfor文と対応します。 昔のBasic言語のように、対応するfor文の変数をnext文に指定することはできません。


for文の例

  for i = 0 to 9
    for j = 0 to 9
      実行文
     next  ' ← 変数jのfor文に対応
  next    ' ← 変数iのfor文に対応

次にwhile文について説明しましょう。

while文は以下のような構文です。


  while 条件式 do
     複数の実行文
  end

while文は条件式の条件が満たされる間は、内部の実行文を実行し続けします。

条件式には、前の項で説明したif文の条件式と同じものが指定できます。

実行文の範囲の最後には、end文を置きます。


while文の例

  i = 100
  while i >= 0 do
    print i
    i = i - 10
  end

3.1.7 出力

print文によって、結果出力などの文字列や数値を出力出来ます。

print文は以下のような形式です。


  print 文字列または数値または変数, ...

  print 文字列または数値または変数, ... ;

  print

print文の引数に、文字列、数値または変数を,カンマで繋いで指定すると、その順番に出力し最後に改行します。

print文の引数の連なりの最後に;セミコロンがあると、引数の出力後に改行しません。

引数のないprint文の場合には、改行だけを実行します。


print文の例

i=123
print
print "abc", 1+2, i
print "date";
print "01/11"

上の例を実行すると次のように出力されます。


abc 3 123
date01/11

3.1.8 入力

input文およびinput#文で、ターミナルからの入力ができます。

input文は文字列の入力を行い、input#文は数値の入力を行います。

なぜ、このように2種類のinputが必要かと言いますと、Closure Basicには変数の型がないからです。 変数に型がないために文字列の型の変数か数値の型の変数か区別がinput文からはできないため、どちらの入力をするのかをinputかinput#で決められるようにしています。

input文は以下のような形式です。


  input 変数, ...

  input 文字列, 変数, ...

変数に、入力された文字列が代入されます。 引数に文字列がある場合にはその文字列を出力してから、変数に入力された文字列のみが代入されます。

input#文は以下のような形式です。


  input# 変数, ...

  input# 文字列, 変数, ...

input文と同じです。変数に入力された数値が代入されます。 引数に文字列がある場合にはその文字列を出力してから、変数に入力された数値のみが代入されます。

なお、input#文の場合には入力が数値でない場合には、"redo from start"と出力されて、もう一度数値の入力を促します。


input文の例

input# "a = ", a
print a
print a+2

input "b = ", b
print b
print b+2


上の例の実行結果を次に示します。


実行結果

a = abc
redo from start
a = 10
10
12
b = abc
abc
abc2

3.2. クロージャとしての関数・サブルーチン

3.2.1 fun文

Closure Basicで、関数・サブルーチンを定義するには、fun文を使います。

関数はクロージャとして実現されています。クロージャについては、後の項で詳しい説明をしたいと思います。

fun文を使った一般的な関数の定義は以下のようになります。


変数 = {fun(引数の変数, 引数の変数, ...) 複数の実行文: return 返り値}

例えば、階乗を計算するfactは次のように定義します。


fact = {fun (n)
        if n <= 1 then
                return 1
        else
                return n*fact(n-1)
        end
}

print fact(3)
print fact(5)
print fact(8)

実行結果を以下に示しましょう。


$ descartes ClosureBasic factor.cbs
Compiling...
Run
6
120
40320
result --
        <compile_run>
-- true

定義を見るとわかると思いますが、{fun() ~}という名前の無い無名関数を変数に代入することによって関数の定義をしています。

このような無名関数を以降ではラムダ関数と呼びましょう。

上の例では、factという関数が定義されていますが、fact自身はただの変数です。 そこにラムダ関数を代入することによって初めて関数として働くようになります。

そのため、factの値を別の変数に代入することもできます。 代入された変数は新たに関数として働くことが可能になります。


fact = {fun (n)
        if n <= 1 then
                return 1
        else
                return n*fact(n-1)
        end
}

kaijo = fact

print kaijo(8)

fact = "this is strings"

もちろん元の関数の代入されていた変数に別の値を入れることも可能です。

また、ラムダ関数の定義の後ろに引数をカッコで括って付けると直接実行することもできます。


 {fun(引数の変数, ...) ~ } (引数, ...)

つまり、ラムダ関数または、ラムダ関数に代入された変数に引数が付けられると関数が評価されて実行されます。

以下にラムダ関数のいろいろな使い方のバリエーションを示しましょう。


b = {fun (a, b) return a+b }

a = b(2, 3)

print a

c = 1 + {fun (a, b) return a+b } (7, 3)

print c

print {fun (f1, b, a) return f1(a,b)}(b, 3, 8)

print {fun (f1, b, a) return f1(a,b)}({fun (a, b) return a-b }, 8, 3)

変数への代入と参照が自由であることから、上に示すように計算式の途中にラムダ関数を書くこともできます。

関数の引数にラムダ関数を代入した関数を入れることも可能です。

さらに関数の引数に直接ラムダ関数を指定することも可能なのです。

3.2.2 return文

fun文による関数定義では、関数の実行が完了するときには、必ずreturn文によって値を返さなければなりません。

return文で返すことのできる値は、数値、文字列、および関数(クロージャ)です。


a = {fun(x) return x*2}
b = {fun(x) return "value = "+x}
c = {fun(x) return {fun(y) return x+y}}

print a(100)
print b("abc")
print c(10)
print c(10)(20)

以下に上のプログラム例の実行結果を示します。


200
value = abc
closure4
30

関数c(10)の結果としてクロージャclosure4が返っていることに注目してください。

クロージャについては後の項で詳しく説明します。

次の関数c(10)(20)の結果は、c(10)で返ったクロージャに引数(20)を与えて評価することになります。 よって、cの関数の定義より、引数xに10, 引数yに20が与えられて、10+20の30が答として最終的に値が返されます。

関数のreturnは何重にも行うことが可能です。

例えば次のようなプログラムも可能です。


b = {fun(x) return {fun(y) return {fun(z) return x+y+z}}}

print b(1)(2)(3)

実行結果は、6になります。

3.2.2 call文・gosub文

fun文による関数定義では、関数は必ず返り値を返さなければなりません。

しかし、実際には返り値を使う必要がなく、サブルーチンとして関数を呼び出したいことがあります。

例えば、計算結果をprintするだけの関数があったとします。


double = {fun(x) print x*2: return 0}

上の例では返り値として0をリターンしていますが、結果はprintしているだけです。

このような場合にはダミーの変数に関数値を代入するようにしてもよいでしょう。


dummy = double(10)

しかし、返り値を必要としないということを明確に示すために、callまたはgosubの引数として関数を呼び出しても良いのです。


call double(10)

gosub double(10)

callとgosubはまったく同じ働きをします。 (callのほうが格好良い気がしますが、Closure BasicはBASIC言語のオマージュなのでgosubも捨てがたいのです。)

3.3. クロージャ

Closure Basicの関数はクロージャです。 クロージャはClosure Basicの最も特徴的な機能なのですが、もっとも説明が難しく悩ましいところです。

クロージャとは何でしょうか?

クロージャという言葉は調べてみるといろいろな意味で使われていることが判ります。 プログラミング言語によって意味合いが微妙に異なります。 また、クロージャの機能のうちどの部分に重点をおいているかも様々で、なにか煙にまかれているような気もしてきます。 そこで、自分の微力は承知のうえで、ここではClosure Basicでのクロージャについて説明しましょう。

Closure Basicでは、クロージャと関数は1対1で対応する同義のオブジェクトです。 関数が定義されるたびにそれはクロージャとなり、関数が実行されるときにはクロージャの内容にしたがって実行されます。

さて、クロージャは、日本語訳では閉包ともいいます。いったい何を包んで閉じているのでしょうか。

クロージャは大きく以下の3つを含んでいます


a) 実行コード
b) 引数変数
c) ローカル変数

実行コードは、関数内で実行される命令コードです。 引数を受け取り、演算処理を行い、結果を返す関数(クロージャ)の一連の命令コードを保持しています。

引数変数は、引数を代入するクロージャ内のローカル変数です。 同じローカル変数であっても、他のローカル変数とは異なり引数変数は特別扱いとなります。 引数変数は関数が呼ばれるたびに古い値を保存してから、新しい引数を設定します。 関数が復帰するときには、保存していた古い値に戻してから復帰します。 そう、引数変数はC言語やJAVAの引数変数(パラメター)と同様な動きをします。 リカーシブ(再帰)関数の処理を行うには、このような従来同様な引数の処理が欠かせません。

ローカル変数は、クロージャ内で定義された変数を表します。 ローカル変数は、クロージャ内およびクロージャ内でさらに定義されたクロージャからのみ参照や代入が行えます。 C言語やJAVAとは異なり、関数が復帰してもクロージャ内のローカル変数は保持され続けます。 このようにローカル変数が関数(クロージャ)の復帰後も保持されることがクロージャを特徴付けます。 クロージャ内のローカル変数は値を保持しつづけることは、クロージャの状態を保持する機能を持つことになります。 そのような状態を保持し続けるクロージャをさらに変数に代入したり、参照したり、別の関数に引数として渡すことが可能になり、 それがプログラムに柔軟性とポータビリティを与えます。

つまり、クロージャを最も特徴づけるのは、 クロージャ内の変数を環境として実行コードと一体で操作できることです。

3.3.1 クロージャの環境

クロージャの環境で参照できる変数には以下の3種類があります。


a) クロージャの実行時の外側にある変数

b) クロージャ内のローカル変数

c) クロージャの引数変数

a) クロージャの実行時の外側にある変数

クロージャの実行時の外側にある変数は、クロージャが呼び出されたときの外側に存在している変数です。 これはグローバル変数やこのクロージャを呼び出したクロージャの内部変数を含みます。

他のクロージャをサポートするプログラミング言語では、クロージャがコンパイルされたときの外側の変数の値を参照することが多いのですが、 Closure Basicでは、実行時のクロージャの外側に存在している変数を参照します。

Closure Basicでは、クロージャ内の中でクロージャを定義することが可能であり、さらにそのクロージャの中でクロージャを定義する ような階層的なクロージャを作成できます。階層の内側のクロージャからは、呼び出した階層内のクロージャのローカル変数を参照 したり値を代入することが可能です。


func1 = {fun() a = 1
           func2 = {fun () b = 2
                   func3 = {fun () d = b + a : return d}
                   return c}
           return a}}

上の例では、func3関数(クロージャ)の中で、func1とfunc2の変数a, bを参照しています。

b) クロージャ内のローカル変数

クロージャ内で始めて値を参照や代入した変数は、そのクロージャのローカル変数となります。

ローカル変数は、作成されたクロージャおよびそのクロージャ内で作成されたクロージャから下の階層のクロージャから参照することが出来ます。

また、ローカル変数はクロージャの実行が終了しても値が保持され、次にクロージャが呼び出されたときには、以前の値が参照できます。


inc = 0
func = {fun() 
          val = 0
          inc = {fun () val=val+1: return val}
          return 0
       }

call func()
print inc()
print inc()
print inc()

 

上の例では、func関数が呼ばれるとvalが0クリアされます。inc関数が呼ばれると、valの値が+1ずつ増えていきます。

実行結果をいかに示しましょう。


1
2
3

inc関数の結果が+1ずつ増えていきます。

c) クロージャの引数変数

引数変数は、クロージャの引数を代入するクロージャ内のローカル変数です。 引数変数は関数が呼ばれるたびに古い値をパラメター専用のスタックに値を保存してから、新しい引数を設定します。 関数が復帰するときには、パラメター専用のスタックに保存していた古い値に引数変数を戻してから復帰します。 引数変数は、スタックを用いてC言語やJAVAの引数と同様な動きをします。

3.3.2 クロージャの操作

Closure Basicではクロージャは、いわゆるファーストクラスオブジェクトです。

変数への代入や参照が出来ます。 クロージャは名無しの無名関数と環境の組み合わせをそのまま保持していて、そのまま変数へ代入することが出来ます。

関数引数へパラメターとして渡せます。 新たに名前つきの関数を定義することなく、直接クロージャの定義{fun()~}を引数として渡すことが出来ます。

関数からの戻り値と出来ます。 関数からの戻り値として、返されたクロージャを評価して実行したり、さらに別の変数に代入したり出来ます。

動的に生成できます。 また、クロージャは動的にいつでも生成できます。

クロージャは本来は無名なのですが、Closure Basic内では便宜的に、"closure番号"の名前が付いています。

クロージャを引数をつけず評価しないでprintするとその名前が文字列として表示されます。


print {fun() return 0}

実行結果


closure1

3.4. グローバル変数とローカル変数のスコープ

Closure Basicでは、関数内で初めて使われるローカル変数以外のトップレベルの変数はグローバル変数として すべての定義される関数(クロージャ)内から値を参照することが出来ます。

グローバル変数とするには、トップレベルで一度でも値を入れれば、それはグローバル変数となります。


foo = 1
bar = 2

func1 = {fun() var1 = foo
               func2 = {fun() var2 = var1 + bar}}

上の例では、fooとbarがグローバル変数であり、var1はfunc1のローカル変数で、var2はfunc2のローカル変数です。

var1は、トップレベルからは参照できませんが、ローカル関数func1内のさらなるローカル関数func2からは参照できます。