Top/マニュアル/文法/2.関数
  トップページへ   [ 一覧 | 検索 | 最終更新 ]   [ 差分 | 履歴 ]

  • 追加された行はこの色です。
  • 削除された行はこの色です。
#author("2021-12-08T19:32:50+09:00","","")
#author("2021-12-09T16:08:57+09:00","","")
#navi(マニュアル/文法)

#contents

*関数 [#r29732f5]

**基礎の基礎 [#p8147893]

YAYAにおける「関数」とは、
- 0個以上の引数を取り
- 0個以上の「出力候補」から1つの出力を選んで返す

処理の集まりです。

引数の数は、変数 _argc、各引数は、変数 _argv[n] に格納されています。~
nは0から始まる引数の番号です。

出力候補とは、関数内に直接書かれた文字列や数値、変数名など、左辺値(「=」の左側)が無いものの集まりです。~
複数の出力候補がある場合、YAYAはそのうちどれか一つを選んで関数の出力(返り値、戻り値)とします。~

また、同名の関数が複数存在するとエラーになります。(特に既存のゴーストをベースに改編する場合に注意)

次の「基礎」の章は、汎用言語DLLとしてのYAYAにとっての「関数」です。良く分からない場合は読み飛ばしてかまいません。


**基礎 [#r94d7456]
request実行時に"Hello World"という文字列を返すプログラムコードの全体を以下に示します。
#code(aya,nooutline,nolink,nonumber){{
request
{
    "Hello World"
}

}}
文をロードしたモジュールがHGLOBAL request(HGLOBAL h, long *len)を実行すると、このスクリプトが実行され、"Hello World"が
返されます。
loadとunloadも同様です。~
ただしloadとunloadは値を返さないので、出力文字列を書いても意味がありません。loadには変数に初期値を入れるといった初期化処理を、
unloadには逆にその後始末をするコードを書きます。
必要な関数だけ書けばよいです。不要なものは省けます。~
たとえば上の例ではloadとunloadは書いていませんが、これはエラーとはなりません。何もしないだけです。~
極端な例を挙げるなら、辞書ファイルがまったく無くてもエラーにはなりません。この場合loadとunloadは何もせず、
requestは空の文字列を返します。

loadとrequestはひとつの引数を持っています。これは変数で取り出せます。~
変数の名前は_argcと_argvで、これはC言語のmain関数のインタフェースと類似しています。

***_argc [#f09b1d74]

引数の数。loadとrequestでは引数はひとつの文字列ですから1となります。unloadは引数を持たないので、この値は0となります。

***_argv [#v1dd42c9]

引数の実体が格納されています。これは_argc個の要素を持つ配列です。各要素へのアクセスは演算子[i]で行います。~
序数iは0オリジン指定です。たとえば_argcが1の場合は_argv[0]が使用可能で、ここに引数が格納されていることになります。

基礎編のまとめとして、loadでは変数strに"Hello"を格納し、requestで引数として渡された文字列とstrを結合して返すプログラムコードを
示します。~これまでの説明を踏まえて読んでみてください。
#code(aya,nooutline,nolink,nonumber){{
load
{
    str = "Hello"
}

request
{
    str + " " + _argv[0] + "!"
}

}}
処理対象文字列を"World"としてrequestを実行すると、結果として"Hello World!"が得られます。

**書式 [#s79234f5]
以下の則があります。

-空行(改行のみの行)、"//"以降、および"/*"と"*/"で囲まれた領域はコメントと見なされます。
-1行の終わりがスラッシュ("/")の場合は、次の行がこの行の後ろに結合されます。
-行頭、および単語間には自由に空白文字を入れることが出来ます。空白文字は空白、およびタブ文字です。
-複数のステートメントを1行に詰めて書きたい場合は、セミコロン(";")で区切ることが出来ます。

つまり先に挙げたHello Worldコードは以下のように1行に詰めて書くことが出来ます。
#code(aya,nooutline,nolink,nonumber){{
request{"Hello World"}

}}
では以下のように書いてもいいのか? もちろん。問題なく動作します。(ただしこんな風に書くのは感心できない!)
#code(aya,nooutline,nolink,nonumber){{
req/
uest          {
"/
Hello World"  }

}}
/の次の行(新たに結合される行)先頭にある空白文字はインデント文字と見なされ、消されます。~
したがって以下の文字列の結合結果は "ABCDEFG" であり、決して "ABCD  EFG" ではありません。
#code(aya,nooutline,nolink,nonumber){{
"ABCD
    EFG"

}}

