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

Yun*_*ama 6 ada

我正在学习 Ada,遇到了一个设计问题。抱歉,我不了解基本的 Ada 机制和习惯用法。

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

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

目前我有以下错误代码:

操作数.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;
Run Code Online (Sandbox Code Playgroud)

操作.广告:

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;
Run Code Online (Sandbox Code Playgroud)

主.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;
Run Code Online (Sandbox Code Playgroud)

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

$ 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
Run Code Online (Sandbox Code Playgroud)

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

建议一:

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

建议2:

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

raised CONSTRAINT_ERROR : main.adb:7 discriminant check failed
Run Code Online (Sandbox Code Playgroud)

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

任何帮助将非常感激 !

Jer*_*ere 5

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;
Run Code Online (Sandbox Code Playgroud)

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