Delphi中的三值逻辑

Posted

技术标签:

【中文标题】Delphi中的三值逻辑【英文标题】:Three valued logic in Delphi 【发布时间】:2013-11-01 09:54:56 【问题描述】:

如何在 Delphi 中最好地实现 three valued logic?

我在想

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 效率对我来说并不重要。

【问题讨论】:

我认为使用枚举没有任何问题。而且很容易扩展。 (不完全是 3 个有价值的逻辑)我首先工作的性别是:“男性,女性”然后变成:“男性,女性,未知”,然后变成:“男性,女性,未知,不适用。” @PieterB ...但是这两个答案允许更优雅的用法。而且我认为它比男性/女性更“通用”。 非常感谢您的两个答案,我不知道 D2006 中提供了运算符重载(但它是:)。这两个答案似乎都提供了我正在寻找的优雅解决方案(+1)。我会稍微修改一下,然后再决定最终接受哪一个。 我会将 Unknown 放在 True/False 之前 - 如果您忘记设置值的结果,它将始终默认为枚举的第一个值。不利的一面是序数值会偏离布尔枚举 @MattAllwood 值类型在为局部变量或返回值时不会默认初始化 【参考方案1】:

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

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);

哪个输出:

真的 未知

【讨论】:

不会 Delphi 抱怨 case Value of 并非所有值都包含在 .ToString 中吗?至少 XE2 在这种情况下倾向于抱怨,经过一番思考后我同意这是正确的。 @Arioch'The 哪个值没有被处理?您指的警告编号是多少? TTriBool.TTriBoolEnum(3), TTriBool.TTriBoolEnum(4), ... TTriBool.TTriBoolEnum(255) 在扩展旧代码后遇到一些未发现的情况后,我倾向于向前兼容(或者更确切地说是明确的向前不兼容)是一个很好的选择要拥有的东西。在那里添加未初始化的变量。是的,那是防御性编程,但 TS 说他不是很受 CPU 限制。我还在我的代码中的 cmets 中说明了这些问题。 @Arioch'The 哪个警告号? @Arioch'我的代码没有那个警告,因为返回值是一个字符串。根据前面的讨论,作为 var 参数传递。并初始化,因为它是托管类型。警告失败。在我自己的代码中,我总是有一个名为 RaiseAssertionFailed 的辅助函数的 else 子句,并且我会传递 Result,这会使编译器关闭。【参考方案2】:

作为。 elegancre 在这里是什么意思?实施的优雅还是使用的优雅或CPI效率或可维护性?优雅是一个很模糊的词……

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

但是,所需的功能可能在 Delphi 2006 之前或不久之前(本身就是一个有缺陷的版本),所以带上您的 DUnit 并进行大量测试..

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

    增强记录:When should I use enhanced record types in Delphi instead of classes? 和 http://delphi.about.com/od/adptips2006/qt/newdelphirecord.htm 和 manual 操作重载,包括隐式类型转换:What operator do I overload when assigning an "Enhanced Record" to a normal "Data Type" variable? 和 Operator Overloading in Delphi 和 manual 函数内联:what is use of inline keyword in delphi 和 manual

概述其中的一些想法:

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。上面未指定的检查是故意悲观的,倾向于在坏的方面犯错。它是针对未初始化变量和未来可能发生的变化的防御,添加的状态多于三个。 虽然 thise 似乎保护过度,但至少 Delphi XE2 同意 mee:请参阅类似情况下的警告:

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 效率对我来说并不重要。 非常感谢您的回答。两个答案都非常相似。我接受了另一个答案,因为我只能接受一个,而另一个代码示例对 IMO 来说更简洁一些。

以上是关于Delphi中的三值逻辑的主要内容,如果未能解决你的问题,请参考以下文章

SQL进价2:三值逻辑和null

MySQL NULL--三值逻辑(Three Value Logic)

读SQL进阶教程笔记02_三值逻辑和NULL

[人工智能] 停机问题罗素悖论和三值逻辑

读SQL进阶教程笔记12_地址与三值逻辑

数组的三值合一