FROM OOP/ANY
TO FP
Dhaka Tech Summit:
Paradigm Shifts in Software Engineering
WHO AM I
Tawsif Aqib
Fullstack Software Engineer && Elixir Enthusiast
Currently, Principal Engineer @ Intelligent Machines Limited
DID YOU KNOW?
You do not need Functional Language to do Functional Programming*?
(*) Condition Applied
... You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
JOE ARMSTRONG
(1950-2019)
RECAP
Pure Function
First Class Function
Higher Order Function
Immutability
Referential Transparency
Recursion
Functional Languages - Haskell, Elixir, Clojure. Scala, Lisp ...
It is all about avoiding
SIDE EFFECTS
WHY?
Easy to test (literally)
Easy to read and reason about (non-imperative)
Avoid confusing problems and errors (no side effect)
Easier Debugging
…
But beware of resource usage.
PURE FUNCTION
Returns same value for the same arguments.
No side-effects.
Pure Function
Impure Function
No globals
No passing value by reference
No side effects
Don’t use any kind of loops (for, for each, while…)
Mutate global state
May modify their input parameters
May throw exceptions
May perform any I/O operations (side-effect)
May produce different results even with the same input parameters
Avoid Temporary Variables
function isPositive(int $number) {
if ($number > 0) {
$status = true;
} else {
$status = false;
}
return $status;
}
Avoid Temporary Variables
function isPositive(int $number) {
if ($number > 0) {
$status = true;
} else {
$status = false;
}
return $status;
}
// better
function isPositive(int $number) {
if ($number > 0) {
return true;
}
return false;
}
// best
function isPositive(int $number) {
return $number > 0;
}
Use Small Functions
Single responsibility
Easier to test
Can be composed
function sum(int $number1, int $number2) {
return $number1 + $number2;
}
echo sum(sum(3, 4), sum(5, 5));
REFERENTIAL TRANSPARENCY
Any expression can be replaced with the value of the expression
without changing the application behaviour.
function sum(int $number1, int $number2) {
return $number1 + $number2;
}
echo sum(sum(3, 4), sum(5, 5));
echo sum(7, sum(5, 5));
Remove State
(as much as possible)
function productImperative(array $data) {
if (empty($data)) {
return 0;
}
$total = 1;
$i = 0;
while ($i < count($data)) {
$total *= $data[$i];
$i++;
}
return $total;
}
// can be solved with
echo array_reduce([5, 3, 2], function($total, $item) {
return $total * $item;
}, 1);
Recursion
function product(array $data) {
if (empty($data)) {
return 0;
}
if (count($data) == 1) {
return $data[0];
}
return array_pop($data) * product($data);
}
Push Impure Functions
(as far as possible)
$lines = file('https://random.url/randomfile.txt');
// Loop through our array, show HTML source as HTML source
foreach ($lines as $line_num => $line) {
echo htmlspecialchars($line) . "\n";
}
Push Impure Functions
(to the boundaries)
Impure function (getFileContent) separated and can easily mocked.
Pure function (formatLines) separated that always return the same output and super easy to test.
// separating the impure one
function getFileContent($file) {
return file($file);
}
function formatLines($lines) {
return array_map(function($line) {
return htmlspecialchars($line) . "\n";
}, $lines);
}
print_r(formatLines(getFileContent('https://random.url/randomfile.txt')));
Immutability
(state should not be changed!)
$users = [“Tawsif”, “Jitu”, “Opu”];
array_push($users, “Shahee”);
print_r($users);
// better
$users = [“Tawsif”, “Jitu”, “Opu”];
$newUsers = array_merge($users, [“Shahee”]);
print_r($users);
print_r($newUsers);
Loops are imperative, uses some temporary variables and they aren’t very readable(!).
A Loop
function getUsers() {
return [
["firstname" => "Tawsif", "location" => "Banani", "age": 30],
["firstname" => "Jitu", "location" => "Mirpur", "age": 35],
["firstname" => "Opu", "location" => "Amsterdam", "age": 29],
];
}
function findUsers()
{
$users = getUsers();
if (empty($users)) {
return false;
}
$usersDTO = [];
foreach ($users as $user) {
$usersDTO[] = new UserDTO($user);
}
return $usersDTO;
}
Map
function getUsers() {
return [
["firstname" => "Tawsif", "location" => "Banani", "age": 30],
["firstname" => "Jitu", "location" => "Mirpur", "age": 35],
["firstname" => "Opu", "location" => "Amsterdam", "age": 29],
];
}
function findUsers()
{
return array_map("mapUserToDTO", getUsers());
}
function mapUserToDTO(array $user) {
return new UserDTO($user);
}
Filter
function getUsersByLocation(array $users, string $location) {
$filteredUsers = [];
foreach ($users as $user) {
if ($user->getLocation() == $location) {
$filteredUsers[] = $user;
}
}
return $filteredUsers;
}
// better
function getUsersByLocation(array $users, string $location) {
return array_filter($users, function ($user) {
$user->getLocation() == $location;
});
}
Reduce
function getAvgAge(array $users) {
$totalAge = 0;
foreach ($users as $user) {
$totalAge += $user->getAge();
}
return $totalAge / count($users);
}
// better
function getAvgAge(array $users) {
return array_reduce($users, "getTotalAge", 0) / count($users);
}
function getTotalAge($total, UserDTO $user) {
return $total + $user->getAge();
}
Pipeline
No native solution for most of the non-functional languages.
But there are libraries.
$users = findUsers();
$bananiUsers = getUsersByLocation($users);
$avgAgeOfBananiUsers = getAvgAge($bananiUsers);
// better
getAvgAge(getUsersByLocation(findUsers()));
Pipeline
$users = findUsers();
$bananiUsers = getUsersByLocation($users);
$avgAgeOfBananiUsers = getAvgAge($bananiUsers);
Pipeline
Laravel Framework - Collection
composer require tightenco/collect
$collection = collect(getUsers());
echo $collection->map("mapUserToDTO")
->filter(“getUsersByLocation”)
->map("getTotalAge")
->average();
WHAT TO CHOOSE?
OOP OR FP
WHAT TO CHOOSE?
OOP OR FP
CHOOSE BOTH
PLEASE ASK EASY
QUESTIONS
AND THANKS
FOR LISTENING