1 of 29

FROM OOP/ANY

TO FP

Dhaka Tech Summit:

Paradigm Shifts in Software Engineering

2 of 29

WHO AM I

Tawsif Aqib

Fullstack Software Engineer && Elixir Enthusiast

Currently, Principal Engineer @ Intelligent Machines Limited

3 of 29

DID YOU KNOW?

You do not need Functional Language to do Functional Programming*?

(*) Condition Applied

4 of 29

... You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.

JOE ARMSTRONG

(1950-2019)

5 of 29

RECAP

Pure Function

First Class Function

Higher Order Function

Immutability

Referential Transparency

Recursion

Functional Languages - Haskell, Elixir, Clojure. Scala, Lisp ...

6 of 29

It is all about avoiding

SIDE EFFECTS

7 of 29

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.

8 of 29

PURE FUNCTION

Returns same value for the same arguments.

No side-effects.

9 of 29

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

10 of 29

Avoid Temporary Variables

function isPositive(int $number) {

if ($number > 0) {

$status = true;

} else {

$status = false;

}

return $status;

}

11 of 29

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;

}

12 of 29

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));

13 of 29

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));

14 of 29

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);

15 of 29

Recursion

function product(array $data) {

if (empty($data)) {

return 0;

}

if (count($data) == 1) {

return $data[0];

}

return array_pop($data) * product($data);

}

16 of 29

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";

}

17 of 29

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')));

18 of 29

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);

19 of 29

Loops are imperative, uses some temporary variables and they aren’t very readable(!).

20 of 29

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;

}

21 of 29

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);

}

22 of 29

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;

});

}

23 of 29

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();

}

24 of 29

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()));

25 of 29

Pipeline

$users = findUsers();

$bananiUsers = getUsersByLocation($users);

$avgAgeOfBananiUsers = getAvgAge($bananiUsers);

26 of 29

Pipeline

Laravel Framework - Collection

composer require tightenco/collect

$collection = collect(getUsers());

echo $collection->map("mapUserToDTO")

->filter(“getUsersByLocation”)

->map("getTotalAge")

->average();

27 of 29

WHAT TO CHOOSE?

OOP OR FP

28 of 29

WHAT TO CHOOSE?

OOP OR FP

CHOOSE BOTH

29 of 29

PLEASE ASK EASY

QUESTIONS

AND THANKS

FOR LISTENING