Edited at

HSPのモジュール型変数とその配列について

モジュール型変数でオブジェクトっぽい処理をさせようと思って勉強したメモです。

環境は HSP3.5, windows 7 personal 64bit です。


言葉の定義

モジュール型変数は、 newmod で作られた変数のことです。

モジュール変数は、下記コードでいうと

#module Data1 name_, id_

の name_ と id_ になります。

C++などのメンバ変数に似ていますが、アクセス修飾子 public のようなものはなく外部から直接アクセスはできません。

Data1 は C++ などのクラス名のようなものです。

モジュール型変数やモジュール変数などの言葉の定義や基本的な使い方は HSPでモジュール型変数入門 - Qiita を参考にさせてもらいました。

あと、クローンについては個人的に違和感を感じます。

クローンは複製であり、深いコピーにより実体もそれぞれにあると思うのですが、 HSP の dup や varuse などの説明では、同じ実体を指すポインタのように思えます。

しかし、 HSP ではクローンは浅いコピーであり、深いコピーではないということを注意したいと思います。


モジュール型変数の代入は浅いコピー(実体は同じ)

以下のコードで検証しました。

#module ModA num_

#modfunc local setNum int _num
num_ = _num
return
#modcfunc local getNum
return num_
#global

// --- モジュール型変数を代入すると浅いコピー(実体は同じ)になるかのテスト ---
newmod _a1, ModA
setNum@ModA _a1, 1
logmes "[1] _a1.num_ = " + getNum@ModA(_a1)

// 代入した後に、代入先の _a2 で値を変更する
_a2 = _a1
setNum@ModA _a2, 2

logmes "[2] _a1.num_ = " + getNum@ModA(_a1)
logmes "[3] _a2.num_ = " + getNum@ModA(_a2)

以下のログ出力を見ると、_a2 = _a1 で代入をしたあと、代入先 _a2 でモジュール変数を変更するとどちらも同じく変更されています。

これは代入が浅いコピーであり、実体は同じであることを指します。

int などの変数を dup したものと同じ扱いです。

[1] _a1.num_ = 1

[2] _a1.num_ = 2
[3] _a2.num_ = 2

確認はしていませんが、モジュール型変数に対する dup は不安定らしく、浅いコピーを行うならば今のように代入のほうがおすすめらしいです。

参照:モジュール型変数 参照コピー - HSPTV!掲示板


モジュール型変数のディープコピーは可能なのか

新たに同じモジュール型で newmod して、モジュール変数の値をコピーする必要があるようです。

モジュール変数にさらにモジュール型変数があれば、それも int, str, double の代入のレベルまでコピーを繰り返す必要があるようです。

モジュール変数はprivateなので、アクセスする関数や全ての値をコピーする clone 命令のようなものをモジュール型ごとに作る必要があるでしょう。

参照:モジュール型変数 参照コピー - HSPTV!掲示板


モジュール型変数のクローン(浅いコピー)の delmod の挙動

モジュール型変数 _a2 = _a1 のように浅いコピーをして、 delmod _a2 をしてもデストラクタのような modterm は呼ばれません。

実体である _a1 を delmod _a1 すると modterm が呼ばれて実体は破棄されます。

しかし、その後 veruse をみると _a2 はクローンである 2 が返され、実体が破棄されたことが反映されていません。

#module ModA name_

#modinit str _name
name_ = _name
logmes "modinit " + getName(thismod)
return
#modterm
logmes "modterm " + getName(thismod)
return

#modfunc local setName str _name
name_ = _name
return
#modcfunc local getName
return name_
#global

newmod _a1, ModA, "foo"
_a2 = _a1

delmod _a2 // 無効
logmes "varuse(_a1) = " + varuse(_a1)
logmes "varuse(_a2) = " + varuse(_a2)
logmes "getName@ModA(_a1) = " + getName@ModA(_a1)
logmes "getName@ModA(_a2) = " + getName@ModA(_a2)

delmod _a1 // 有効
logmes "varuse(_a1) = " + varuse(_a1) // delmod されたので 0 になる
logmes "varuse(_a2) = " + varuse(_a2) // _a1 と連動せず変数のクローンである 2 が表示される
;logmes "getName@ModA(_a1) = " + getName@ModA(_a1) // Error 36 「モジュール変数の指定が無効です」
logmes "getName@ModA(_a2) = " + getName@ModA(_a2)

