AliceScript 3.0の新機能

この記事では、AliceScript3.0の変更点と新機能について説明します


taiseiue | 2023-12-23

新機能

AliceScript 3.0(Alice3.0)は、Alice2.3の後継です。 Alice3.0では大きく分けて3つの領域で性能が向上しました。

  • 開発効率の向上 : Alice3.0では、変数への型チェック・nullチェック、関数での契約、名前空間を用いて変数や関数を整理できるようになりました。これによって、より複雑なコードでも可読性が向上します。
  • 相互運用性の向上 : Alice3.0では、関数バインド関数の外部実装などによって.NETやC言語製のAPIとシームレスに通信できるようになりました。これによって、よりたくさんのことをAliceScriptでこなせるようになります。
  • パフォーマンスの向上 : AliceScriptとLosettaで、Alice2.3と比較して性能が最大で100倍向上しました。そのほかにも、さまざまなケースでパフォーマンスが飛躍的に向上しています。

最新のAliceScriptADKはAliceScriptのダウンロードからダウンロードできます。

ご意見をお寄せください

AliceScriptの新機能に関するご意見や不具合がある場合、AliceScriptのリポジトリにお寄せください。

変数関係

型チェックの導入

Alice3.0での変数は、自動的に型チェックされており、一度定まった型に異なる型の値を代入することはできません。また、変数は宣言時に型を宣言できます。 次の例をご覧ください。

AliceScript
string str = "Hello"; //文字列型
number num = 1; //数値型

str = 1; //これはエラー

型キーワードの代わりにvarキーワードを使用することで、右辺の型を使用して自動的に変数の型を決定できます。次の例をご覧ください。

AliceScript
var num = 1; //自動的に数値型になる

num = "Hello"; //これはエラー

従来通りどんな型の値でも代入できる変数を作成するには、variableキーワードを使用して、バリアント型を定義します。バリアント型の変数は型チェックが省略されます。

AliceScript
variable item = 1;

item = "Hello"; //エラーにならない

nullチェックの導入

型チェック同様、Alice3.0での変数は自動的にnullチェックがされており、通常変数にnullを代入することはできません。nullを代入できる変数を作成するには、型キーワードに?を付けます。次の例をご覧ください。

AliceScript
number num1 = 0;
number? num2 = 0;

num1 = null; //これはエラー
num2 = null; //これはエラーにならない

関数関係

戻り値の型指定

関数のシグネチャに戻り値の型を記述することで、その関数がどんな値を返すかを明らかにできます。次の例をご覧ください。

AliceScript
string GetUserName()
{
    return "UserName"; //文字列を返す
}

string BadFunc()
{
    return 0; //戻り値の型が異なるためエラー
}

void SubRoutine()
{
    return; //戻り値を返さない(暗黙的にvoidを返している)
}

また、関数の宣言時にfunctionキーワードが不要になりました。次の例でFunc1Func2Func3はどちらも同じ内容の関数です。

AliceScript
function Func1()
{
    print("Hello,World");
}

void Func2()
{
    print("Hello,World");
}

Func3()
{
    print("Hello,World");
}
この変更について詳しく知るには、Losetta#106を参照してください。

参照渡し

関数内で引数の値を変更し、その変更を呼び出し元にも反映できます。AliceScriptではrefキーワードを使用することで、参照渡しであることが明らかになり、間違いを防げます。 次に例を示します。

AliceScript
void Increment(ref number num)
{
    num++;
}

var i = 0;
Increment(ref i);
print(i); //出力 : 2

拡張メソッド

任意の型について、拡張メソッドを実装できるようになりました。 また、拡張メソッドとしてのみ呼び出せるようにするextonlyキーワードを導入しました。 次に例を示します。

AliceScript
void Output(this string str)
{
    print(str);
}

string s = "Hello,World";
Output(s);  //通常の関数として呼び出し
s.Output(); //拡張メソッドとして呼び出し

extonly number Pow(this number a)
{
    return a * a;
}

number n = 2;
n.Pow(); //拡張メソッドとして呼び出し
Pow(n);  //これはエラー

この変更について詳しく知るには、Losetta#99を参照してください。

型を省略することで、すべての型で使用できる拡張メソッドを定義できます。 次の例をご覧ください。

