Delphi中的三值逻辑

14

如何在Delphi中最佳实现三值逻辑

我正在考虑

type
  TExtBoolean = (ebTrue, ebFalse, ebUnknown);

使用

function ExtOr(A: TExtBoolean; B: TExtBoolean): TExtBoolean;
begin
  if (A = ebTrue) or (B = ebTrue) then
    Result := ebTrue
  else if (A = ebFalse) and (B = ebFalse) then
    Result := ebFalse
  else
    Result := ebUnknown;
end;

等等,这种方法似乎并不太优雅。是否存在更好的方法?

编辑:我所说的优雅是指易于使用。实现越优雅,越好。对于我来说,CPU效率并不是很重要。


我认为使用枚举没有问题。而且很容易扩展。(不完全是三值逻辑)在我的工作中,性别首先是“男性,女性”,然后变成了“男性,女性,未知”,然后变成了“男性,女性,未知,不适用”。 - Pieter B
@PieterB...但是这两个答案允许更优雅的使用。我认为它比男/女更“通用”。 - yonojoy
非常感谢您的两个答案,我不知道D2006中有运算符重载(但确实有:)。 两个答案似乎都提供了我正在寻找的优雅解决方案(+1)。 我会稍微尝试一下,然后决定最终接受哪一个。 - yonojoy
1
如果你忘记设置一个值的结果,它将始终默认为枚举的第一个值,因此我会在True/False之前放置Unknown。这样做的负面影响是序数值将偏离布尔枚举。 - Matt Allwood
1
@MattAllwood 当值类型作为局部变量或返回值时,它们不会被默认初始化。 - David Heffernan
2个回答

15

您可以使用运算符重载来实现增强记录。它看起来像这样:

type
  TTriBool = record
  public
    type
      TTriBoolEnum = (tbFalse, tbTrue, tbUnknown);
  public
    Value: TTriBoolEnum;
  public
    class operator Implicit(const Value: Boolean): TTriBool;
    class operator Implicit(const Value: TTriBoolEnum): TTriBool;
    class operator Implicit(const Value: TTriBool): TTriBoolEnum;
    class operator Equal(const lhs, rhs: TTriBool): Boolean;
    class operator LogicalOr(const lhs, rhs: TTriBool): TTriBool;
    function ToString: string;
  end;

class operator TTriBool.Implicit(const Value: Boolean): TTriBool;
begin
  if Value then
    Result.Value := tbTrue
  else
    Result.Value := tbFalse;
end;

class operator TTriBool.Implicit(const Value: TTriBoolEnum): TTriBool;
begin
  Result.Value := Value;
end;

class operator TTriBool.Implicit(const Value: TTriBool): TTriBoolEnum;
begin
  Result := Value.Value;
end;

class operator TTriBool.Equal(const lhs, rhs: TTriBool): Boolean;
begin
  Result := lhs.Value=rhs.Value;
end;

class operator TTriBool.LogicalOr(const lhs, rhs: TTriBool): TTriBool;
begin
  if (lhs.Value=tbTrue) or (rhs.Value=tbTrue) then
    Result := tbTrue
  else if (lhs.Value=tbFalse) and (rhs.Value=tbFalse) then
    Result := tbFalse
  else
    Result := tbUnknown;
end;

function TTriBool.ToString: string;
begin
  case Value of
  tbFalse:
    Result := 'False';
  tbTrue:
    Result := 'True';
  tbUnknown:
    Result := 'Unknown';
  end;
end;

一些使用示例:

var
  x: Double;
  tb1, tb2: TTriBool;

tb1 := True;
tb2 := x>3.0;
Writeln((tb1 or tb2).ToString);

tb1 := False;
tb2.Value := tbUnknown;
Writeln((tb1 or tb2).ToString);

输出结果为:

True
Unknown

你指的是哪个值没有被处理?你所提到的警告编号是多少? - David Heffernan
@Arioch:我的代码没有那个警告,因为返回值是字符串。正如早期讨论中所述,它作为变量参数传递并初始化,因为它是托管类型。警告失败。在我的代码中,我总是会有一个名为“RaiseAssertionFailed”的辅助函数的else子句,并且我会传递“Result”,这将关闭编译器的警告。 - David Heffernan
“关闭编译器”是“try ... except end;”背后的思想。这里的问题不是要摆脱警告,而是要消除警告只是症状的潜在危险。缺乏警告并不意味着代码没有变得脆弱,它只意味着Delphi无法确定在这种情况下。当然,关于代码应该有多少防御措施,因人而异。个人认为,这些代码模式对于未来的维护者来说是跑步场上的车兵。再次强调,因人而异。 - Arioch 'The
一个TTriBool变量如果没有被赋值,是否期望它的状态为tbUnspecified - LU RD
@LURD 如果一个整数没有被赋值,是否期望它有任何特定的值?我们需要初始化变量。 - David Heffernan
显示剩余16条评论

