9

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.

Basically, I want a parameter with three possible states; true, false or "unspecified".

awmross
  • 3,789
  • 3
  • 38
  • 51

5 Answers5

18

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;
Serhii Kheilyk
  • 933
  • 1
  • 8
  • 24
  • +1 This is the best way to achieve this, IMO. There are three states: `doSomething(1)`, `doSomething(1, True)` and `doSomething(1, False)`. – Rudy Velthuis Sep 22 '11 at 09:14
  • @Rudy, yes, but only because the OP specified the need to work with *Boolean* parameters. Otherwise I think 3 distinct code paths are more efficiently differentiated by using a single 3 state variable (such as Greg Hewgill's ThreeStateBoolean example). – Sam Sep 22 '11 at 10:53
  • @Rudy, what makes this the better answer is that it complies with the OP's sentence "sometimes we want an optional parameter", not the fact that three states are supported (because both answers do that). IMO. – Sam Sep 22 '11 at 11:00
  • @Sam: sure, it is not only because of three states, it is because the Boolean is not always required. Both ways will work: ThreeStateBoolean or the one above. I like the one above better. – Rudy Velthuis Sep 22 '11 at 12:25
  • The advantage of this method over a custom "ThreeStateBoolean" type, is that this method does not require additional dependencies. It would be good if the core Delphi libraries had something like this. – awmross Sep 22 '11 at 23:49
4

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.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • only the first part of you answer is correct, you can't declare a function like this in delphi , you must use predefined types in the parameters like `PBoolean ` so must be something like `procedure doSomething2(bar: PBoolean = nil);` – RRUZ Sep 22 '11 at 02:05
  • Thanks, I'll change that. It's been a very long time since I've coded in Pascal... – Greg Hewgill Sep 22 '11 at 02:05
  • 3
    type ThreeStateBoolean = (True, False, Unspecified); Note however that you cannot actually use True and False without overwriting the system boolean True and False declarations. This will give compilation errors wherever you actually use booleans. TTrue ,TFalse, Unspecified will work. – HMcG Sep 22 '11 at 11:12
4

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.
Community
  • 1
  • 1
HMcG
  • 2,062
  • 4
  • 24
  • 36
0

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

Community
  • 1
  • 1
Sasha
  • 3,599
  • 1
  • 31
  • 52
0

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").

Jerry Gagnon
  • 1,131
  • 8
  • 16