Notification
2014年6月時点の話です。 最終的にどうなるかはわかりません。 筆者はヲチしてるだけで関係者ではありません。 勘違い及び私見や願望も含まれております。
☞ Ada 2005 の復習
Ada 2012 の復習
Ada Corrigendum 2014
未解決のIssues
またの名を Amendment 1 to Ada 95。
not null
pragma Assert
limited with
で循環参照もできるoverriding
interface
(いらん)Ada.Containers
(Alex StepanovがC++に行ったのは別にAdaのせいではない)Ada.Environment_Variable
, Ada.Directories
……世間の情報はほとんどAda 95のもの。
2005以降を知らずにAdaを語るなかれ。
とはいえ2005以降を解説している日本語ページは皆無なので英語読んでください。
Ada 2005 の復習
☞ Ada 2012 の復習
Ada Corrigendum 2014
未解決のIssues
構文糖、構文糖、構文糖。
use all type
(お、邪悪と評判高いADLか?)if
, case
, for
等が式中で使えるように (お、関数型ry)aliased
引数Ada.Iterator_Interfaces
(所謂range)Ada.Containers
が大幅に使いやすくなったfor
文でもイテレータを回せる構文糖。
構文糖。
個人的には微妙な構文糖も多いです。
Ada 2005 の復習
Ada 2012 の復習
☞ Ada Corrigendum 2014
未解決のIssues
Ada Corrigendum 2014はAda 2012の細かい修正。
次の規格自体はAda 202xになる。
A := raise Program_Error;
式の中で例外を投げられる。
既にAda 2012で、if、case、forは式中でも使えた。
B := (if ... then ... else ...);
C := (case ... is when ... => ... when ... => ...);
D := (for all I in ARR'Range => CHK (ARR (I)));
-- CHK (ARR (1)) and then CHK (ARR (2)) and then ...
E := (for some I in ARR'Range => CHK (ARR (I)));
-- CHK (ARR (1)) or else CHK (ARR (2)) or else ...
end
がいらない代わりに括弧は必要。
function Based_Log (Base, X : Float) return Float is
begin
return (if Base <= 0.0 or else Base = 1.0 then
raise Argument_Error;
else
Log2 (X) / Log2 (Base));
end Based_Log;
あまり嬉しくなさそう?
普通のif文でいいよね。
どこで使えばいいのか?
事前条件。
先のBased_Log
の仕様部。
function Based_Log (Base, X : Float) return Float;
何を渡せば例外になるのかわからない。
そこで、Ada 2012のPre
aspectを使って事前条件を表明。
function Based_Log (Base, X : Float) return Float
with Pre => Base > 0.0 and then Base /= 1.0;
仕様部を見ただけで渡すべき引数がわかる。
しかし、この書き換えは問題を生む。
投げる例外が、明記していたArgument_Error
から事前条件が失敗した時に投げられるAssertion_Error
に変わってしまう。
raise expressionで解決。
function Based_Log (Base, X : Float) return Float
with Pre => (Base > 0.0 and then Base /= 1.0)
or else raise Argument_Error;
本体側もスッキリ。
function Based_Log (Base, X : Float) return Float is
begin
return Log2 (X) / Log2 (Base);
end Based_Log;
DBCの本家Eiffelで書くとすると……チェックに名前を付けることができ、この名前が例外の種類になる。
Based_Log (Base, X : REAL) : REAL
require
Argument_Error : Base > 0.0 and then Base /= 1.0;
Eiffelでは、DBCと例外メカニズムは一体のもの。
ここを真似てない劣化コピーが多すぎる。
似たような話。
従来(Ada 2005まで)は、連続した範囲のsubtypeは作れても
subtype Positive is Integer range 1 .. Integer'Last;
subtype Negative is Integer range Integer'First .. -1;
ゼロ以外は正負OK、みたいな途中抜かしはできなかった。
subtype Non_Zero is Integer
range Integer'First .. -1 | 1 .. Integer'Last; -- error!
Ada 2012では、subtypeの制約に自由な式を書けるようになった。
subtype Non_Zero is Integer
with
Static_Predicate =>
Non_Zero in Integer'First .. -1 | 1 .. Integer'Last;
静的に評価できるものはStatic_Predicate
、それ以外はDynamic_Predicate
と使い分ける。
predicateも、投げられる例外はAssertion_Error
。
X : Non_Zero := 0; -- Assertion_Error
事前条件と似たような問題。
ちなみに従来のsubtypeの範囲エラーはConstraint_Error
。
raise expressionでOKでは?
subtype Non_Zero is Integer
with
Static_Predicate =>
(Non_Zero in Integer'First .. -1 | 1 .. Integer'Last)
or else raise Constraint_Error;
……これでいいと思われていた。
subtypeの用途はいろいろある。例えば条件式でも使える。
if X in Non_Zero then -- 例外投げちゃダメ
...
else
... -- X = 0の時はここが実行されないと
end if;
predicateの式で例外投げちゃダメ!
predicate失敗時に例外を投げる場所が別に用意された。
subtype Non_Zero is Integer
with
Static_Predicate =>
(Non_Zero in Integer'First .. -1 | 1 .. Integer'Last)
Predicate_Failure =>
raise Constraint_Error;
(predicate式中のraise expressionは文脈によってはFalse
扱い案もあったがPredicate_Failure
が勝利)
この話はまだ続く。
ところで、Dynamic_Predicate
には何でも書けるわけですが……
ん?今何でも(ry
procedure Read_A_From_File (File : File_Type)
with Pre => Is_Open (File)
and then Mode (File) = In_File;
procedure Read_B_From_File (File : File_Type)
with Pre => Is_Open (File)
and then Mode (File) = In_File;
procedure Read_C_From_File (File : File_Type)
with Pre => Is_Open (File)
and then Mode (File) = In_File;
開いている状態のファイルを渡して欲しいが……
事前条件だと、並ぶと結構くどい。
subtype Open_File_Type is File_Type
with
Dynamic_Predicate =>
Is_Open (Open_File_Type),
Predicate_Failure =>
raise Status_Error;
ファイルが開いている状態のsubtypeとか……
subtype Input_File_Type is Open_File_Type
with
Dynamic_Predicate =>
Mode (Input_File_Type) = In_File,
Predicate_Failure =>
raise Mode_Error;
subtype Output_File_Type is Open_File_Type
with -- (省略)
更に入力/出力モードで限定するとか……
procedure Read_A_From_File (File : Input_File_Type);
procedure Read_B_From_File (File : Input_File_Type);
procedure Read_C_From_File (File : Input_File_Type);
すっきり。
ええぇ!?それってsubtypeじゃなくね?
理論的に考えて。
細けえことはいいんだよ!
(Open_File_Type
/Input_File_Type
/Output_File_Type
は実際にCorrigendum 2014のドラフトに載ってますマジで)
だからこんなif文も
if Is_Open (File) then
...
こう書き換える事もできる。
if File in Open_File_Type then
...
チェックがしたいだけならOpen_File_Type (File)
と型変換するだけでもOK。
何でもかんでもsubtype。
procedure Read_A_From_File (File : Input_File_Type);
procedure Read_B_From_File (File : Input_File_Type);
procedure Read_C_From_File (File : Input_File_Type);
例えばRead_A_From_File
がRead_B_From_File
を呼ぶとき、事前条件では呼び出し毎に毎回チェックがなされるが、subtypeでは同じInput_File_Type
なのでチェックも省かれることが期待できる。
チェックが必要なのは最初にFile_Type
からInput_File_Type
になる時の1回だけ。
(もちろんコンパイラの機嫌が良ければ)
variadic functionってのは可変長引数のこと。
要するにprintf
のこと。
use Interfaces;
function printf (format: C.Strings.chars_ptr)
return C.int;
function printf (format: C.Strings.chars_ptr; a1 : C.int)
return C.int;
pragma Import (C, printf);
今まではこんな風にしてprintf
を呼べていた。
ところが64bit環境下ではクラッシュする!
64bit環境下ではクラッシュする!ナンデ!?
System V AMD64 ABIでは、eaxに浮動小数点数の引数の数を入れないといけない。
でたらめな値が入っていると、その数のXMMレジスタをどうこうしようとしてクラッシュする。
C言語からでも自分でプロトタイプ宣言を書く場合
int printf (char const *, ...);
は動くが、...
を省いた
int printf (char const *);
はクラッシュする!
(警告は出ます)
なので、インラインアセンブラでeaxをゼロクリア。
System.Machine_Code.Asm ("xorl %eax, %eax");
printf ("X = %d" & lf & nul, X);
これで一応動く。
……最適化の機嫌次第で。
pragma Import
で使える呼び出し規約にC_Variadic_N
が追加される。
(Nは固定の引数の数)
use Interfaces;
function printf (format: C.Strings.chars_ptr)
return C.int;
function printf (format: C.Strings.chars_ptr; a1 : C.int)
return C.int;
pragma Import (C_Variadic_1, printf);
System.Multiprocessors.Dispatching_Domains
にタスク毎のCPUの範囲を限定する機能が入る。
4コアの場合、タスクAは1 or 2、タスクBは3 or 4を使え等。
package Pure_Package is
pragma Pure;
...
end Pure_Package;
packageをPureにすると、色々嬉しい。
packageをPureにすると、グローバル変数、Pureではないpackageの使用が禁止される。
これにより、型がRemote Call Interfaceに使えるようになったりもする。
関数が__attribute__((pure))
扱いになったりもする。
ところで、C++で言うところのmutable
を実現するテクニックとしてこういうのがある。
type T is record
Mutable : access T := T'Access;
Var : Integer;
end record;
デフォルト値として自分自身への非constantなアクセスを獲得しておく。
X.Mutable.Var := 10;
X
がconstantであってもMutable
経由で変更できる。
Pureなpackage内でこれをやると、グローバル変数が存在できてしまっていた!
package Pure_Package is
pragma Pure;
X : constant T := (
Mutable => <>, -- デフォルト値を使用
Var => 0);
end Pure_Package;
このPure_Package.X
はconstantなのに変更可能!
昔っからの話ながら、ようやく禁止されます。
めでたしめでたしなのかは不明。
accessibility checkまわりの修正。
Type_Invariantまわりの修正。
RPCまわりの修正。
pragma
からaspectへの移行の漏れ修正。
構文の曖昧なところを修正。
他、微調整多数。
Ada 2005 の復習
Ada 2012 の復習
Ada Corrigendum 2014
☞ 未解決のIssues
前のAda 2012 Language Updateスライドを見てくださった方は、アレどうなったの?という疑問を覚えていて……くれてませんよね……。
(自意識過剰)
stuct T {
vector<T> children;
};
別にC++でも規格で保証されているわけではないが、実装によってはできる。最悪BoostでもOK。
Adaでは絶対できない。
type T; -- この時点でTはincomplete type
package T_Vectors is new Vectors (T); -- エラー!
type T is record -- Tの実体
children : T_Vectors.Vector;
end record;
record型の宣言とgeneric packageのインスタンス化の構文が別になっているため、recordの宣言の途中でインスタンス化できない。
Ada 2012ではincomplete type(Cのopaque type)をgenericで使えるようにはなったが、結局Vectors
は完全な型の情報を必要とするため無力。
色々案は出た。
with private
, limited new
, private new
, end private
, pragma May_Be_Partial
……
……が、全部没ったorz
代わりに木構造だけを実現するAda.Containers.Multiway_Trees
が2012で入って、Corrigendum 2014でも細々微修正されたりする。
そんなのJ節送りにしてこっち進めてくださいよう……。
(※Annex JはObsolescent Features)
要するにDの public import
、OCamlの include
。
まともなモジュール機能を持つAdaでは、#include
のネストみたいなことができない。
更にpackageが小分けになる傾向のあるAdaでは、with
節やrenames
を大量にダラダラ並べる羽目になる。
そうして提案された“Integrated” nested packages は絶対欲しい機能……だった。(過去形)
激しい議論の末、決まらないまま2012年になってしまった。タイムアップ。
AI12- として再開される様子も無く絶賛凍結中。
……の中で、これは欲しい、というやつ。
generic
type Rec is record
Next : access Rec;
end record;
package Intrusive_Lists is
...
特定の要素を持つrecordを要求するgeneric。
type T is record
Next_In_The_List : access T;
Next_In_Another : access T;
end record;
package List_1 is
new Intrusive_Lists (T (Next => Next_In_The_List));
package List_2 is
new Intrusive_Lists (T (Next => Next_In_Another));
インスタンス化の際に別の名前にもできる。
Data : array (1 .. 10) of Integer := (
for I in 1 .. 5 => I,
for I in 6 .. 10 => 12 - I);
↑と↓は同じになる。
Data : array (1 .. 10) of Integer := (
1, 2, 3, 4, 5, 6, 5, 4, 3, 2);
Mapped : array (Data'Range) of Integer := (
for I in Data'Range => Data (I) * 2);
お手軽なmap関数の代わりにも。
Ada 2012で追加されたfunction expressionをconstexpr扱いにする。
function Bitwidth (X : Integer) return Integer is
(if X <= 1 then 0 else Bitwidth ((X + 1) / 2) + 1);
type T is mod 10;
for T'Size is Bitwidth (10); -- コンパイル時に計算
Adaではsubtypeの範囲や配列のサイズ等の決定は実行時まで遅らせてよいので実はC++程の需要は無い。
とはいえ'Size
属性等、静的に決める必要があるものも少しはある。コードサイズの削減にもなる。
それになにより、constexprですよconstexpr!
ご清聴ありがとうございました。
Thanks to ななかなn+ε号機さん (@n7k7)
Use a spacebar or arrow keys to navigate