はじめに
このチュートリアルでは、Second Life で使えるカウンターHUDを作りながら Hitoshina - Language(以下 HL)の基本を学びます。
何を作るか
完成するHUDは以下の機能を持ちます:
- + ボタンでカウントを1増やす
- - ボタンでカウントを1減らす
- Menu ボタンでダイアログメニューを表示し、リセットや現在値の表示ができる
シンプルなHUDですが、HL の主要な概念をひと通り体験できます。
学べること
| 章 | 学ぶ概念 |
|---|---|
| プロジェクトの準備 | script 宣言、#init イベント、メッセージ送信 |
| HUDの基本構造 | prim 定義、プロパティ |
| ボタンとタッチ操作 | #touch_start イベント、prims グループ |
| メッセージ通信 | -> 演算子、リンクメッセージ |
| 状態管理とダイアログ | context、#listen、ダイアログ構文 |
前提知識
- Second Life の基本操作(オブジェクトの作成、HUDの装着)
- テキストエディタの操作(VS Code、Neovim など)
- HL コンパイラ (
hl) がインストール済みであること
コンパイラのインストールについてはダウンロードページを参照してください。
プロジェクトの準備
まず HL のプロジェクトを作成し、最初のスクリプトを書いてコンパイルするところまで進めます。
プロジェクトの作成
ターミナルで以下のコマンドを実行してプロジェクトを作成します。
hl new counter_hud
以下のディレクトリ構成が生成されます。
counter_hud/
├── lib/ # ライブラリファイル (.hlib)
├── prc/ # プロシージャライブラリ (.hprc)
├── msg/ # メッセージハンドラー (.hmsg)
├── output/ # 出力ファイル (.lsl)
├── tests/ # テストファイル (.hltest)
└── main.hl # メインスクリプト(初期テンプレート)
lib/— 再利用する関数・定数をまとめる.hlibファイルの置き場prc/— 再利用するプロシージャをまとめる.hprcファイルの置き場msg/— メッセージ受信時の処理を定義する.hmsgファイルの置き場output/— コンパイル結果(LSLファイル)の出力先tests/— 単体テスト(.hltestファイル)の置き場main.hl— プロジェクトのエントリポイント。初期テンプレート("Hello, World!" -> world;)が書き込まれた状態で生成されます
最初のスクリプト
生成された counter_hud/main.hl を開いて、以下のコードに書き換えます。
script main;
main #init {
"Counter HUD Ready" -> owner;
}
このコードは3つの要素で構成されています:
script main;—mainという名前のスクリプトを宣言します。コンパイルするとoutput/main.lslが生成されますmain #init { ... }— スクリプトの初期化イベント。Second Life でスクリプトが読み込まれたとき、またはリセットされたときに実行されます"Counter HUD Ready" -> owner;— オーナー(自分)にメッセージを送信します
コンパイルと実行
プロジェクトディレクトリでコンパイルを実行します。
hl main.hl
output/main.lsl が生成されます。このLSLファイルを Second Life のオブジェクトに入れてテストしてみましょう。
チャット欄に「Counter HUD Ready」と表示されれば成功です。
メッセージ送信の基本
HL では -> 演算子でメッセージを送信します。送信先はいくつか用意されています。
"Hello" -> owner; // オーナーにだけ送信
"Hello" -> world; // 周囲全体(チャット)に送信
"Hello" -> debug; // デバッグチャンネルに送信
HUDの開発中は -> owner を使うと、自分にだけメッセージが見えるので便利です。
HUDの基本構造
HUDのボタンを HL のコードで扱えるようにするため、prim 定義とプロパティを設定します。
Second Life 側の準備
Second Life で以下のオブジェクトを作成してください:
- ルートプリム(HUD本体)を1つ作成
- 子プリムを3つリンクして追加(+ボタン、-ボタン、Menuボタン)
- 各子プリムの Name(名前) をそれぞれ
btn_plus、btn_minus、btn_menuに設定
重要: HL の
prim識別子は Second Life のプリム名と一致している必要があります。タッチイベントはプリム名をもとに正しいハンドラへ振り分けられます。
prim 定義
main.hl に prim 宣言を追加します。prim はリンクセット内の子プリムを表します。
script main;
prim btn_plus, btn_minus, btn_menu;
main #init {
"Counter HUD Ready" -> owner;
}
prim btn_plus, btn_minus, btn_menu; で3つのプリムを宣言しています。これらの識別子は Second Life 上のプリム名と一致させる必要があります。タッチイベントの振り分けはこのプリム名で行われます。
プロパティの設定
prim のメソッド(set_color など)を使うには、スクリプト起動時にリンク番号をキャッシュする必要があります。#cache_linknum プロパティを追加します。
script main;
prim btn_plus, btn_minus, btn_menu;
main #cache_linknum;
main #init {
"Counter HUD Ready" -> owner;
}
また、HUDは装着して使うものです。装着時にも #init が実行されるように #attachable を追加します。
script main;
prim btn_plus, btn_minus, btn_menu;
main #cache_linknum;
main #attachable;
main #init {
"Counter HUD Ready" -> owner;
}
プロパティの意味
| プロパティ | 効果 |
|---|---|
#cache_linknum | 起動時にリンクセットを走査し、プリム名と prim 識別子を照合してリンク番号を記録します。prim のメソッド呼び出し(set_color、name()、desc() など)に必要です |
#attachable | アバターに装着されたときにも #init と #animable(設定時)を再実行します |
これで、コードからボタンプリムを操作する準備が整いました。
ボタンとタッチ操作
HUDのボタンをタッチしたときの処理を実装します。
touch_start イベント
HL では #touch_start イベントでプリムのタッチを検出します。イベントハンドラは <prim名> #touch_start @<スクリプト名> の形式で記述します。
script main;
prim btn_plus, btn_minus, btn_menu;
main #cache_linknum;
main #attachable;
main #init {
"Counter HUD Ready" -> owner;
}
btn_plus #touch_start @main |prim obj| {
"Plus tapped" -> owner;
}
btn_minus #touch_start @main |prim obj| {
"Minus tapped" -> owner;
}
btn_menu #touch_start @main |prim obj| {
"Menu tapped" -> owner;
}
各イベントハンドラの構成要素:
btn_plus— タッチを検出する prim#touch_start— タッチ開始イベント@main— このイベントを処理するスクリプト|prim obj|— タッチされたプリムの情報を受け取るパラメータ
prim 名によるイベント振り分け
前章で Second Life のプリム名を btn_plus、btn_minus、btn_menu に設定しました。HL はこのプリム名と prim 識別子の一致をもとに、タッチイベントを自動的に正しいハンドラに振り分けます。
つまり、btn_plus #touch_start @main と書けば btn_plus という名前のプリムがタッチされたときだけ実行されます。条件分岐を自分で書く必要はありません。
prims グループ
ボタンが多い場合は prims グループでまとめて処理することもできます。例えば、3文字の接尾辞を持つプリム名であれば、以下のように書けます。
prim btn_add, btn_sub, btn_cfg;
prims btn_*** = btn_add, btn_sub, btn_cfg;
btn_*** #touch_start @main |prim obj| {
obj.name() -> owner;
}
prims btn_*** = btn_add, btn_sub, btn_cfg; は3つの prim をグループ化します。ワイルドカード *** は3文字を表し、btn_ の後に続く部分(add, sub, cfg)にマッチします。
ワイルドカードのルール:
*の数は接尾辞の文字数と一致する必要があります。全てのメンバーの接尾辞が同じ長さでなければなりません。今回のbtn_plus(4文字)、btn_minus(5文字)、btn_menu(4文字) は接尾辞の長さが揃っていないため、prims グループにはできません。
今回は各ボタンで異なる処理を行い、かつ接尾辞の長さも揃っていないため、個別ハンドラで進めます。
ここまでのコード
script main;
prim btn_plus, btn_minus, btn_menu;
main #cache_linknum;
main #attachable;
main #init {
"Counter HUD Ready" -> owner;
}
btn_plus #touch_start @main |prim obj| {
"Plus tapped" -> owner;
}
btn_minus #touch_start @main |prim obj| {
"Minus tapped" -> owner;
}
btn_menu #touch_start @main |prim obj| {
"Menu tapped" -> owner;
}
ここまでの段階では、まだカウントの加減算は実装していません。次章でメッセージ通信を学んだ後、状態管理と組み合わせて完成させます。
コンパイルしてHUDに入れ、各ボタンをタッチすると対応するメッセージが表示されることを確認してください。
メッセージ通信
HL のメッセージ送信機能について、もう少し詳しく見ていきます。
メッセージ送信の種類
HL の -> 演算子には複数の送信先があります。
// 組み込みターゲット
"msg" -> owner; // オーナーにのみ表示 (llOwnerSay)
"msg" -> world; // 周囲に表示 (llSay)
"msg" -> debug; // デバッグチャンネル (llDebugSay)
// スクリプト間通信(リンクメッセージ)
"msg":100 -> sub; // チャンネル100で sub スクリプトに送信
リンクメッセージ
複数のスクリプトを持つオブジェクトでは、リンクメッセージでスクリプト間通信を行います。HUDの規模が大きくなると、処理をスクリプト分割することが一般的です。
送信側:
"update":100 -> sub;
受信側は #linkmsg イベントで受け取ります。
script sub;
sub #linkmsg :100 |str msg| {
msg -> owner;
}
:100 はチャンネル番号です。送信側と受信側で同じ番号を使うことで、メッセージをフィルタリングできます。
チャンネルの使い分け
リンクメッセージのチャンネルは自由に決められますが、用途ごとに分けると管理しやすくなります。
const int CH_UPDATE = 100; // 表示更新用
const int CH_COMMAND = 200; // コマンド送信用
const で定義した定数は、コード中のどこでも使えます。
カウンターHUDでの活用
今回のカウンターHUDは1スクリプトで完結するため、リンクメッセージは使いません。ただし、HUDが複雑化した場合(表示制御を別スクリプトに分離するなど)に必要になる重要な概念です。
次章では context を使った状態管理を導入し、カウンターの増減を実装します。
状態管理とダイアログ
カウンターの値を保持し、メニューダイアログを表示する機能を実装します。
context による状態管理
HL では context を使ってスクリプトの状態を管理します。context 内に宣言した変数は、イベントハンドラをまたいで値が保持されます。
context @main {
int count = 0;
}
context @main は main スクリプトに紐づく状態です。int count = 0; でカウンターの初期値を0に設定しています。
イベントハンドラで context にアクセスするには、パラメータリストに context を追加します。
main #init |context ctx| {
ctx.count = 0;
"Counter HUD Ready: " + ctx.count.to_str() -> owner;
}
カウンターの実装
context を使って、ボタンタッチでカウントを増減するコードを完成させます。
script main;
prim btn_plus, btn_minus, btn_menu;
main #cache_linknum;
main #attachable;
context @main {
int count = 0;
}
main #init |context ctx| {
ctx.count = 0;
"Counter HUD Ready" -> owner;
}
btn_plus #touch_start @main |prim obj, context ctx| {
ctx.count += 1;
"Count: " + ctx.count.to_str() -> owner;
}
btn_minus #touch_start @main |prim obj, context ctx| {
ctx.count -= 1;
"Count: " + ctx.count.to_str() -> owner;
}
|prim obj, context ctx| のように、パラメータは複数受け取れます。ctx.count.to_str() は整数を文字列に変換するメソッドです。
ダイアログの表示
Menu ボタンをタッチしたときにダイアログを表示します。送信側と受信側で同じチャンネル番号を共有する必要があるため、5 章で見た const を使ってチャンネル番号に名前を付けておきます。リテラルを複数箇所に書くと、片方だけ変更してしまった際に送受信ミスマッチで気づきにくいバグが入ります。
ダイアログ応答用には負のチャンネルを使います。アバターは通常チャット欄からチャンネル 0 や正のチャンネルへしか発言できないため、負のチャンネルにはユーザー入力が混ざらず、ダイアログ応答以外のメッセージで誤動作する事故を防げます。
const int CH_DIALOG = -101;
HL にはダイアログ専用の構文があり、ボタンの配置をコード上で視覚的に表現できます。
btn_menu #touch_start @main |prim obj, context ctx| {
"Counter: " + ctx.count.to_str()
|"Reset", "Close"|
@owner:CH_DIALOG -> world;
}
この構文の要素:
"Counter: " + ctx.count.to_str()— ダイアログのメッセージ|"Reset", "Close"|— ボタンの1行(ソースコード上の並び順で表示されます)@owner:CH_DIALOG— ダイアログの応答を受け取るターゲット(オーナー)とチャンネル(CH_DIALOG = -101)-> world;— ダイアログの送信(固定構文)
ボタンの表示順序: LSL のダイアログはボタンリストを逆順で表示しますが、HL ではコンパイル時に自動的に逆転処理を行います。そのため、ソースコードに書いた並び順がそのままダイアログに反映されます。
listen イベントでダイアログの応答を処理
ダイアログのボタンが押されると、指定したチャンネル(CH_DIALOG)にメッセージが届きます。#listen イベントで受け取ります。
main #listen :CH_DIALOG + owner |str msg, context ctx| {
switch msg {
"Reset" => {
ctx.count = 0;
"Counter reset" -> owner;
}
"Close" => "Closed" -> owner;
}
}
:CH_DIALOG—CH_DIALOGチャンネルのメッセージを受信+ owner— オーナーからのメッセージのみ受け付ける(セキュリティ対策)|str msg, context ctx|— 受信メッセージと context を受け取る
ここまでの全コード
script main;
prim btn_plus, btn_minus, btn_menu;
const int CH_DIALOG = -101;
main #cache_linknum;
main #attachable;
context @main {
int count = 0;
}
main #init |context ctx| {
ctx.count = 0;
"Counter HUD Ready" -> owner;
}
btn_plus #touch_start @main |prim obj, context ctx| {
ctx.count += 1;
"Count: " + ctx.count.to_str() -> owner;
}
btn_minus #touch_start @main |prim obj, context ctx| {
ctx.count -= 1;
"Count: " + ctx.count.to_str() -> owner;
}
btn_menu #touch_start @main |prim obj, context ctx| {
"Counter: " + ctx.count.to_str()
|"Reset", "Close"|
@owner:CH_DIALOG -> world;
}
main #listen :CH_DIALOG + owner |str msg, context ctx| {
switch msg {
"Reset" => {
ctx.count = 0;
"Counter reset" -> owner;
}
"Close" => "Closed" -> owner;
}
}
コンパイルしてHUDに入れてみましょう。+/-ボタンでカウントが増減し、Menuボタンでダイアログが表示されることを確認してください。
primの説明を表示する
これまでのチュートリアルでは #touch_start の |prim obj| パラメータを宣言だけして、中身は使ってきませんでした。本章では obj.name() と obj.desc() でタッチされた prim の情報を取得し、Second Life 側で設定した値を表示します。
Second Life 側の準備
各ボタンの Description(説明) フィールドに任意の文字列を設定します。Second Life の編集パネルから:
- HUD を装着または rez
- 編集モードで
btn_plus、btn_minus、btn_menuを1つずつ選択 - General タブの Description に以下を入力
| プリム名 | Description |
|---|---|
btn_plus | Increment counter |
btn_minus | Decrement counter |
btn_menu | Open menu |
obj.name() と obj.desc()
|prim obj| は touch_start ハンドラに渡される prim インスタンスです。obj.name() で識別子名、obj.desc() で Description を取得できます。
btn_plus #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
ctx.count += 1;
"Count: " + ctx.count.to_str() -> owner;
}
タッチすると以下のように表示されます。
btn_plus: Increment counter
Count: 1
ここまでの全コード
script main;
prim btn_plus, btn_minus, btn_menu;
const int CH_DIALOG = -101;
main #cache_linknum;
main #attachable;
context @main {
int count = 0;
}
main #init |context ctx| {
ctx.count = 0;
"Counter HUD Ready" -> owner;
}
btn_plus #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
ctx.count += 1;
"Count: " + ctx.count.to_str() -> owner;
}
btn_minus #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
ctx.count -= 1;
"Count: " + ctx.count.to_str() -> owner;
}
btn_menu #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
"Counter: " + ctx.count.to_str()
|"Reset", "Close"|
@owner:CH_DIALOG -> world;
}
main #listen :CH_DIALOG + owner |str msg, context ctx| {
switch msg {
"Reset" => {
ctx.count = 0;
"Counter reset" -> owner;
}
"Close" => "Closed" -> owner;
}
}
Description はコンパイル後の LSL を変更しなくても Second Life 側だけで書き換えられるため、ボタンに紐づくラベルや設定値を後から差し替える用途にも使えます。
状態に応じて色を変える
context のカウンター値に応じて、Menu ボタンの色を切り替えます。set_color メソッドを使うと prim の色を動的に変更できます。
set_color メソッド
prim の色は #<prim識別子>.set_color|<面>, <色>, <透明度>| で設定します。
| 引数 | 型 | 説明 |
|---|---|---|
| 面 | int | 面番号。std::ALL_SIDES で全面 |
| 色 | vec | <R, G, B> 形式(各成分 0.0〜1.0) |
| 透明度 | float | 0.0(透明)〜 1.0(不透明) |
std::ALL_SIDES は標準ライブラリの定数のため、import standard as std; をスクリプトの先頭に追加します。
カウンター値で分岐
カウンターが正なら緑、負なら赤、0 なら白に切り替えます。if / elif / else で分岐します。
if 0 < ctx.count {
#btn_menu.set_color|std::ALL_SIDES, <0.0, 1.0, 0.0>, 1.0|; // 緑
} elif ctx.count < 0 {
#btn_menu.set_color|std::ALL_SIDES, <1.0, 0.0, 0.0>, 1.0|; // 赤
} else {
#btn_menu.set_color|std::ALL_SIDES, <1.0, 1.0, 1.0>, 1.0|; // 白
}
比較演算子: HL でも一般的なコーディングルールとして
</<=のみを使用しています。0 < ctx.countのように左辺に小さい方の値を置きます。
プロシージャに切り出す
カウンターは #init、btn_plus、btn_minus、#listen 内 Reset の 4 箇所で変化します。各所に同じ if / elif / else を書くと冗長なので、色更新処理をプロシージャとして切り出します。
プロシージャは #<プロシージャ名> |<引数リスト>| { ... } で定義し、#<プロシージャ名>|<引数>|; で呼び出します。色を塗る対象も prim 型の引数として受け取るようにすれば、btn_menu 以外のボタンに使い回せます。
#update_color |prim target, int count| {
if 0 < count {
#target.set_color|std::ALL_SIDES, <0.0, 1.0, 0.0>, 1.0|;
} elif count < 0 {
#target.set_color|std::ALL_SIDES, <1.0, 0.0, 0.0>, 1.0|;
} else {
#target.set_color|std::ALL_SIDES, <1.0, 1.0, 1.0>, 1.0|;
}
}
ポイント:
- 仮引数
prim targetに対しても、prim 識別子と同じく#target.set_color|...|;の形で#プレフィックスを付けてメソッドを呼び出します - 呼び出し側は対象の prim を
#なしの識別子で渡します:#update_color|btn_menu, ctx.count|;
ここまでの全コード
import standard as std;
script main;
prim btn_plus, btn_minus, btn_menu;
const int CH_DIALOG = -101;
main #cache_linknum;
main #attachable;
context @main {
int count = 0;
}
#update_color |prim target, int count| {
if 0 < count {
#target.set_color|std::ALL_SIDES, <0.0, 1.0, 0.0>, 1.0|;
} elif count < 0 {
#target.set_color|std::ALL_SIDES, <1.0, 0.0, 0.0>, 1.0|;
} else {
#target.set_color|std::ALL_SIDES, <1.0, 1.0, 1.0>, 1.0|;
}
}
main #init |context ctx| {
ctx.count = 0;
#update_color|btn_menu, ctx.count|;
"Counter HUD Ready" -> owner;
}
btn_plus #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
ctx.count += 1;
#update_color|btn_menu, ctx.count|;
"Count: " + ctx.count.to_str() -> owner;
}
btn_minus #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
ctx.count -= 1;
#update_color|btn_menu, ctx.count|;
"Count: " + ctx.count.to_str() -> owner;
}
btn_menu #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
"Counter: " + ctx.count.to_str()
|"Reset", "Close"|
@owner:CH_DIALOG -> world;
}
main #listen :CH_DIALOG + owner |str msg, context ctx| {
switch msg {
"Reset" => {
ctx.count = 0;
#update_color|btn_menu, ctx.count|;
"Counter reset" -> owner;
}
"Close" => "Closed" -> owner;
}
}
コンパイルして HUD に入れ、+/- ボタンでカウンターを増減させると Menu ボタンの色が緑↔白↔赤と切り替わります。これで #cache_linknum が set_color の前提として実際に効いていることが確認できます。
完成とまとめ
カウンターHUDが完成しました。ここまでに学んだ概念を整理します。
完成コード
import standard as std;
script main;
prim btn_plus, btn_minus, btn_menu;
const int CH_DIALOG = -101;
main #cache_linknum;
main #attachable;
context @main {
int count = 0;
}
#update_color |prim target, int count| {
if 0 < count {
#target.set_color|std::ALL_SIDES, <0.0, 1.0, 0.0>, 1.0|;
} elif count < 0 {
#target.set_color|std::ALL_SIDES, <1.0, 0.0, 0.0>, 1.0|;
} else {
#target.set_color|std::ALL_SIDES, <1.0, 1.0, 1.0>, 1.0|;
}
}
main #init |context ctx| {
ctx.count = 0;
#update_color|btn_menu, ctx.count|;
"Counter HUD Ready" -> owner;
}
btn_plus #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
ctx.count += 1;
#update_color|btn_menu, ctx.count|;
"Count: " + ctx.count.to_str() -> owner;
}
btn_minus #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
ctx.count -= 1;
#update_color|btn_menu, ctx.count|;
"Count: " + ctx.count.to_str() -> owner;
}
btn_menu #touch_start @main |prim obj, context ctx| {
obj.name() + ": " + obj.desc() -> owner;
"Counter: " + ctx.count.to_str()
|"Reset", "Close"|
@owner:CH_DIALOG -> world;
}
main #listen :CH_DIALOG + owner |str msg, context ctx| {
switch msg {
"Reset" => {
ctx.count = 0;
#update_color|btn_menu, ctx.count|;
"Counter reset" -> owner;
}
"Close" => "Closed" -> owner;
}
}
学んだ概念
| 概念 | HL の記法 | 説明 |
|---|---|---|
| スクリプト宣言 | script main; | LSL出力ファイルの単位 |
| インポート | import standard as std; | 標準ライブラリの読み込み |
| 定数 | const int CH_DIALOG = -101; | リテラル重複を避けて意図に名前を付ける |
| プリム定義 | prim btn_plus; | リンクセット内のプリムを識別 |
| プロパティ | #cache_linknum, #attachable | スクリプトの動作設定 |
| イベント | #init, #touch_start, #listen | Second Life イベントへの応答 |
| メッセージ送信 | "msg" -> owner; | 様々なターゲットへの送信 |
| 状態管理 | context @main { int count = 0; } | イベント間で共有する変数 |
| ダイアログ | "msg" |"btn"| @owner:ch -> world; | インタラクティブなメニュー表示 |
| 制御構文 | if / elif / else, switch msg { "val" => ...; } | 条件分岐とパターンマッチ |
| プロシージャ | #name |args| { ... }, #name|args|; | 副作用処理を切り出して再利用 |
| prim メソッド | obj.name(), obj.desc(), #prim.set_color|...| | prim の情報取得と外観操作 |
次のステップ
このチュートリアルではHLの基礎を体験しました。さらに詳しく学ぶには以下のリソースを活用してください:
- 言語リファレンス — 型システム、式、標準ライブラリなどの完全な仕様
- Playground — ブラウザ上でHLコードを試せるエディタ
HLにはこのチュートリアルで扱わなかった多くの機能があります:
- ライブラリ (
.hlib) — 関数や定数を再利用可能なモジュールにまとめる - プロシージャ (
.hprc) — 共通処理を分離してincludeで使い回す - メッセージハンドラ (
.hmsg) — リンクメッセージの処理を別ファイルに切り出す - 型付きリスト (
ilist,slistなど) — 型安全なリスト操作 - map/fold 式 — コレクションの変換・集約
- テスト (
.hltest) — ユニットテストの記述
これらの機能を使うことで、より大規模で保守しやすいプロジェクトを構築できます。