第4項を説明します。~
以下のrequestは1+2の答えを求め、日本語の文章にして返しています。
#code(aya,nooutline,nolink,nonumber){{
request
{
    answer = 1 + 2
    "答えは" + answer + "です。"
}

}}
セミコロンを使用して以下のように書けます。
#code(aya,nooutline,nolink,nonumber){{
request
{
    answer = 1 + 2; "答えは" + answer + "です。"
}
}}
セミコロンは過剰に書いても問題ありません。それらは無視され、動作に影響は与えません。~
したがって、あなたがC言語の書式に慣れているなら、各行の終端に必ずセミコロンを書くことができます。

**ユーザー関数の定義と実行 [#f5f27796]
load、unload、request以外に好きな名前の関数を作成できます。~
作成した関数は、その名前を書くだけで実行できます。
#code(aya,nooutline,nolink,nonumber){{
request
{
    hello
}

hello
{
    "Hello World"
}
}}
上に示したのはもっとも単純な例です。requestは結果として"Hello World"を返します。
同じ結果が得られるもう少し複雑な例を、以下に示します。
#code(aya,nooutline,nolink,nonumber){{
request
{
    combine("Hello", "World")
}

combine
{
    _argv[0] + " " + _argv[1]
}
}}
関数名の後ろに( )をつけて、その中にカンマで値を並べて書くと、これらは該関数に渡される引数として扱われます。
すなわち変数_argvと_argcに値が格納されて、該関数内で参照できるようになります。~
ここで挙げた例では、combine関数内において_argcは2、_argv[0]は"Hello"、_argv[1]は"World"となるわけです。
関数名は自由につけられますが、以下に抵触する名前はエラーとなります。

-数字0~9で始まる。
--定数の数字として解釈される可能性があります。
-アンダースコア("_")で始まる。
--アンダースコアで始まる名前はローカル変数で使われています。
-以下の文字を含む。~
空白 ! " # $ % & ( ) * + , - / : ; < = > ? @ [ ] ` { | } ~
--アンダースコア以外の記号は使わないほうが良いでしょう。
-予約語と完全に一致する。
--アルファベット大文字+アンダースコアはシステム関数で使われます。
--forやwhile、ifなど、制御構造に使われるものも避けなければいけません。


関数は再帰呼び出しが可能です。~
もっともありふれた例として階乗計算を行った例を挙げます。
#code(aya,nooutline,nolink,nonumber){{
request
{
    factorial(5)
}

factorial
{
    if !_argv[0]
        1
    else
        factorial(_argv[0] - 1)*_argv[0]
}
}}

requestは120を返します。


**択一 [#vce5bec3]

#code(aya,nooutline,nolink,nonumber){{
request
{
    "Hello World"
    "こんにちは世界"
    "Hallo Welt"
}
}}

このように列挙すると、これらは平等な「出力候補」として扱われ、出力はこれらのうちのいずれかひとつになります。
下記のいずれかを選ぶことができます。ただし、voidとarray / pool_arrayは特殊な用途のみで使用します。

poolがらみはTc563-1以降での実装になります。

***指定なし / random / pool [#c8db39e4]
***指定なし / random[#c8db39e4]
デフォルトでは無作為に選択します。~
randomと書いても同じ動きになります。

poolは下記のようなコードにおいて…
***nonoverlap[#y4e0c799]

できるだけ直前と同じ候補を選択しなくなります。
#code(aya,nooutline,nolink,nonumber){{
request : nonoverlap
{
    "Hello World"
    if 1 {
        "こんにちは世界"
        "Hallo Welt"
    }
    "こんにちは世界"
    "Hallo Welt"
}
}}
中括弧に囲まれた部分は本来random選択された後で候補に加えられますが、pool指定では囲まれない部分と同列に扱うことができます。~
結果として、選択確率は以下のようになります。
-random
--Hello World 50%
--こんにちは世界 25%
--Hallo Welt 25%
-pool
--Hello World 33.3%
--こんにちは世界 33.3%
--Hallo Welt 33.3%
上のように、関数名の後ろに ": nonoverlap" を付け加えます。

以降の nonoverlap_pool / sequential_pool / pool_array も考え方は同じになります。

***nonoverlap / nonoverlap_pool [#y4e0c799]
関数によっては実行毎に出力候補の数が変動します。~たとえば以下の関数は、変数iの値によって候補数は2個もしくは4個に変化します。

できるだけ直前と同じ候補を選択しなくなります。
#code(aya,nooutline,nolink,nonumber){{
request : nonoverlap
request : sequential
{
    "Hello World"
    "こんにちは世界"
    "Hallo Welt"
    if i {
        "1"
        "2"
    }
    "3"
    "4"
}
}}
上のように、関数名の後ろに ": nonoverlap" か ": nonoverlap_pool" を付け加えます。

