开发者

Pass an optional boolean variable

开发者 https://www.devze.com 2023-04-06 08:59 出处:网络
Sometimes we want an optional parameter function doSomething(foo:Integer; bar:TObject=nil) begin if bar <> nil then // do something optional with bar

Sometimes we want an optional parameter

function doSomething(foo:Integer; bar:TObject=nil)
begin
    if bar <> nil then // do something optional with bar
    ....
end

How do I do the equivalent with a boolean, that allows me to differentiate between the two boolean values and "no value"?

function doSomething(foo:Integer; bar:Boolean=nil) // Won't compile
begin
    if bar <> nil then // do something optional with bar
end

Obviously this won't compile as a Boolean cannot be nil.

Basica开发者_StackOverflowlly, I want a parameter with three possible states; true, false or "unspecified".


You can do this other way, using overloading:

function doSomething(foo:Integer): Boolean; overload;
begin
  // do something without bar
end;

function doSomething(foo:Integer; bar:Boolean): Boolean; overload
begin
  // do something optional with bar
end;

Then you can use it as doSomething(1) , as well as doSomething(1, true)

Using your example, it will be equivalent to:

function doSomething(foo:Integer; bar:Boolean=nil): Boolean; // Won't compile
begin
    if bar <> nil then 
      // do something optional with bar
    else
      // do something without bar
end;


Values of the Boolean type can only be True or False. You can define your own type that has three states: True, False, and Unspecified:

type ThreeStateBoolean = (True, False, Unspecified);

Or, you can pass a pointer to a Boolean:

type PBoolean = ^Boolean;

function doSomething(bar: PBoolean = nil)
begin
    if bar <> nil then
        // do something with bar^
end

Passing a pointer to a Boolean might be awkward depending on how you're calling it.


Another option (if you have a relatively modern version to Delphi) is to implement this as a record, with implicit conversion to and from boolean values. With operator overloading, you can also enable 3-state logic. This is overkill if all you require is occasional use, but if you do need a three-state logic system it works very nicely, particularly as you can assign boolean values to it. Be careful with assignments from 3-state to 2-state thought. The example below assigns False to a Boolean <- 'Troolean' assignation where the troolean is TNil, as per an unassigned boolean in Delphi, but there are obvious complications.

Please note that this is not a complete or efficient implementation by any means, it's just a demo to indictate what is possible. Incidentally, there is a good CodeRage vidoe by Jeroen Pluimers on nullable types. This question provides a link.

unit UnitTroolean;

interface

type

  TTroolean = record
    private type
      TThreeState = (TTrue = 1, TFalse = 0, TNil = -1);

    var
      fThreeState: TThreeState;
    public
      function AsString: string;
      class operator Implicit(Value: boolean): TTroolean;
      class operator Implicit(Value: TTroolean): boolean;
      class operator Implicit(Value: TThreeState): TTroolean;
      class operator Implicit(Value: TTroolean): TThreeState;
      class operator LogicalAnd(Left, Right: TTroolean): TTroolean;
      class operator LogicalOr(Left, Right: TTroolean): TTroolean;
      class operator LogicalNot(Value: TTroolean): TTroolean;
  end;

implementation

{ TRoolean }

class operator TTroolean.Implicit(Value: boolean): TTroolean;
begin
  if Value then
    result.fThreeState := TTrue
  else
    result.fThreeState := TFalse;
end;

class operator TTroolean.Implicit(Value: TTroolean): boolean;
begin
  if not(Value.fThreeState = TNil) then
    result := (Value.fThreeState = TTrue)
  else
    result := false;
end;

class operator TTroolean.Implicit(Value: TThreeState): TTroolean;
begin
  result.fThreeState := Value;
end;

class operator TTroolean.Implicit(Value: TTroolean): TThreeState;
begin
  result := Value.fThreeState;
end;

class operator TTroolean.LogicalAnd(Left, Right: TTroolean): TTroolean;
begin
  if (Left.fThreeState = TNil) or (Right.fThreeState = TNil) then
    result.fThreeState := TNil
  else if ((Left.fThreeState = TTrue) and (Right.fThreeState = TTrue)) then
    result.fThreeState := TTrue
  else
    result.fThreeState := TFalse;
end;

class operator TTroolean.LogicalNot(Value: TTroolean): TTroolean;
begin
  begin
    case value.fThreeState of
    TNil: result.fThreeState:= TNil;
    TTrue: result.fThreeState:= TFalse;
    TFalse: result.fThreeState:= TTrue
    end;
  end;

end;

class operator TTroolean.LogicalOr(Left, Right: TTroolean): TTroolean;
begin
  if (Left.fThreeState = TNil) or (Right.fThreeState = TNil) then
    result.fThreeState := TNil
  else if ((Left.fThreeState = TTrue) or (Right.fThreeState = TTrue)) then
    result.fThreeState := TTrue
  else
    result.fThreeState := TFalse;