setName@ModA _a2, "bar" // この呼び出し先の name_ = _name で Error 1 「システムエラーが発生しました」
logmes "getName@ModA(_a2) = " + getName@ModA(_a2)

出力ログは以下のとおりです。

modinit foo

varuse(_a1) = 1
varuse(_a2) = 2
getName@ModA(_a1) = foo
getName@ModA(_a2) = foo
modterm foo
varuse(_a1) = 0
varuse(_a2) = 2
getName@ModA(_a2) = foo

delmod した直後から _a1 は varuse(_a1) = 0 で使用できないため、 _a1 を使った呼び出しはエラーになります。

しかし、変数のクローン _a2 は varuse(_a2) = 2 でまだ呼び出すことができます。

delmod しても開放された領域に値は残っていて、 getName で値を読むことはできましたが、解放された領域なのでいつまで残っているかはわからないと思います。

また、 setName のように値を書き込む処理はシステムエラーになりました。

delmod すれば _a1 と _a2 の実体は破棄されているようですが、_a1 変数のクローンである _a2 はそれを varuse で知ることはできません。破棄する場合は、その変数のクローンも 0 などを代入して無効にしたほうがよいと思います。


モジュール型変数配列にモジュール型変数の浅いコピーを混在させられるか

モジュール型変数配列を newmod を複数しようすることで作ることができます。

その途中で、別のモジュール型変数(モジュール型は同じ)の浅いコピーを要素の最後に代入します。

この場合、次の newmod で、浅いコピーも実体と同じように扱い、浅いコピーの次の要素にモジュール型変数が配置されることを以下のコードで確認しました。

#cmpopt varinit 1

#module ModA name_
#modinit str _name
name_ = _name
return
#modterm
logmes "modterm " + name_
return

#modcfunc local getName
return name_
#modfunc local setName str _name
name_ = _name
return
#global

_modaListFoo = 0
_modaListBar = 0

logmes "--- test1 参照を含めた後にモジュール型変数配列はその後に追加されるか---
newmod _modaListFoo, ModA, "foo1"
newmod _modaListFoo, ModA, "foo2"

newmod _modaListBar, ModA, "bar1"
_modaListFoo(length(_modaListFoo)) = _modaListBar(0) // 浅いコピーをモジュール型変数配列に設定

newmod _modaListFoo, ModA, "foo3"

foreach _modaListFoo
logmes "(" + cnt + ") name = " + getName@ModA(_modaListFoo(cnt))
loop

logmes "--- test2 参照元と実体が同じか確認---
setName@ModA _modaListBar(0), "bar2" // 参照元のモジュール変数を変更
foreach _modaListFoo
logmes "(" + cnt + ") name = " + getName@ModA(_modaListFoo(cnt))
loop

logmes "--- test2 参照元を破棄しても浅いコピーには反映されない---
delmod _modaListBar(0) // 参照元を破棄
foreach _modaListFoo
logmes "(" + cnt + ") name = " + getName@ModA(_modaListFoo(cnt))
loop

// #error 36 line 14 (破棄されたモジュール型変数のモジュール変数にアクセスしようとするとエラーが発生)
;setName@ModA _modaListBar(0), "bar3" // ただし、浅いコピーから破棄した実体へアクセスするとエラーになる。

結果のログは以下のとおりです。

--- test1 参照を含めた後にモジュール型変数配列はその後に追加されるか---

(0) name = foo1
(1) name = foo2
(2) name = bar1
(3) name = foo3
--- test2 参照元と実体が同じか確認---
(0) name = foo1
(1) name = foo2
(2) name = bar2
(3) name = foo3
--- test2 参照元を破棄しても浅いコピーには反映されない---
modterm bar2
(0) name = foo1
(1) name = foo2
(2) name = bar2
(3) name = foo3

foo のリストの途中(2)で bar 変数のモジュール型変数の浅いコピーをいれたあとに、 newmod をすると (2) のあとの (3) に新たにモジュール型変数が作成されていることが確認できます。


モジュールの定義の注意点


  • newmod するモジュールはモジュール変数を1個以上持たないとエラー( 「error 28 : モジュール変数の指定が無効です 」)になります。

  • modfunc の呼び出しでは、 local がついているものは @ModA のように呼び出す命令の後にモジュール名を指定する必要があります。逆にlocalがついていない modfunc では @ModA をつけません。それぞれ違反するとエラー「error 2 : 文法が間違っています」になります。

#module ModA name_

#modinit str _name
mes "init"
name_ = _name
return

#modterm
mes "Bye!"
return