AliceScript
extonly void Output(this item)
{
    print(item);
}

この変更について詳しく知るには、Losetta#107を参照してください。

関数の外部実装

外部で実装された関数をAliceScriptで使用するには、externキーワードを使用して関数を宣言します。

次の例では、.NETで実装されているSystem.Console.WriteLineメソッドを使用してコンソールにメッセージを表示します。

AliceScript
#netimport "System.Console","System.Console"
extern void WriteLine(string text);

WriteLine("Hello,World");

また、次の例では、Win32APIで定義されているMessageBox関数を使用してメッセージを表示します。

AliceScript
#libimport "user32.dll"
extern int MessageBox(HWND hwnd,LPCSTR lpText,LPCTSTR lpCaption,UINT uType);

MessageBox(0,"Hello,World!","TestMessage",0);

関数と契約

requiresおよびensureキーワードを使用して関数の事前条件・事後条件を明記することで、関数がとりうる値を表明できます。次の例をご覧ください。

AliceScript
number Divide(number numerator, number denominator) 
  requires (denominator != 0) ensures (return >= 0) // 表明
  {
      return numerator / denominator;
  }

この関数は、numeratordenominatorで除算します。 この関数を呼び出すとき、denominator0以外の値である必要があり(requiresによる事前条件の表明)、この関数は常に0以上の値を返します(ensuresによる事後条件の表明)。

この変更について詳しく知るには、Losetta#104を参照してください。

ユーザー定義の名前空間

名前空間を使用すると、たくさんの関数や関数を名前空間単位で分類できます。 次の例をご覧ください。

AliceScript
namespace MyNamespace
{
    // この関数は名前空間の外からアクセスできる
    public void PublicFunc()
    {
        print($"Hello,{Name}");
    }

    // この変数は名前空間の外からアクセスできる
    public string Name = "John";

    // この関数は同じ名前空間内からアクセスできる
    internal void InternalFunc()
    {
        print($"Hello,{Name}");
    }

    // この関数はこのスコープのみからアクセスできる
    private void PrivateFunc()
    {
        print($"Hello,{Name}");
    }
}

MyNamespace.PublicFunc(); //呼び出し

print(MyNamespace.Name); //変数アクセス

namespace MyNamespace
{
    InternalFunc(); //呼び出し
}

この変更について詳しく知るには、Losetta#101を参照してください。

文字列リテラル関係

UTF-8リテラル

文字列リテラルの先頭にu8を追加することで、文字列をUTF-8バイナリの形式で表現できます。次の例をご覧ください。

AliceScript
var str = u8"ABC";
print(str.Type);//出力:BYTES

文字列補間

文字列補間を使用すると、文字列を連結して結果を表示する代わりに文字列中で式の結果を使用できます。次の例をご覧ください。

AliceScript
print($"2 + 3 = {2+3}");//出力:2 + 3 = 5

エスケープ文字

新しいエスケープ文字\eを使用すると、ANSI X.364を用いてコンソールでさまざまな効果を表現できます。次の例では、コンソールでテキストを赤い文字でを表示しています。

AliceScript
print("\e[31mHello,World");

Unicodeエスケープシーケンス

Unicodeエスケープシーケンスを使用すると、任意のUnicode文字をコードポイントを使って表記できます。次の例をご覧ください。

AliceScript
// `N`のUnicodeコードポイントはU+0004e

print("\u004e");     //出力 : N  (\uXXXX)4ケタ
print("\U0000004e"); //出力 : N  (\Uxxxxxxxx)8ケタ
print("\x4e");       //出力 : N  (\xXX)1~4ケタ

数値リテラル関係

8進数リテラル

数値リテラルの先頭に0oを追加することで8進数で数値を表現できます。次の例をご覧ください。

AliceScript
var num = 1234;//10進数リテラル
print(num);//出力:1234

num = 0o2322;
print(num);//出力:123

数値区切り文字

桁数が大きな数値リテラルを見やすく表現するため、リテラルの先頭と終端、小数点の前後以外の任意の場所にアンダースコア(_)を使用できます。次の例をご覧ください。

AliceScript
var num = 1_234_567;

num = 0x_12D_687;

num = _12_3;//これはエラーになる

num = 12._3;//これもエラーになる

例外処理関係

