Knowledge Base

“Unit” specific typing in Delphi

Primitive types have default conversion rules, a lot of programming languages work this way by default. This is so that you don’t have to typecast everything and it’s really a convenience. For example, you’ll be able to assign an Integer to an Extended without trouble, both by passing a literal 42 as well as assigning a variable of type Integer.

procedure Hello;
var
  SomeNumber: Extended;
  AnotherNumber: Integer;
begin
  AnotherNumber := 6;
  SomeNumber := 2; // works
  SomeNumber := AnotherNumber; // works
end;

Even if do type aliasing, the types are still the types they were and have automatic conversion rules:

type
  ANewType = Extended;
  AnotherNewType = Integer;

implementation

procedure Hello;
var
  SomeNumber: ANewType;
  AnotherNumber: AnotherNewType;
begin
  AnotherNumber := 6;
  SomeNumber := 2; // works
  SomeNumber := AnotherNumber; // works
end;

If you really want strict conversion rules, because for example you want to avoid meters being assigned to seconds or vice versa, you will need to create new types. And new types are created by using either a record or a class.

To allow certain conversions, you can add those rules to the types using operator overloading. (see https://docwiki.embarcadero.com/RADStudio/Sydney/en/Operator_Overloading_(Delphi))

In FPC the syntax is different, but you can do the same https://www.freepascal.org/docs-html/ref/refse104.html#x224-24800015.3

You can then even make conversion rules for meters * seconds creating a meters per second type.

Here’s an example of how it works in Delphi:

 

type
  metersPerSecond = record
  private
    m: Extended;
    s: Extended;
 
    value: extended;
 
    mset: Boolean;
    sset: Boolean;
 
    function Units: string;
  public
    function ToString: string;
  end;
 
  meters = record
  private
    value: Extended;
 
    function Units: string;
  public
    class operator Implicit(Value: Extended): meters;
    class operator Explicit(Value: meters): metersPerSecond;
    class operator Multiply(ms: metersPerSecond; m: meters): metersPerSecond;
 
    function ToString: string;
  end;
 
  seconds = record
  private
    value: Extended;
 
    function Units: string;
  public
    class operator Implicit(Value: Extended): seconds;
    class operator Explicit(Value: seconds): metersPerSecond;
    class operator Multiply(ms: metersPerSecond; s: seconds): metersPerSecond;
 
    function ToString: string;
  end;
 
implementation
 
uses
  SysUtils;
 
class operator meters.Implicit(Value: Extended): meters;
begin
  Result.value := Value;
end;
 
class operator seconds.Implicit(Value: Extended): seconds;
begin
  Result.value := Value;
end;
 
class operator meters.Explicit(Value: meters): metersPerSecond;
begin
  Result.m := Value.value;
  Result.mset := True;
  if Result.sset then
    Result.value := Result.s * Result.m; 
end;
 
class operator seconds.Explicit(Value: seconds): metersPerSecond;
begin
  Result.s := Value.value;
  Result.sset := True;
  if Result.mset then
    Result.value := Result.s * Result.m; 
end;
 
class operator seconds.Multiply(ms: metersPerSecond; s: seconds): metersPerSecond;
begin
  Result := ms;
  Result.s := s.value;
  Result.sset := True;
  if Result.mset then
    Result.value := Result.s * Result.m; 
end;
 
class operator meters.Multiply(ms: metersPerSecond; m: meters): metersPerSecond;
begin
  Result := ms;
  Result.m := m.value;
  Result.mset := True;
  if Result.sset then
    Result.value := Result.s * Result.m; 
end;
 
function metersPerSecond.Units: string;
begin
  Result := 'm/s';
end;
 
function metersPerSecond.ToString: string;
begin
  Assert(mset and sset);
 
  Result := Format('%f%s', [value, Units])
end;
 
function meters.Units: string;
begin
  Result := 'm';
end;
 
function meters.ToString: string;
begin
  Result := Format('%f%s', [value, Units]);
end;
 
function seconds.Units: string;
begin
  Result := 's';
end;
 
function seconds.ToString: string;
begin
  Result := Format('%f%s', [value, Units]);
end;
 
var
  time: seconds;
  distance: meters;
  speed: metersPerSecond;
  different_speed: metersPerSecond;
initialization
  time := 10.0;
  distance := 3;
 
  // distance := meters; // does not work, because seconds are not meters!
 
  speed := metersPerSecond(time) * distance;
 
  // or the other way around
  speed := metersPerSecond(distance) * time;
 
  WriteLn('speed -> ' + time.ToString + ' * ' + distance.ToString + ' = ' + Speed.ToString);
 
  // different_speed := distance; // this doesn't work without explicit conversion
 
  WriteLn('different_speed -> ' + different_speed.ToString);
end.

Written by Patrick Quist
Delphi developer

Contact

Let us help you to realise your ambitions

GDK Software UK

(+44) 20 3355 4470

GDK Software USA

+1 (575) 733-5744