#modfunc greetEn
mes "Hello! " + name_
return

#modfunc local greetJp
mes "こんにちは! " + name_
return
#global

newmod _ma1, ModA, "HSP"
greetEn _ma1 // @ModA はつけない
greetJp@ModA _ma1 // @ModA をつける

2018-08-13_120430.png


モジュール型変数の作成・呼び出し・取得(ポインタ)・破棄のコード確認


  • モジュール型変数の作成・関数呼び出し

  • 別のモジュール型変数の内部での、モジュール型変数の作成・保持・関数呼び出し

  • モジュール型変数を命令・関数で取得する

  • モジュール変数の値の変更

  • 代入したモジュール型変数がコピーではなくポインタ(実体を共有している)であることの確認

  • モジュール型変数が破棄されるときのイベントについての確認


StudyModule.hsp

#module Data1 name_, id_

#modinit str _name, int _id
name_ = _name
id_ = _id
return
#modterm
mes "Bye! Data1 : name_ = " + name_ + ", id_ = " + id_
return

#modfunc MesData
mes getString(thismod)
return

#modcfunc GetString
return "Data1 : name_ = " + name_ + ", id_ = " + id_

#modcfunc GetName
return name_

#modfunc SetName str _name
name_ = _name
return

#modcfunc GetId
return id_

#modfunc SetId int _id
id_ = _id
return
#global

mes "(1)モジュール型変数を global で作成して、中身を表示するモジュール関数を呼び出す"
newmod d1, Data1, "hoge", 0
newmod d2, Data1, "piyo", 1

MesData d1
MesData d2
mes "---"

#module DataManager1 data1Array_
#modinit
mes "Init DataManager1"
return

#modterm
mes "Bye! DataManager1"
return

#modfunc AddData1 str _name, int _id
newmod data1Array_, Data1, _name, _id
return

#modfunc mesAllData
foreach data1Array_
mes "DataManager1[" + cnt + "] = " + GetString(data1Array_(cnt))
loop
return

#modfunc local GetData1 int _idx, var _ret
_ret = data1Array_(_idx)
return

#global

mes "(2)他のモジュール型変数の内部でモジュール型変数を作成・保持させる"
newmod dm1, DataManager1
AddData1 dm1, "foo", 100
AddData1 dm1, "bar", 101
mesAllData dm1
mes "---"

mes "(3)モジュール型変数を他のモジュール型変数の内部から取得"
GetData1@DataManager1 dm1, 0, d3
MesData d3
mes "---"

mes "(4)取得したモジュール型変数の値を変更"
SetId d3, -100
MesData d3
mes "---"

mes "(5)取得元のモジュール型変数の値が変化しているか確認"
mesAllData dm1
mes "---"

mes "(6)delmod を呼ばないで終了すると #modterm は呼び出されない"
//stop
mes "---"

mes "(7)DataManager1 を破棄したら持っている Data1 は破棄されるか確認"
delmod dm1
mes "---"


結果は次のようになりました。

StudyModule0813.png

(1) global 領域で Data1 モジュール型変数を複数作成し、それぞれの内容をモジュール関数を呼び出して表示できました。

(2) DataManager1 モジュール型変数 の内部で先ほどのモジュール型変数 Data1 を複数作成できました。

(3) DataManager1 モジュール型変数の内部の Data1 モジュール型変数を取得できました。

  return の戻り値は int, double, str のいずれかの型しか返せないためエラーになります。

  そのため、呼び出しの際に受け皿となる変数を var 引数として渡し、それに代入してもらい取得しています。

   参照:hspで、ユーザー定義関数の戻り値に、配列を返すことはできますか?... - Yahoo!知恵袋

(4) DataManager1 モジュール型変数から取得した Data1 モジュール型変数を使ってモジュール関数を呼び出せました。

  取得した Data1 モジュール型変数がコピーなのかポインタなのかを後で調べるため、モジュール変数を書き換えました。

(5) DataManager1 モジュール型変数の内部の Data1 をすべて表示しました。

  (4)で行った変更が反映されています。これにより (3) で DataManager1 モジュール型変数から取得した Data1 モジュール型変数は、ポインタであり、 DataManager1 モジュール型変数の内部の Data1 モジュール型変数と同一であることが確認できました。

(6) モジュール型変数は #modinit, #modterm で作成時・破棄時のイベント処理を定義できます。

  newmod 呼び出しで #modinit が呼び出されるます。

  delmod 呼び出しで #modterm が呼び出されます。

  delmod を呼ばずに終了したり stop が呼び出されると #modterm は呼び出されませんでした。