end;

function TTroolean.AsString: string;
begin
  case ord(fThreeState) of
    1:
      result := 'TTrue';
    0:
      result := 'TFalse';
    -1:
      result := 'TNil';
  end;
end;

end. 

And an example of use

program ThreeStateLogicTest;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  UnitTroolean in 'UnitTroolean.pas';

var
  ABoolean: boolean;
  ATroolean, Anothertroolean, AThirdTroolean: TTroolean;

begin

  try
    { TODO -oUser -cConsole Main : Insert code here }

    write('Boolean:', BoolToStr(ABoolean, true), #10#13);
    write(#10#13);

    ATroolean := TFalse;
    ABoolean := true;
    ATroolean := ABoolean;

    write('Boolean:', BoolToStr(ABoolean, true), #10#13);
    write('Troolean:', ATroolean.AsString, #10#13);
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
    write(#10#13);

    ATroolean := TTrue;
    ABoolean := false;
    ATroolean := ABoolean;

    write('Boolean:', BoolToStr(ABoolean, true), #10#13);
    write('Troolean:', ATroolean.AsString, #10#13);
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
    write(#10#13);

    ABoolean := false;
    ATroolean := TTrue;
    ABoolean := ATroolean;

    write('Boolean:', BoolToStr(ABoolean, true), #10#13);
    write('Troolean:', ATroolean.AsString, #10#13);
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
    write(#10#13);

    ABoolean := true;
    ATroolean := TFalse;
    ABoolean := ATroolean;

    write('Boolean:', BoolToStr(ABoolean, true), #10#13);
    write('Troolean:', ATroolean.AsString, #10#13);
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
    write(#10#13);

    ABoolean := false;
    ATroolean := Tnil;
    ABoolean := ATroolean;

    write('Boolean:', BoolToStr(ABoolean, true), #10#13);
    write('Troolean:', ATroolean.AsString, #10#13);
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
    write(#10#13);

    ABoolean := true;
    ATroolean := Tnil;
    ABoolean := ATroolean;

    write('Boolean:', BoolToStr(ABoolean, true), #10#13);
    write('Troolean:', ATroolean.AsString, #10#13);
    write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
    write(#10#13);

    ATroolean := TTrue;
    Anothertroolean := false;

    AThirdTroolean := ATroolean and Anothertroolean;
    write('And:', AThirdTroolean.AsString, #10#13);

    AThirdTroolean := ATroolean or Anothertroolean;
    write('Or:', AThirdTroolean.AsString, #10#13);

    ATroolean := TNil;
    Anothertroolean:= not ATroolean;
    write('Not TNil:', Anothertroolean.AsString, #10#13);

    ATroolean := TTrue;
    Anothertroolean:= not ATroolean;
    write('Not Ttrue:', Anothertroolean.AsString, #10#13);

    ATroolean := Tfalse;
    Anothertroolean:= not ATroolean;
    write('Not Tfalse:', Anothertroolean.AsString, #10#13);



    readln;

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.


Or... you could use an Integer and cast it to a Boolean as necessary. Use 0 for false, 1 for true, and -1 for "nil". Anyway you slice it, you can't use a Boolean variable to do what you want, you will need another type or a manipulation of parameters as lynxnake suggested.

EDIT: A variation on this, which is very inefficient, would be to use a Variant. With a variant you can pass in Null values (similar to nil in some ways but still a value) as well as Unassigned (also similar to nil, but representing "no value").


The cleanest way is to use enumeration (a.k.a. enumerated type).

This is already shown in the Greg Hewgill's answer — but incorrectly, you shouldn't use predefined false and true as enumeration identifiers¹. And within the HMcG's answer — but within the wrapper type (more complex example). I suggest writing something like: type TTrilean = (triFalse, triTrue, triNull);.

Alternatively, you can use existing TCheckBoxState type from StdCtrls module, if you don't mind bringing VCL modules into your project.


Additionally you can write wrapper functions per the Serhii Kheilyk's answer:

procedure DoSomething(Foo: Integer; Bar: TTrilean); overload;
begin
    … //main code
end;

procedure DoSomething(Foo: Integer; Bar: Boolean); overload;
const
    Trileans: array[Boolean] of TTrilean = (triFalse, triTrue);
begin
    DoSomething(Foo, Trileans[Bar]);
end;

procedure DoSomething(Foo: Integer); overload;
begin
    DoSomething(Foo, triNull);
end;

You can even make the first one private and the last two public, if you wish.


Notes:
1. I think (not sure), formally you can write type TMyType = (False, True, Unspecified);, as False and True aren't reserved words. But this will break your access to original False and True of type Boolean (you'll need to refer them as System.False and System.True after that). And that is not third-party-compiler-compatible (e.g. FPC won't allow that).

0

精彩评论

暂无评论...
验证码 换一张
取 消