例外の再スロー

try...catchで受取ったExceptionオブジェクトを使って例外を再スローできるようになりました。次の例をご覧ください。

AliceScript
try
{
    throw 0x00; //例外をスローさせる
}
catch(e)
{
    print("例外がスローされています");
    throw e; //例外を再スローする
}

この変更について詳しく知るには、Losetta#40を参照してください。

catchのオブジェクトの省略

例外がスローされた際例外の内容を報告するオブジェクトが不要な場合、受取る引数が不要になりました。次の例をご覧ください。

AliceScript
try
{
    throw 0x00; //例外をスローさせる
}
catch
{
    print("例外がスローされました");
}

例外フィルター

自分で処理できる例外のみを選択して処理できるようになりました。 例外フィルターでは、when(condition)を使って条件に一致する例外のみを補足できます。 次の例をご覧ください。

AliceScript
try
{
    throw 0x00;
}
catch(e) when (e.ErrorCode == 0x00)
{
    print("0x00例外がスローされました");
}
catch
{
    print("それ以外の例外がスローされました");
}

配列アクセス関係

スプレッド構文

スプレッド構文(...ary)を使用することで配列式中で、別の配列にある要素を展開できるようになりました。 次の例をご覧ください。

AliceScript
var ary1 = [1,2,3];

var ary2 = [0,...ary1,4,5]; // [0,1,2,3,4,5]になる

この変更について詳しく知るには、Losetta#100を参照してください。

逆から指定できる配列添え字

次のように、配列中の後ろからn番目を取得するには配列添え字に^を使用できます。

AliceScript
var ary = [1,2,3,4];

print(ary[^2]); //後ろから2番目を表示する (出力 : 3)

// ary[^2]は以下のコードに動的書き換えされる
ary[ary.Length-2];

その他フロー関係

caseおよびdefaultでフォールスルーを予防するように

従来、switch中のcaseおよびdefaultは一致した部分より下がすべて実行されていました。しかし多くの場合一致したcaseのみ処理させたいため、breakを使用していました。

Alice3.0では規定では、確実にブロックを抜けないcaseやdefaultをエラーにするようになりました。次の例をご覧ください。

AliceScript
var item = 1;

switch(item)
{
    case 0:
    {
        break; //breakで抜けている
    }
    case 1:
    {
        print("itemは1でした"); //breakで抜けていないためエラー
    }
    case 2:
    {
        return; //returnで抜けている
    }
}
従来通りフォールスルーを使用するには、#fall_through enableをコードの先頭に記述してください。

AliceScript
#fall_through enable

var item = 1;

switch(item)
{
    case 0:
    {
        print("itemは0でした");
    }
    case 1:
    {
        print("itemは1でした"); // 0のときも実行される
    }
}

複数caseに一致する処理を書けるように

次のように、複数のcaseのいずれか(OR)に該当する処理を簡単に書けるようになりました。

AliceScript
var item = 1;

switch(item)
{
    case 0:
    case 1:
    case 2:
    {
        print("itemは0,1,2のいずれかでした");
        break;
    }
    default:
    {
        print("itemはそれ以外の値でした");
        break;
    }
}

特定範囲内で変数を読み取り専用にするreadonly文の導入

次のように、readonly文を使うことで特定範囲内で変数を読み取り専用にできるようになりました。これによって変数が書き換えられない範囲を明記できます。

AliceScript
var item = 1;

readonly(item)
{
    item = 2; //これはエラーになる
}

item = 3; //これはエラーにならない

スコープを明示的に表記できるblock文の実装

次のように、block文および空の文を使用して変数のスコープを明示的に表現できます。

AliceScript
block
{
    var item = 1;
}

print(item); //これはエラーになる

{
    var item = 1;
}

print(item); //これもエラーになる

ステートメント形式のラムダ式

ステートメント形式のラムダ式を使用すると、処理が複数行にわたるような複雑なデリゲートをラムダ式で表現できます。また、丸かっこ()を用いた引数リストにも対応したため、引数を持たないラムダ式を作成できます。次の例をご覧ください。

AliceScript
// ステートメント形式のラムダ式
var hello = (name) =>
{
    print(name);
}

hello("John");

// 引数を持たないラムダ式
var hello2 = () => print("Hello");