(7) DataManager1 モジュール型変数を破棄(delmod)する際に、モジュール変数で保持する Data1 モジュール型変数の #modterm が呼び出されることが確認できました。


モジュール型変数配列の要素をdelmodした後の配列について

delmod すると配列の要素数は減るのか試したところ、要素数は変わりませんでした。

空の要素として配列に残っていて、次の newmod では要素番号の小さいほうにある空の要素の部分が再利用されます。

また、配列の全ての要素を delmod しても要素数は変わらず、全てが空のモジュール型変数配列として残ります。

空の要素のmodfuncなどを使おうとするとエラーになるので、空の要素をとばしてくれる foreach などを利用して処理するのがよいでしょう。

モジュール型変数配列の foreach については「HSP のループ処理について - Qiita」をよろしければご覧ください。


検証コード1

前述した delmod しても要素数が変わらないことや、 newmod すると空の要素に割り当てられることを次のコードで検証しました。

#module Mod name_

#modinit str _name
name_ = _name
logmes "modinit " + serialize(thismod)
return

#modterm
logmes "modterm " + serialize(thismod)
return

#modcfunc getName
return name_

#modcfunc local serialize
return "Mod," + name_
#global

// 要素を全て削除したモジュール型配列に newmod した際の、要素数や割り当てられる要素番号についてのテスト
#module
#deffunc test1
// 2個の要素を newmod してから、2個全て delmod
newmod _modArray, Mod, "test1-1"
newmod _modArray, Mod, "test1-2"
delmod _modArray(0)
delmod _modArray(1)
logmes "length(_modArray) = " + length(_modArray) // 要素数は 2 なので、 0, 1 番の要素は delmod 済みのものが割り当てられている

// 要素追加
newmod _modArray, Mod, "test1-3"
foreach _modArray
logmes "(" + cnt + ")" + getName(_modArray(cnt)) // "test1-3" は 0 番目に割り当てられている。
loop
logmes "length(_modArray) = " + length(_modArray) // 要素数はさきほどと変わらず 2 なので、 1 番には delmod 済みのものが割り当てられている。

// 要素追加
newmod _modArray, Mod, "test1-4"
foreach _modArray
logmes "(" + cnt + ")" + getName(_modArray(cnt)) // "test1-4" は 1 番目に割り当てられている。
loop
logmes "length(_modArray) = " + length(_modArray) // 要素数はさきほどと変わらず 2 で、 (0)="test1-3", (1)="test1-4" が割り当てられている。

return
#global

test1

このプログラムでは Mod というモジュール型変数を 2 個 newmod してから全部 delmod して、その後さらに newmod を2回しています。

結果は次のようにログに出力されました。

modinit Mod,test1-1

modinit Mod,test1-2
modterm Mod,test1-1
modterm Mod,test1-2
length(_modArray) = 2
modinit Mod,test1-3
(0)test1-3
length(_modArray) = 2
modinit Mod,test1-4
(0)test1-3
(1)test1-4
length(_modArray) = 2

delmod するとデストラクタのような役割の modterm が呼ばれています。

その後要素数を見ても 2 から 0 に減ってはいません。

その後の newmod では、0番の空き要素に最初に割り当てられ、その後 1 番の空き要素に割り当てられています。

newmod は単純な追加ではなく、 delmod による空き要素があればそこを先に使うことがわかりました。

他の配列と連動させようとするときには、delmod をしたあとは newmod が追加の要素にはならないことに注意しなければいけません。


検証コード2

検証コード1と似ていますが、今回は全ての要素を delmod してから newmod するのではなく、4つの要素のうち (1) と (3) だけ delmod して試しました。

#module Mod name_

#modinit str _name
name_ = _name
logmes "modinit " + serialize(thismod)
return

#modterm
logmes "modterm " + serialize(thismod)
return

#modcfunc getName
return name_

#modcfunc local serialize
return "Mod," + name_
#global

// 途中の要素を delmod で消した場合、次の newmod はどの要素番号に割り当てられるかのテスト
#module
#deffunc test2
logmes "--- 初期の配列の作成 ---"
newmod _modArray, Mod, "test2-1"
newmod _modArray, Mod, "test2-2"
newmod _modArray, Mod, "test2-3"
newmod _modArray, Mod, "test2-4"
delmod _modArray(1) // "test2-2" を破棄
delmod _modArray(3) // "test2-4" を破棄

