Ada 2020 (21日目) - user-defined literal

息継ぎがしたいので寄り道回入れていいですか。 と言いましても割と大きな変更になります。

経緯

これまで標準の型をラップするライブラリを使うのが結構大変でした。 Adaでは暗黙の型変換は匿名の access 型まわりとユーザー定義参照型ぐらいしかありません。 定数ひとつ作るにも関数呼び出しが必要です。

with Ada.Strings.Unbounded;
package Pkg1 is
   X : constant Ada.Strings.Unbounded.Unbounded_String :=
     Ada.Strings.Unbounded.To_Unbounded_String ("ABC");

use を使えばパッケージ名は省略できます。 それでも関数名と括弧は必要です。

with Ada.Strings.Unbounded;
package Pkg1 is
   use all type Ada.Strings.Unbounded.Unbounded_String;
   X : constant Ada.Strings.Unbounded.Unbounded_String :=
     To_Unbounded_String ("ABC");

そこでよく使われていたテクニックが単項 "+" 演算子の悪用です。

with Ada.Strings.Unbounded;
package Pkg1 is
   function "+" (S : String) return Ada.Strings.Unbounded.Unbounded_String
     renames Ada.Strings.Unbounded.To_Unbounded_String;
   X : constant Ada.Strings.Unbounded.Unbounded_String := +"ABC";

一口に悪用と言いましても単項 "+" 演算子は元々値をそのまま返す演算子です。 値はそのまま型だけ変える用途に使うのは某言語で <<>>/ 等の演算子を意味を変えて使ってしまっているよりも随分まともと言えましょう。

Ada 2020での改善

Integer_Literalアスペクト/Real_Literalアスペクト/String_Literalアスペクト

Integer_LiteralReal_LiteralString_Literal アスペクトが追加されました。 文字列からユーザー定義型への変換関数を指定します。 文脈に応じて対応するリテラルがその型として解釈されるようになります。

元々Adaのリテラルは特に修飾しなくても文脈に応じて型が変わっていました。

declare
   U : constant := 0; -- universal integer
   X : Integer := 10;
   Y : Short_Integer := 20;
   Z : Interfaces.Unsigned_32 := 30;

なおuniversal_integerは型が付く前のコンパイル時定数です。 U はオブジェクト(型付きの定数)ではなくてnamed numberと呼ばれる型なしの定数となり整数リテラル同様に使用できます。

明示的に修飾するならこうですね。

declare
   U : constant := 0; -- universal_integer
   X : Integer := Integer'(10);
   Y : Short_Integer := Short_Integer'(20);
   Z : Interfaces.Unsigned_32 := Interfaces.Unsigned_32'(30);

さて Integer_Literal を使ってみます。

declare
   type Wrapped_Integer is
      record
         Element : Integer;
      end record
        with Integer_Literal => To_Wrapped_Integer;
   function To_Wrapped_Integer (X : String) return Wrapped_Integer is
     (Element => Integer'Value (X));

   W : Wrapped_Integer := 40;

やっていることは簡単ですね。

リテラルが文字列で渡されてくるので注意が必要です。 そうでなければ Long_Long_IntegerLong_Long_Float を超える数値型等を定義して使うことができなくなりますので妥当といえば妥当なのですが、変換関数の実装によっては非常に効率が悪いことになりかねません。 変換関数は是非ともstatic式化したいところです。 ('Value 属性がstaticかどうかで実装のしやすさが大きく変わりますよね。 規格では明記されてなさそうです。 GNATで試したところstaticではありません。)

Real_LiteralString_LiteralInteger_Literal と大体同じです。 ただし String_Literal の引数の型は Wide_Wide_String となっています。

またユーザー定義リテラルを使用したライブラリとして任意精度整数/実数が追加されました。 AIの番号としてはこちらが先ですのでこのライブラリのためにユーザー定義リテラルが追加されたと言ったほうが正しいかもしれません。

Ada.Numerics.Big_Numbers.Big_Integers

Ada.Numerics.Big_Numbers.Big_Reals

Big_Reals は浮動小数点数ではなくて分数になります。 GMPで言えば q です。

関連AI

所感

まさかこんな機能が入るとは思ってもみませんでした。 暗黙の型変換よりはHaskellで fromInteger を実装するのに近い挙動ですのでいいのか……な。

構文糖ですけれども割と異物感は少ないです。

ただ数値リテラルの方はともかく、文字列リテラルは私としてはちょっと異議ありですね。 文字列は文字の配列ですので "ABC"['A', 'B', 'C'] としてgeneralized array aggregateで扱っていいのではと思ってしまいます。