March 25, 2013

New in Smart 1.1: Property Expression

Smart Pascal supports properties just the same as Delphi and Free Pascal do. Properties are supported in classes, records, interfaces and in helpers (helper classes and records). In addition to the standard syntax, Smart Pascal supports property expressions which help you write more compact code.

Property expressions can be used in two ways – by providing a backing storage (typically a field or property containing the data) or by providing a modifying expression (an expression that modifies and returns/stores data). In both cases, property expressions are introduced by an opening parenthesis and ended by a closing parenthesis.

The simplest way to explain the functioning of this language extension is through examples. The following example defines a form containing two labels, W3lblTask and W3lblDetail. The form also defines two properties. Property Task provides access to the W3lblTask.Caption and property Detail provides access to the W3lblDetail.Caption.

type
TToDoListTemplate=class
(TW3form)
private
{$I 'ToDoListTemplate:intf'}
protected
procedure InitializeObject; override
;
public
property Task: string read
(W3lblTask.Caption) write (W3lblTask.Caption);
property Detail: string read
(W3lblDetail.Caption) write (W3lblDetail.Caption);
end
;

Equivalent code in a “classic” style would be quite longer.

type
TToDoListTemplate=class
(TW3form)
private
{$I 'ToDoListTemplate:intf'}
function GetDetail: string
;
function GetTask: string
;
procedure SetDetail(value: string
);
procedure SetTask(value: string
);
protected
procedure InitializeObject; override
;
public
property Task: string read GetTask write
SetTask;
property Detail: string read GetDetail write
SetDetail;
end
;

function TToDoListTemplate.GetDetail: string
;
begin
Result :=
W3lblDetail.Caption;
end
;

function TToDoListTemplate.GetTask: string
;
begin
Result :=
W3lblTask.Caption;
end
;

procedure TToDoListTemplate.SetDetail(value: string
);
begin
W3lblDetail.Caption :=
value;
end
;

procedure TToDoListTemplate.SetTask(value: string
);
begin
W3lblTask.Caption :=
value;
end
;

In the example above, (W3lblTask.Caption) and (W3lblDetail.Caption) provide a backing storage; a place where the data is stored. We can, however, do more and write a full expression inside the property expression-wrapping brackets. In case of a getter (read access) this expression must return a result and in case of the setter (write access) this expression should be a statement setting the value of the property. The setter can use a pseudo-parameter Value holding the value that was assigned to the property in the code.

I've said should be a statement because the compiler is not really fussy and will handle any expression in the setter. This allows for weird tricks where the assignment to a property produces a side effect.

The second example defines a TAngle record which holds one value (angle) stored in radians and exposes it through two properties, Radians returning the value in radians and Degrees returning the value in degrees. Conversion between radians and degrees is done inside property expressions.

type
TAngle =
record
FAngleRad:
float;
property Radians: float read FAngleRad write
FAngleRad;
property Degrees: float read (Radians/Pi*180
) write (Radians := Value/180*Pi);
end
;

Equivalent code in “classic” style would again be longer.

type
TAngle =
record
FAngleRad:
float;
functon GetDegrees:
float;
procedure SetDegrees(Value:
float);
property Radians: float read FAngleRad write
FAngleRad;
property Degrees: float read GetDegrees write
SetDegrees;
end
;

function TAngle.GetDegrees:
float;
begin
Result := Radians/Pi*180
;
end
;

procedure TAngle.SetDegrees(Value:
float);
begin
Radians := Value/180
*Pi;
end
;

March 22, 2013

New in Smart 1.1: Lambdas

Version 1.1 introduced more compact syntax for closures, similar to the C#'s syntax with an extra lambda keyword thrown at the front. Two different types of `lambda` closures are supported -- functions and statements.

Lambda Functions

Lambda functions are used when the code expects an anonymous function and when the code implementing this function is short. The syntax for lambda functions is:

lambda (parameter_list) => expression

The lambda keyword is followed by an optional parameter list, => and an expression returning a result of the appropriate type.

For example, the following code creates an event that is called every five seconds.

var repeater := TW3EventRepeater.Create(
  function (Sender: TObject): boolean
  begin
    Result := MyFunction;
  end,
  5000);

Anonymous method calls some function in the code and returns its result. (False will trigger another event after the timeout (5000 ms) and True will stop the repeater.)

Let's rewrite this code using a lambda function.

var repeater := TW3EventRepeater.Create(lambda (Sender) => MyFunction, 5000);

As you can see, there's no need to declare the parameter (Sender) type and the function result type; Smart will detect them automatically. Even more, as we don't use the Sender parameter, we can drop the parameter list and use an even shorter form.

var repeater := TW3EventRepeater.Create(lambda => MyFunction, 5000); 

Lambda Statements

The statement form is used when you want to put more than one statement inside the lambda. The syntax for lambda statement is:
lambda (parameter_list)  statement;  [statement;]  [...]
  [statement;]
end

A lambda statement can also implement a function; in that case you should return the result in a normal Delphi way by assigning to a Result pseudo-variable.

Following example sets up a repeater that calls a method MyProc every three seconds.

var repeater := TW3EventRepeater.Create(lambda => MyProc; Result := false; end, 5000);

You can use variables inside a lambda statement, but only if they are declared inline.

Lambdas are an excellent addition to the language, as they provide for a more compact code. For example, in Smart 1.0 the W3Layout unit used the following code:

ResizeChildren(FClientArea.Height, [TAlign.Top, TAlign.Bottom],
  function (layout: TLayoutImpl): Variant
  begin
    Result := layout.GetConfig.GetHeight;
  end,
  procedure (layout: TLayoutImpl; value: integer)
  begin
    layout.GetConfig.Height(value);
  end);

In Smart 1.1, this was simplified to:

ResizeChildren(FClientArea.Height, [TAlign.Top, TAlign.Bottom],
  lambda (layout) => layout.GetConfig.GetHeight,
  lambda (layout, value) layout.GetConfig.Height(value) end);

Lambdas are also very helpful when defining event handlers.

W3btnOK.OnClick := lambda Application.HideModal(mrOK); end;