// モジュール型変数の配列の中身の確認
logmes "--- 配列の要素を表示(欠番はdelmodされた要素) ---"
foreach _modArray
logmes "(" + cnt + ")" + getName(_modArray(cnt)) // "test2-1", "test2-3" だけ表示される
loop
logmes "length(_modArray) = " + length(_modArray) // 2つ delmod したけど、要素数は 4 で変わらない

// 要素追加
logmes "--- 要素を追加したあとの表示(欠番に割り当てられる) ---"
newmod _modArray, Mod, "test2-5"
foreach _modArray
logmes "(" + cnt + ")" + getName(_modArray(cnt)) // newmod("test2-5") は delmod された要素 2 つの中で要素番号が小さいほうにされた。
loop
logmes "length(_modArray) = " + length(_modArray) // delmod された要素にいれられたので、要素数は 4 で変わらない
return
#global

test2

結果のログは以下です。

--- 初期の配列の作成 ---

modinit Mod,test2-1
modinit Mod,test2-2
modinit Mod,test2-3
modinit Mod,test2-4
modterm Mod,test2-2
modterm Mod,test2-4
--- 配列の要素を表示(欠番はdelmodされた要素) ---
(0)test2-1
(2)test2-3
length(_modArray) = 4
--- 要素を追加したあとの表示(欠番に割り当てられる) ---
modinit Mod,test2-5
(0)test2-1
(1)test2-5
(2)test2-3
length(_modArray) = 4

一部の要素を不連続に delmod した場合も、小さい要素番号の空き要素から順に newmod で使用されていることが確認できました。


モジュール型変数の配列は1個でも配列としてアクセスできる

結果として1個だけでも配列としてアクセスできました。

次のコードで確認しました。

#module Mod name_

#modinit str _name
name_ = _name
logmes "modinit " + serialize(thismod)
return

#modterm
logmes "modterm " + serialize(thismod)
return

#modcfunc getName
return name_

#modcfunc local serialize
return "Mod," + name_
#global

// モジュール型変数配列は1個だけでも配列としてアクセスできるのかのテスト
#module
#deffunc test3
newmod _mod, Mod, "test1-1"
logmes "length(_mod) = " + length(_mod)
logmes "getName(0) = " + getName(_mod(cnt)) // local ではない関数はモジュール名 @Mod をつけるとエラー
foreach _mod
logmes "(" + cnt + ")" + serialize@Mod(_mod(cnt)) // local の関数はモジュール名 @Mod をつけないとエラー
loop
return
#global

test3

ログ出力の結果は以下です。

modinit Mod,test1-1

length(_mod) = 1
getName(0) = test1-1
(0)Mod,test1-1

普通に1回だけの newmod でも配列としてアクセスできます。


モジュール型変数の状態をvaruse関数で調べる

モジュール型変数は delmod されたりして無効になっていると modfunc などの呼び出しでエラーが発生します。

これを避けるため、varuse関数で null チェックのようなことができます。

次のコードでテストしました。

#module Mod name_

#modinit str _name
name_ = _name
logmes "modinit " + serialize(thismod)
return

#modterm
logmes "modterm " + serialize(thismod)
return

#modcfunc getName
return name_

#modcfunc local serialize
return "Mod," + name_
#global

// モジュール型変数配列の要素が未使用か初期化済みかをvaruse関数で判定するテスト
#module
#deffunc test4
newmod _modArray, Mod, "test4-1"
newmod _modArray, Mod, "test4-2"
newmod _modArray, Mod, "test4-3"
delmod _modArray(1) // "test4-2" を破棄

logmes "--- 配列内のモジュール型変数の使用状況 ---"
for i, 0, length(_modArray)
varStat = varuse(_modArray(i))
if varStat = 0 {
logmes "(" + i + ") varuse = " + varStat + " = 未使用"
} else {
logmes "(" + i + ") varuse = " + varStat + " = 初期化済み, " + serialize@Mod(_modArray(i))
}
next

//logmes "--- newmod されていない変数の使用状況 ---"
//_var1 = 0
//varStat = varuse(_var1) // Error6 : パラメータの型が違います。
//logmes "varuse(_var1) = " + varStat
return
#global

test4

結果のログは以下です。

modinit Mod,test4-1

modinit Mod,test4-2
modinit Mod,test4-3
modterm Mod,test4-2
--- 配列内のモジュール型変数の使用状況 ---
(0)Mod,test4-1
(1)未使用
(2)Mod,test4-3

