DMarlon Studio

Lesson 14 · 14 of 20

Procedural types and callbacks · Pascal

Pass functions and methods as parameters in Object Pascal with procedural types and of object, the mechanism behind Lazarus events.

Pascal · Procedural types and callbacks in DMarlon Studio: the example's code and its output in the terminal

A procedural type describes the shape of a function: TPriceRule = function(price: Double): Double. Any function with that signature fits, and Sale (20 % off) and Clearance (50 % off) both do. In objfpc mode, `@Sale` takes a reference to the function so you can pass it around like any other value.

Checkout receives the pricing rule and calls it as if it were an ordinary function: rule(price). The same checkout routine works for any discount, present or future, without touching its code; that is what a callback is.

`of object` changes the game: TSaleEvent does not point to a free-standing function but to a method of a specific object. Internally it stores two things, the code and the object (Self). That is why @till.ShowSale carries till along and the method can print its Name, Till 1.

Before calling an event, check `Assigned(onSale)`: if nobody has subscribed it is nil and calling it would crash. This is how Lazarus events work: OnClick is a procedure(Sender: TObject) of object, and in DMarlon Studio's desktop apps every button hooks up a form method in exactly this way.

Code · main.pas

program main;type  TPriceRule = function(price: Double): Double;          // any function of this shape  TSaleEvent = procedure(price, total: Double) of object;  // a method + its object  TTill = class    Name: String;    procedure ShowSale(price, total: Double);  end;procedure TTill.ShowSale(price, total: Double);begin  WriteLn('[', Name, '] ', price:0:2, ' -> ', total:0:2);end;function Sale(price: Double): Double;begin  Result := price * 0.80;end;function Clearance(price: Double): Double;begin  Result := price * 0.50;end;procedure Checkout(price: Double; rule: TPriceRule; onSale: TSaleEvent);begin  if Assigned(onSale) then onSale(price, rule(price));  // only if someone listensend;var till: TTill;begin  till := TTill.Create;  till.Name := 'Till 1';  Checkout(59.90, @Sale, @till.ShowSale);  Checkout(59.90, @Clearance, @till.ShowSale);  till.Free;end.

Output · fpc main.pas && ./main

[Till 1] 59.90 -> 47.92
[Till 1] 59.90 -> 29.95

Key points

  • A procedural type is a function signature.
  • @Function takes the reference in objfpc mode.
  • of object stores a method together with its object.
  • Check Assigned before calling an event.
  • Lazarus events (OnClick and friends) are of object methods.

Exercise

Write a Member rule that takes 5 EUR off prices above 30, create a second TTill named 'Till 2' and use it to charge 59.90 with @Member.