***sequential / sequential_pool [#m0d8b091]
候補数が変化した場合、nonoverlapとsequential(後述)の巡回順序は初期化され、最初からやり直しになります。この時に限って、前回と同じ値が重複して
出力されることはあり得ます。


***sequential[#m0d8b091]
記述された順に出力します。最後まで出力したらまた先頭に戻ります。
#code(aya,nooutline,nolink,nonumber){{
request : sequential
{
    "Hello World"
    "こんにちは世界"
    "Hallo Welt"
}
}}
上のように、関数名の後ろに ": sequential" か ": sequential_pool" を付け加えます。
上のように、関数名の後ろに ": sequential" を付け加えます。

***void [#sd2e95a2]

***array[#b6965d54]
出力候補をすべて結合した汎用配列を関数の返値とします。~
#code(aya,nooutline,nolink,nonumber){{
request : array
{
  "This is a pen."
  ("A","B","C")
  3.14
}
}}

出力は汎用配列 ("This is a pen.", "A", "B", "C", 3.14) となります。




***void[#sd2e95a2]
何も出力しなくなります。~
以下の例の場合、requestは3つの候補のどれも出力しません。
#code(aya,nooutline,nolink,nonumber){{
request : void
{
    "Hello World"
    "こんにちは世界"
    "Hallo Welt"
}
}}

「何も実行しない」ではなく、「何も出力しない」であることに注意してください。~
内部に書かれた関数や数式は処理されます。

#code(aya,nooutline,nolink,nonumber){{
increment_i : void
{
    i++
    i
}
}}

上の関数increment_iはiに1を加算します。voidが無い場合、この関数は加算結果を返しますが、voidを指定すると値を返さず、
ただ加算を行うのみとなります。

***array / array_pool [#b6965d54]
出力候補をすべて結合した汎用配列を関数の返値とします。~
#code(aya,nooutline,nolink,nonumber){{
request : array
{
  "This is a pen."
  ("A","B","C")
  3.14
}
}}
***all[#z4948755]

出力は汎用配列 ("This is a pen.", "A", "B", "C", 3.14) となります。
Tc567-1で実装

nonoverlap・sequential・arrayとそのpool版は、出力確定子が存在する場合でも取り得るすべての組み合わせに対して正常に機能します。
選択候補すべてを1つの文字列に結合します。

たとえばsequentialは、
#code(aya,nooutline,nolink,nonumber){{
request : sequential
{
    "1"
    "2"
    "3"
    --
    "A"
    "B"
}
}}
requestは以下の順序で出力を発生します。
***last[#k507b16b]

#code(aya,nooutline,nolink,nonumber){{
"1A" "2A" "3A" "1B" "2B" "3B" "1A" "2A" …
}}
Tc567-1で実装

選択候補の中から、一番最後の候補を出力として選択します。

たとえばarrayは、
#code(aya,nooutline,nolink,nonumber){{
request : array
{
    "1"
    "2"
    "3"
    --
    "A"
    "B"
}
}}
***_pool 修飾子 [#v7563922]

上記関数は、 ("1A","1B","2A","2B","3A","3B") という汎用配列を出力します。
Tc563-1で実装

関数によっては実行毎に出力候補の数が変動します。~たとえば以下の関数は、変数iの値によって候補数は2個もしくは4個に変化します。

択一指定の後ろに_poolを加えると、下記のようなコードにおいて…
#code(aya,nooutline,nolink,nonumber){{
request : sequential
request : random //または random_pool (poolと書くだけでも可)
{
    if i {
        "1"
        "2"
    "Hello World"
    if 1 {
        "こんにちは世界"
        "Hallo Welt"
    }
    "3"
    "4"
}
}}
中括弧に囲まれた部分は本来random指定として選択された後で候補に加えられますが、random_pool指定では囲まれない部分と同列に扱うことができます。~
結果として、選択確率は以下のようになります。
-pool指定なし
--Hello World 50%
--こんにちは世界 25%
--Hallo Welt 25%
-pool指定あり
--Hello World 33.3%
--こんにちは世界 33.3%
--Hallo Welt 33.3%