これにより、delmod したモジュール型変数であるかどうかを確認することができます。

配列要素の場合は、小さい要素番号から varuse で確認をして未使用の部分があれば、次の newmod がその未使用の要素番号に作成されることも推定できます。

これについては、前述の「モジュール型変数配列の要素をdelmodした後の配列について」も参照してください。


モジュール型変数配列を引数で渡すときは var / array どちらがよいのか。

var 型は要素をそのまま渡すことができ、 array 型は要素で指定してもその配列自体が渡されることがわかりました。

例えば、渡す引数が _mods(1) だとして、 var で受け取ると _mods(1) という要素が渡されますが、 array で受け取ると要素番号が無視され _mods が渡されます。

これだけをみると、 var 型のほうが使い勝手がよさそうですが、 array のほうが良い点もあります。

それは引数で渡したモジュール型変数配列に newmod をして、さらに操作をしたい場合です。

newmod は最初はモジュール型変数を作り、2回目は最初の要素のあとに要素を追加したモジュール型変数配列を作ります。

このとき、追加したモジュール型変数配列にアクセスするには _mods(1) のように要素番号を指定する必要があります。

しかし、_mods のような引数が var 型の場合は (1) とした時点で

「Error 34 --> 配列・関数として使用できない型です」

のエラーが発生します。

var 型はリファレンスにあるように配列ではない変数の型です。

当然といえば当然ですが、配列ではない変数なのに配列として扱えばエラーが起きます。

検証コードは以下です。

#module Mod name_

#modinit str _name
name_ = _name
return
#modcfunc local getName
return name_
#global

#module
#deffunc funcEr var _modAry, str _name, local idx
newmod _modAry, Mod, _name
idx = length(_modAry) - 1
logmes getName@Mod(_modAry(idx)) // Error 34
return

#deffunc funcOk array _modAry, str _name, local idx
newmod _modAry, Mod, _name
idx = length(_modAry) - 1
logmes getName@Mod(_modAry(idx))
return
#global
funcOk _modAry1, "Ok1": funcOk _modAry1, "Ok2"
FuncEr _modAry2, "Er1": FuncEr _modAry2, "Er2"

ログは以下のとおりです。 array 型では、newmod で追加されたモジュール変数にも正しくアクセスできています。

var 型の引数の関数のほうでは、 _modAry(idx) で Error 34 が発生しました。

Ok1

Ok2


モジュール型変数配列はいろんなモジュール型変数を格納できる

同じ変数に newmod を繰り返すとモジュール型変数の配列として追加されていきます。

このときに指定するモジュール型は同じでなくても良いようです。

ただし、配列にさまざまなモジュール型変数をいれたとして、それをどのように扱えばよいのでしょうか。

モジュール変数はprivateですし、同じ名前の関数・命令は local にするので func@moduleName のようにモジュール名が必要になります。

そこでひとつ考えたのが、モジュール型変数配列の並びと同じ int 型の配列を用意し、どのモジュールかを ID として記録しておく方法です。

少し面倒ですが、さまざまな種類のモジュール型変数をひとつの配列に格納しておけるメリットがあります。

検証コードは以下です。

#module ModGreetEn name_

#const CLASSID 1
#modinit str _name
name_ = _name
return

#modfunc local greet
logmes "Hello " + name_
return
#global

#module ModGreetJp name_
#const CLASSID 2
#modinit str _name
name_ = _name
return

#modfunc local greet
logmes "こんにちは " + name_
return
#global

newmod greetMods, ModGreetEn, "hoge"
modTypes(0) = CLASSID@ModGreetEn
newmod greetMods, ModGreetJp, "piyo"
modTypes(1) = CLASSID@ModGreetJp

foreach greetMods
;greet@??? greetMods(cnt) // モジュール名の識別子がないと関数が呼び出せない。
if modTypes(cnt) == CLASSID@ModGreetEn {
greet@ModGreetEn greetMods(cnt)
}
if modTypes(cnt) == CLASSID@ModGreetJp {
greet@ModGreetJp greetMods(cnt)
}
loop

ログ出力は以下です。

Hello hoge

こんにちは piyo

foreach のループ内でモジュール型ごとに処理をわけるので、面倒ではありますが、個別の対応が柔軟にできそうです。

delmod をしたあとの newmod は空き要素を優先するので、次の要素番号が気になる場合は前述の「モジュール型変数配列の要素をdelmodした後の配列についてをご覧ください。