5

AS. 在这里你所说的优雅是指实现的优雅还是使用的优雅还是CPI效率还是可维护性?优雅是一个非常模糊的词...

我认为让它更易于使用的明显方法是将类型转换为可像ExtBoolean1 or (ExtBoolean2 and True)一样使用。

然而,所需的功能可能在Delphi 2006之前(本身就相当有缺陷)或短时间内完成,因此请使用您的DUnit并进行大量测试。

要列出要使用的功能及其描述:

  1. 增强型记录:何时应该在Delphi中使用增强型记录类型而不是类?http://delphi.about.com/od/adptips2006/qt/newdelphirecord.htm手册
  2. 运算符重载,包括隐式类型转换:将“增强型记录”分配给普通“数据类型”变量时重载哪个运算符?Delphi中的运算符重载手册
  3. 函数内联:delphi中inline关键字的用途是什么手册

概述其中一些想法:

type
  TExtBoolean = record
     Value: (ebUnknown, ebTrue, ebFalse);

     function IsNull: boolean; inline;
     function Defined: boolean; inline;

     class operator Implicit ( from: boolean ): TExtBoolean; inline;
     class operator Implicit ( from: TExtBoolean ): boolean; 
     class operator LogicalAnd( Value1, Value2: TExtBoolean ):   TExtBoolean; 
     class operator LogicalAnd( Value1: TExtBoolean; Value2: boolean):  TExtBoolean; inline;
     class operator LogicalAnd( Value1: boolean; Value2: TExtBoolean ):   TExtBoolean; 
....
  end;

const Unknown: TExtBoolean = (Value: ebUnknown); 

...
var v1: TExtBoolean;
    v1 := False; 
    v1 := True;
    v1 := Unknown;
...

class operator TExtBoolean.Implicit ( from: boolean ): TExtBoolean; 
begin
  if from
     then Result.Value := ebTrue
     else Result.Value := ebFalse
end;

class operator TExtBoolean.Implicit ( from: TExtBoolean ): Boolean; 
begin
  case from.Value of
    ebTrue: Result := True;
    ebFalse: Result := False;  
    else raise EConvertError.Create('....');
end;


function TExtBoolean.Defined: boolean; 
begin
  Result := (Self.Value = ebTrue) or (Self.Value = ebFalse);
end;

// this implementation detects values other than ebTrue/ebFalse/ebUnkonwn
// that might appear in reality due to non-initialized memory garbage 
// since hardware type of Value is byte and may be equal to 3, 4, ...255
function TExtBoolean.IsNull: boolean; 
begin
  Result := not Self.Defined
end;

class operator TExtBoolean.And( Value1, Value2: TExtBoolean ): TExtBoolean; 
begin
  if Value1.IsNull or Value2.IsNull
     then Result.Value := eb.Undefined
     else Result := boolean(Value1) and boolean(Value2);
// Or, sacrificing readability and safety for the sake of speed
// and removing duplicate IsNull checks
//   else Result := (Value1.Value = ebTrue) and (Value2.Value = ebTrue);
end;

class operator TExtBoolean.LogicalAnd( Value1, TExtBoolean; Value2: boolean):  TExtBoolean;
begin
  Result := Value2 and Value1;
end;

class operator TExtBoolean.LogicalAnd( Value1: boolean; Value2: TExtBoolean ):   TExtBoolean; 
begin
  if Value2.IsNull
     then Result := Value2
     else Result := Value1 and (Value2.Value = ebTrue);
// or if to accept a duplicate redundant check for readability sake
//   and to avert potential later erros (refactoring, you may accidentally remove the check above)
//    else Result := Value1 and boolean (Value2);
end;

PS. 上面的未指定检查是故意悲观的,倾向于犯错。 这是防止非初始化变量和可能的未来更改添加更多状态而采取的措施。 虽然这似乎过度保护,但至少Delphi XE2与我达成了一致:在类似情况下,请参见警告:

program Project20;  {$APPTYPE CONSOLE}
uses System.SysUtils;

type enum = (e1, e2, e3);
var e: enum;

function name( e: enum ): char;
begin
  case e of
    e1: Result := 'A';
    e2: Result := 'B';
    e3: Result := 'C';
  end;
end;

// [DCC Warning] Project20.dpr: W1035 Return value of function 'name' might be undefined

begin
  for e := e1 to e3
      do Writeln(name(e));
  ReadLn;
end.

我所说的优雅是指易于使用。实现越优雅,越好。对我来说,CPU效率并不是那么重要。 - yonojoy
非常感谢您的回答。两个答案非常相似。我接受了另一个答案,因为我只能接受一个,而且另一个代码示例在我看来更加简洁。 - yonojoy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接