候補数が変化した場合、nonoverlapとsequentialの巡回順序は初期化され、最初からやり直しになります。この時に限って、前回と同じ値が重複して
出力されることはあり得ます。
nonoverlap_pool、sequential_pool、array_poolなどのようにも書けます。

***melt_ 修飾子[#jb7be704]

Tc567-1で実装

択一指定の前に melt_ を加えると、[[汎用配列のパラレル出力>マニュアル/文法/5.配列#j8feade9]]と同じ効果になります。

例:melt_array

主に下記の「入れ子」で使います。


**入れ子 [#icb3830b]
#code(aya,nooutline,nolink,nonumber){{
request
{
    {
        "Hello World"
        "こんにちは世界"
    }
    
    "Hallo Welt"
}
}}

{ } は階層的にいくつでも重ねて書くことが出来ます。動きは最上層の{ }と同じです。すなわち、包含される候補からひとつを選択して出力します。~
ただし選択方法は無作為抽出のみで、最上層のようにnonoverlapやsequentialの指定はできません。
上の例では、{ }の有無にかかわらず結果は同じであるように思われるかもしれませんが、実はそうではありません。~
{ }が無い場合、それぞれが出力される確率は平等に1/3です。しかし上の例の場合、まず"Hello World"と"こんにちは世界"からひとつが選ばれ、次いでその結果と残った"Hallo Welt"から出力が抽出されます。すなわち、出現率は"Hello World"と"こんにちは世界"が1/4、"Hallo Welt"は1/2となるのです。

***入れ子内の択一指定 (Tc564-1) [#d84d9a46]
#code(aya,nooutline,nolink,nonumber){{
request
{
    nonoverlap : {
        "Hello World"
        "こんにちは世界"
    }
    
    "Hallo Welt"
}
}}

以上のように、入れ子それぞれに前項の択一指定を追加できます。

if文などの直後に続く { } に指定するには、";"で区切って以下のようにするか、改行を追加します。

#code(aya,nooutline,nolink,nonumber){{
request
{
    if 1 ; nonoverlap : {
        "Hello World"
        "こんにちは世界"
    }
    
    "Hallo Welt"
}
}}

#code(aya,nooutline,nolink,nonumber){{
request
{
    if 1
    nonoverlap : {
        "Hello World"
        "こんにちは世界"
    }
    
    "Hallo Welt"
}
}}

**出力確定子 [#bc934c88]
#code(aya,nooutline,nolink,nonumber){{
request
{
    "Hello"
    "Perfect"
    "Peaceful"
    --
    " Wor"
    --
    "ld"
    "th"
}
}}

&#8208;&#8208;は出力確定子というもので、選択候補はここを区切りとしてグループ分けされます。そして、グループごとに選ばれた結果がひとつの文字列に結合されます。~
したがって上のrequestを実行すると、以下のいずれかが出力されることになります。
#code(aya,nooutline,nolink,nonumber){{
"Hello World"
"Perfect World"
"Peaceful World"
"Hello Worth"
"Perfect Worth"
"Peaceful Worth"
}}

nonoverlap、sequential、arrayと組み合わせて使用した場合、(グループ単位ではなく)関数が取り得る全ての組み合わせに対して動作します。
出力確定子はどこでも問題なく使用できます。{ }入れ子の深い位置でも使えます。

たとえばsequentialは、
#code(aya,nooutline,nolink,nonumber){{
request : sequential
{
    "1"
    "2"
    "3"
    --
    "A"
    "B"
}
}}
requestは以下の順序で出力を発生します。

#code(aya,nooutline,nolink,nonumber){{
"1A" "2A" "3A" "1B" "2B" "3B" "1A" "2A" …
}}


たとえばarrayは、
#code(aya,nooutline,nolink,nonumber){{
request : array
{
    "1"
    "2"
    "3"
    --
    "A"
    "B"
}
}}

上記関数は、 ("1A","1B","2A","2B","3A","3B") という汎用配列を出力します。

文は文字列のほかに数値なども扱えますが、出力確定子は結合する際にそれらをすべて文字列に変換し、文字列として結合します。

#navi(マニュアル/文法)