D365 Business Central : Lazy Evaluation
Hi and Happy New Year 2023 !!
For the start of the year, let us talk about Lazy Evaluation. What is Lazy Evaluation ? If you have never heard of lazy evaluation, it is basically a technique where the compiler do not process an expression until it’s needed. What does that mean ? Let’s take a look at example in AL conditional statements.
if <Condition1> and <Condition2> then
DoSomething();
Using the above example, DoSomething() will run only if both Condition1 and Condition2 return true. Let’s do a sample where we have two conditions. Condition1 is called FastProcessButFalse() which runs for 1 second and always returns false. Condition2 is called SlowProcessButTrue() which runs for 5 seconds and always returns true.
local procedure TestEvaluation()
begin
if FastProcessButFalse() and SlowProcessButTrue() then
Message('OK');
end;
local procedure FastProcessButFalse(): Boolean
begin
Sleep(1000); // 1 second
exit(false);
end;
local procedure SlowProcessButTrue(): Boolean
begin
Sleep(5000); // 5 seconds
exit(true);
end;
The above TestEvaluation() procedure will never output OK message because FastProcessButFalse() procedure returns false. When we are talking about ideal performance, SlowProcessButTrue() procedure does not need to be processed because FastProcessButFalse() procedure returns false.
Evaluating SlowProcessButTrue() procedure is useless and waste of performance because it is not used. This is the Lazy Evaluation concept. It is being “lazy” and stop evaluating other Conditions when we already have enough information to know the result. This concept is especially important when we have slow running procedure. The opposite approach is called Strict Evaluation.
We would normally expect the AL to adopt the Lazy Evaluation approach. Unfortunately, that is not the case. When we run this in AL, it will process both Conditions and run for 6 seconds. Let’s check this by adding duration into it.
local procedure TestEvaluation()
var
StartDateTime: DateTime;
EndDateTime: DateTime;
TimeDuration: Duration;
begin
StartDateTime := CurrentDateTime();
if FastProcessButFalse() and SlowProcessButTrue() then
Message('OK');
EndDateTime := CurrentDateTime();
TimeDuration := EndDateTime - StartDateTime;
Message('%1', TimeDuration);
end;
local procedure FastProcessButFalse(): Boolean
begin
Sleep(1000); // 1 second
exit(false);
end;
local procedure SlowProcessButTrue(): Boolean
begin
Sleep(5000); // 5 seconds
exit(true);
end;
When we run the TestEvaluation(), we get 6 seconds duration which is the sum of both Conditions.
Now we know that AL does not do Lazy Evaluation. What should we do with this information and how to improve our code?
The first one is to make use of nested if. It is especially useful when we don’t have “else” statement. We also need to make sure the fastest Condition is evaluated first.
Instead of:
if <Condition1> and <Condition2> and <Condition3> and <Condition4> then
DoSomething();
Do this:
if <Condition1> then
if <Condition2> then
if <Condition3> then
if <Condition4> then
DoSomething();
Another good way to do it is to wrap all the conditions into one procedure and use “if not” to exit early.
if CheckConditions() then
DoSomething();
local procedure CheckConditions(): Boolean
begin
if not <Condition1> then
exit(false);
if not <Condition2> then
exit(false);
if not <Condition3> then
exit(false);
if not <Condition4> then
exit(false);
exit(true);
end;
Let’s try it on AL.
local procedure TestIfEvaluation()
var
StartDateTime: DateTime;
EndDateTime: DateTime;
TimeDuration: Duration;
begin
StartDateTime := CurrentDateTime();
if CheckIfConditions() then
Message('OK');
EndDateTime := CurrentDateTime();
TimeDuration := EndDateTime - StartDateTime;
Message('%1', TimeDuration);
end;
local procedure CheckIfConditions(): Boolean
begin
if not FastProcessButFalse() then
exit(false);
if not SlowProcessButTrue() then
exit(false);
exit(true);
end;
local procedure FastProcessButFalse(): Boolean
begin
Sleep(1000); // 1 second
exit(false);
end;
local procedure SlowProcessButTrue(): Boolean
begin
Sleep(5000); // 5 seconds
exit(true);
end;
When we run the TestIfEvaluation(), we get 1 seconds duration which is what we want.
What about using Case statement ?
local procedure TestCaseEvaluation()
var
StartDateTime: DateTime;
EndDateTime: DateTime;
TimeDuration: Duration;
begin
StartDateTime := CurrentDateTime();
if CheckCaseConditions() then
Message('OK');
EndDateTime := CurrentDateTime();
TimeDuration := EndDateTime - StartDateTime;
Message('%1', TimeDuration);
end;
local procedure CheckCaseConditions(): Boolean
begin
case false of
FastProcessButFalse(),
SlowProcessButTrue():
exit(false);
end;
exit(true);
end;
local procedure FastProcessButFalse(): Boolean
begin
Sleep(1000); // 1 second
exit(false);
end;
local procedure SlowProcessButTrue(): Boolean
begin
Sleep(5000); // 5 seconds
exit(true);
end;
When we run the TestCaseEvaluation(), we get 1 seconds duration which is only for Condition1 similar to “if not” statement.
That’s it. Make sure to pay attention when you have multiple conditions on your if statement. You can then use “if not” or “case” statement to improve the performance.
You can also find the sample on GitHub.
Here other blogs that are talking about Lazy Evaluation as well for your reference.
– https://www.hougaard.com/al-is-not-a-lazy-language/
– https://nataliekarolak.wordpress.com/2022/12/21/lazy-evaluation-in-al/