如何绕过 Ada 中标记记录的禁止判别式默认值?

How to get around forbidden discriminants defaults for tagged records in Ada?

我正在学习 Ada,但遇到了设计问题。对不起,我不了解基本的 Ada 机制和习语。

假设我想表示一个操作。运算符可以是 plusminus,操作数可以是整数或字符串。

免责声明:有些事情在语义层面上可能没有多大意义(minus 字符串,没有操作数的运算符,......)但这都是关于表示的。

目前我有以下不正确代码:

operand.ads:

package Operand is

   --  I want a None for unary operands or operators without operands
   type Operand_Type is (Int, Str, None);

   --  This needs to be tagged
   type Instance (Op_Type : Operand_Type := None) is tagged record
      case Op_Type is
         when Int =>
            Int_Value : Integer;
         when Str =>
            Str_Value : String (1 .. 10);
         when None =>
            null;
      end case;
   end record;

   --  Some methods on operands...

end Operand;

operation.ads:

with Operand;

package Operation is

   type Operation_Type is (Plus, Minus);

   --  This also needs to be tagged
   type Instance is tagged record
      Left, Right : Operand.Instance;
   end record;

   --  Some methods on operations...

end Operation;

main.adb:

with Operand;
with Operation;

procedure Main is
   Op : Operation.Instance;
begin
   Op.Left := (Op_Type => Operand.Int, Int_Value => 1);
   Op.Right := (Op_Type => Operand.Int, Int_Value => 3);
end Main;

当我尝试编译时出现以下错误:

$ gnatmake main.adb
gcc -c main.adb
operand.ads:7:45: discriminants of nonlimited tagged type cannot have defaults
operation.ads:9:28: unconstrained subtype in component declaration
gnatmake: "main.adb" compilation error

我明白为什么我不能在标记类型的判别式上使用默认值,但我真的不知道如何绕过这个限制。

提案 1:

停止使用变体记录并为每个操作数使用一个字段的记录。但我觉得这只是抛弃了代码的优雅。

提案 2:

Operand.Instance 记录中删除默认值并从 Operation.Instance 记录中限制 LeftRight。但是我收到运行时错误:

raised CONSTRAINT_ERROR : main.adb:7 discriminant check failed

因为我无法动态更改记录字段的判别值。

如有任何帮助,我们将不胜感激!

Jim Rogers 已经讨论过使用继承。如果愿意,您还可以通过创建内部非标记类型(允许默认值)来使用组合,将 Operand.Instance 类型标记为私有,让私有实现使用内部非标记版本,然后添加您需要的操作设置和获取操作数:

with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is

    package Operand is

        --  I want a None for unary operands or operators without operands
        type Operand_Type is (Int, Str, None);
        Invalid_Operand : exception;
        
        --  This needs to be tagged
        type Instance is tagged private;
        function Int_Value(Value : Integer) return Instance;
        function Str_Value(Value : String) return Instance;
        function Null_Instance return Instance;
        
        function Int_Value(Self : Instance) return Integer;
        function Str_Value(Self : Instance) return String;
        function Op_Type(Self : Instance) return Operand_Type;
        
        --  Some methods on operands...
        
    private
    
        
        type Instance_Internal (Op_Type : Operand_Type := None) is record
            case Op_Type is
                when Int =>
                    Int_Value : Integer;
                when Str =>
                    Str_Value : String (1 .. 10);
                when None =>
                    null;
            end case;
        end record;
        
        type Instance is tagged record
            Impl : Instance_Internal;
        end record;
        
        function Int_Value(Value : Integer) return Instance is (Impl => (Int, Value));
        function Str_Value(Value : String)  return Instance is (Impl => (Str, Value));
        function Null_Instance              return Instance is (Impl => (Op_Type => None));
        
        function Int_Value(Self : Instance) return Integer
            is (if Self.Impl.Op_Type = Int then Self.Impl.Int_Value else raise Invalid_Operand);
        function Str_Value(Self : Instance) return String
            is (if Self.Impl.Op_Type = Str then Self.Impl.Str_Value else raise Invalid_Operand);
        function Op_Type(Self : Instance) return Operand_Type
            is (Self.Impl.Op_Type);

    end Operand;
    
    package Operation is

        type Operation_Type is (Plus, Minus);
    
        --  This also needs to be tagged
        type Instance is tagged record
            Left, Right : Operand.Instance;
        end record;
    
       --  Some methods on operations...
    
    end Operation;
    
    Op : Operation.Instance;

begin
    Put_Line("Hello, world!");
    
    Op.Left  := Operand.Int_Value(1);
    Op.Right := Operand.Int_Value(3);
    
    Put_Line(Integer'Image(Op.Left.Int_Value));
    Put_Line(Integer'Image(Op.Right.Int_Value));
end Hello;

您可以将 Operand 包分解为 spec 和 body 以获得更好的可读性,这只是为了举例。