この変更について詳しく知るには、Losetta#16を参照してください。

前置インクリメント・デクリメント演算子の導入

前置インクリメント演算子(++i)は、変数の値を1増やし、増やした後の値を返します。 反対に、前置デクリメント演算子(--i)を使用すると変数の値が1減ります。 次の例をご覧ください。

AliceScript
var i = 0;

print(++i); //出力 : 1

print(--i); //出力 : 0

パフォーマンスの改善

ソースコードの解析をロケール非依存のものに変更することで、すべてのコードで解析にかかる時間が2桁~3桁倍高速化しました。 また、空の文やブロックの解析をスキップする最適化を行ったほか、関数の呼び出しも高速化しました。次のソースコードの実行にかかる時間をAlice2.3に準拠したLosettaとAlice3.0に準拠したLosettaで比較しました。

AliceScript
function ForEmpty()
{
    for(var i = 0;i < 10000;i++)
    {
        ;
    }
}

function ForIncrement()
{
    for(var i = 0;i < 10000;i++)
    {
        str++;
    }
}

function ForStringAppend()
{
    var str = "";
    for(var i = 0;i < 10000;i++)
    {
        str += "\"\"\"\"\"\"\"\"\"";
    }
}
Function Mean Error StdDev Max Min
ForEmpty(2.0) 560.87ms 0.17ms 5.30ms 592ms 555ms
ForIncrement(2.0) 820.36ms 6.17ms 61.70ms 1051ms 765ms
ForStringAppend(2.0) 1437.83ms 3.13ms 31.34ms 1527ms 1393ms
ForEmpty(3.0) 5.34ms 0.05ms 1.60ms 25ms 5ms
ForIncrement(3.0) 86.76ms 1.71ms 17.13ms 254ms 82ms
ForStringAppend(3.0) 277.82ms 1.63ms 16.34ms 428ms 269ms

この測定結果は一例です。すべての環境で同様の結果が得られるとは限りません。

使用できる文字の変更

Alice3.0では、制御文字やフォーマット文字、非文字など一部の文字を無視するようになりました。これは、Unicode標準にのっとり、他の多くの言語と同様にプログラムの可読性を高め、誤解を避けるための措置です。 使用できない文字の厳密な定義はProcessing.csおよびConstants.csを参照してください。

CLIの改善

  • デバッグ出力のインデントに使用される文字をを空白文字4つに変更
  • デバッグ出力の表示色を白色から水色に変更
  • REPLで呼び出し履歴(スタックトレース)が表示されるように
  • ARM版macOS(M1Mac)をネイティブサポート
  • 可能な場合スタックトレースの文字列表現に名前空間を表示するように
  • パス設定用のスクリプトを生成するように
  • aliceで例外発生時に式を挿入できるように
  • 構文エラー時に表示されるエラーを改善

実装の変更

  • C#のchar型との相互変換を実装
  • 別の場所にあるアセンブリを読み込めるように

APIの変更

  • env_lang_name関数とenv_lang_version関数を追加
  • file_read_codepage関数を実装
  • file_write_textfile_append_textでコードページ番号を指定して書き込めるように
  • より安全にファイルを読み書きできるfile_write_encryptfile_read_decryptを実装
  • exit関数を丸括弧なしで呼び出せるように変更
  • bytes.ToString(number codePage)で文字コードを指定して文字列化できるように
  • bytes.ToBase64を実装
  • 文字列や配列の拡張メソッド追加
  • スクリプトから直接ParsingScriptを制御できるように
  • DNS関連の関数実装
  • 対数関数(math_log)、逆数の近似値を求める関数(Math_ReciprocalEstimate)、階乗を求める関数(math_factorial)を実装
  • 表明関連の関数を実装
  • string.PadCenter関数を実装
  • ProcessInfoオブジェクトで終了コードをとれるように
  • 数値丸め処理APIのオプションを追加

APIの不具合の修正

  • #28 Alice.Random.random_intがクラッシュする不具合を修正
  • #29 Alice.Random.random_intに最大値だけを指定して実行すると、nullが返ってくる不具合を修正
  • #31 do...whileのループが完了後に0x020例外が発生する不具合を修正
  • env_expand_environmentVariablesの引数の文字列がnullの場合に例外が発生する不具合の修正