SlideShare a Scribd company logo
Banishing
Loops with
Functional PHP
@davidbhayes
@davidbhayes from ThoughtfulCode.com
Goals
To understand...
1. what functional programming is
2. practical substitutions we can make in our code using it
3. why it can be beneficial to make these changes
@davidbhayes from ThoughtfulCode.com
In the beginning, there was code.
And that was all...
@davidbhayes from ThoughtfulCode.com
As time goes on, we learn that
maintaining code is hard
@davidbhayes from ThoughtfulCode.com
To ponder...
Why is
maintaining
code hard?
@davidbhayes from ThoughtfulCode.com
Today, there are basically
Three Paradigms
@davidbhayes from ThoughtfulCode.com
Procedural Code
β€’ You tell the computer exactly what to do
β€’ You might have "functions" which contain your common
procedures
@davidbhayes from ThoughtfulCode.com
OO Code
β€’ You use the inherent human understanding of objects to
make code more comprehensible
β€’ In PHP, you use classes to define blueprint for instances of
objects
@davidbhayes from ThoughtfulCode.com
Functional Code
β€’ Mathematical-functions have magic powers, pure functions
are more unitary than procedures
β€’ All pure functions are stateless and create only their own
output
@davidbhayes from ThoughtfulCode.com
Functional Programming
in a bit more depth
@davidbhayes from ThoughtfulCode.com
Pure functions...
Read only their inputs
@davidbhayes from ThoughtfulCode.com
Pure functions...
Affect only their outputs
@davidbhayes from ThoughtfulCode.com
Only touching inputs and outputs means
β€’ No DB changes
β€’ No files created
β€’ No reading of global state (like time)
@davidbhayes from ThoughtfulCode.com
It also means...
β€’ Functions are completely idempotent
β€’ Functions can be composed
β€’ Rerunning a function for debugging requires only knowing
its name and parameters
@davidbhayes from ThoughtfulCode.com
Functional programming languages
β€’ Haskell
β€’ Elm
β€’ Clojure
β€’ Scala (sort of)
β€’ JavaScript (sort of)
@davidbhayes from ThoughtfulCode.com
Now, Lets Talk About
PHP
@davidbhayes from ThoughtfulCode.com
Most "classic" or "legacy" PHP is
procedural
@davidbhayes from ThoughtfulCode.com
Most modern PHP code is
object-oriented
with procedural processes inside
@davidbhayes from ThoughtfulCode.com
I love foreach
@davidbhayes from ThoughtfulCode.com
foreach is fundamentally
procedural
(yes, even inside an object method)
@davidbhayes from ThoughtfulCode.com
A classic PHP pattern:
$saved = [];
foreach($items as $item) {
if ($item->size > 5) {
$saved[] = $item;
}
}
$display_titles = [];
foreach($saved as $item) {
$display_titles[] = ucfirst($item->title);
}
foreach($display_titles as $title) {
echo '<h1>'.$title.'</h1>';
}
@davidbhayes from ThoughtfulCode.com
How Does Functional
Help?
@davidbhayes from ThoughtfulCode.com
What's map?
Transform each entity in a list using a given function
@davidbhayes from ThoughtfulCode.com
What's filter?
Keep items in a list if a function return true when run on an
item
@davidbhayes from ThoughtfulCode.com
Getting Practical
FP in PHP
@davidbhayes from ThoughtfulCode.com
array_map
// array_map ( callable $callback , array $array1 [, array $... ] )
$start = [1, 2, 3, 4, 5];
$end = array_map(function ($i) {
return $i * 2;
}, $start);
// [2, 4, 6, 8, 10]
@davidbhayes from ThoughtfulCode.com
array_map makes this code...
$start = [1, 2, 3, 4, 5];
$end = [];
foreach($start as $i) {
$end[] = $i * 2;
}
@davidbhayes from ThoughtfulCode.com
Into this
$start = [1, 2, 3, 4, 5];
$end = array_map(function ($i) {
return $i * 2;
}, $start);
@davidbhayes from ThoughtfulCode.com
array_filter
// array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
$start = [1, 2, 3, 4, 5, 6];
$even = array_filter($start, function ($i) {
return $i % 2 === 0;
});
// [2, 4, 6]
@davidbhayes from ThoughtfulCode.com
array_filter makes this code...
$start = [1, 2, 3, 4, 5, 6];
$even = [];
foreach($start as $i) {
if ($i % 2 === 0) {
$even[] = $i;
}
}
@davidbhayes from ThoughtfulCode.com
Into this
$start = [1, 2, 3, 4, 5, 6];
$even = array_filter($start, function ($i) {
return $i % 2 === 0;
});
@davidbhayes from ThoughtfulCode.com
Let's Talk about
Callables
@davidbhayes from ThoughtfulCode.com
Simple function declarations are
global
function hello($name) {
return 'Hello, '.$name;
}
Called like...
echo hello('David');
@davidbhayes from ThoughtfulCode.com
Anonymous functions can be saved to
variables
$hello = function($name) {
return 'Hello, '.$name;
}
Called like...
echo $hello('David');
@davidbhayes from ThoughtfulCode.com
Classes can have static methods
class Namer {
public static function hello($name) {
return 'Hello, '.$name;
}
}
Called like...
echo Namer::hello('David');
@davidbhayes from ThoughtfulCode.com
Objects can have methods
class Namer {
public function hello($name) {
return 'Hello, '.$name;
}
}
Called like...
$namer = new Namer;
echo $namer->hello('David');
@davidbhayes from ThoughtfulCode.com
All forms can be used with array_map, etc
$names = ['David', 'Megan', 'Sierra'];
array_map('hello', $names);
array_map($hello, $names);
array_map('Namer::hello', $names);
array_map(['Namer', 'hello'], $names);
$namer = new Namer;
array_map([$namer, 'hello'], $names);
@davidbhayes from ThoughtfulCode.com
Let's Talk about use
@davidbhayes from ThoughtfulCode.com
Variable Scoping
@davidbhayes from ThoughtfulCode.com
This does not work!
$nonlocal = 7;
$greaterThan = function($number) {
return $number > $nonlocal;
}
$greaterThan(1);
@davidbhayes from ThoughtfulCode.com
This also does not work!
global $nonlocal = 7;
$greaterThan = function($number) {
return $number > $nonlocal;
}
$greaterThan(1);
@davidbhayes from ThoughtfulCode.com
This does work!
global $nonlocal = 7;
$greaterThan = function($number) {
global $nonlocal;
return $number > $nonlocal;
}
$greaterThan(1);
@davidbhayes from ThoughtfulCode.com
But this is better!
$nonlocal = 7;
$greaterThan = function($number) use ($nonlocal) {
return $number > $nonlocal;
}
$greaterThan(1);
@davidbhayes from ThoughtfulCode.com
Why this matters
$nonlocal = 7;
$greaterThan = function($number) use ($nonlocal) {
return $number > $nonlocal;
}
array_filter($array, $greaterThan);
@davidbhayes from ThoughtfulCode.com
Or else
$nonlocal = 7;
array_filter($array, function($number) use ($nonlocal) {
return $number > $nonlocal;
});
@davidbhayes from ThoughtfulCode.com
Remember this?
$saved = [];
foreach($items as $item) {
if ($item->size > 5) {
$saved[] = $item;
}
}
$display_titles = [];
foreach($saved as $item) {
$display_titles[] = ucfirst($item->title);
}
foreach($display_titles as $title) {
echo '<h1>'.$title.'</h1>';
}
@davidbhayes from ThoughtfulCode.com
Let's transform
filter in for foreach
$saved = array_filter($items, function($i) {
return $item->size > 5;
});
$display_titles = [];
foreach($saved as $item) {
$display_titles[] = ucfirst($item->title);
}
foreach($display_titles as $title) {
echo '<h1>'.$title.'</h1>';
}
@davidbhayes from ThoughtfulCode.com
Let's transform
map for capitalizing
$saved = array_filter($items, function($i) {
return $i->size > 5;
});
$display_titles = array_map(function($item) {
return ucfirst($item->title);
}, $saved);
// somewhere else
foreach($display_titles as $title) {
echo '<h1>'.$title.'</h1>';
}
@davidbhayes from ThoughtfulCode.com
Hassles with that
β€’ Temporary variables at every step
β€’ Argument order on array_filter and array_map is
inconsistent
@davidbhayes from ThoughtfulCode.com
But maybe we can solve that...
@davidbhayes from ThoughtfulCode.com
Collection Libraries
@davidbhayes from ThoughtfulCode.com
Collections allow us to streamline
filter, map, etc
@davidbhayes from ThoughtfulCode.com
Why collection
pipelines?
@davidbhayes from ThoughtfulCode.com
Easier to read -- we read left to right in
English
array_map(array_filter()) executes inside out
@davidbhayes from ThoughtfulCode.com
Skip argument order issues
Which one's the array?
@davidbhayes from ThoughtfulCode.com
No need for temps with
fluent interface
->chaining()->methods()->is()->cool();
@davidbhayes from ThoughtfulCode.com
Your ORM may
already have one...
@davidbhayes from ThoughtfulCode.com
Using a Laravel Collection
We have a Reddit-like site:
β€’ Posts have scores
β€’ Comments have scores
β€’ We have a 'fluency scorer'
β€’ We have a hypothesis that good posts get higher scoring
and more fluent comments
@davidbhayes from ThoughtfulCode.com
Without FP
$posts = Posts::all();
$goodPosts = [];
foreach($posts as $post) {
if ($post->score > 500) {
$goodPosts[] = $post;
}
}
$goodComments = [];
foreach($goodPosts as $post) {
$comments = $post->comments;
foreach($comments as $c) {
if ($c->score > 100) {
$goodComments[] = $c;
}
}
}
$scoredGoodComments = [];
foreach($goodComments as $c) {
$local['score'] = $c->score;
$local['fluency'] = FluentScorer::score($c->content);
$local['comment'] = $c;
$local['post'] = $c->post;
$scoredGoodComments[] = $local;
}
@davidbhayes from ThoughtfulCode.com
Refactor 1 - filter out posts
$goodPosts = Posts::all()
->filter(function($post) {
return $post->score > 500
});
$goodComments = [];
foreach($goodPosts as $post) {
$comments = $post->comments;
foreach($comments as $c) {
if ($c->score > 100) {
$goodComments[] = $c;
}
}
}
$scoredGoodComments = [];
foreach($goodComments as $c) {
$local['score'] = $c->score;
$local['fluency'] = FluentScorer::score($c->content);
$local['comment'] = $c;
$local['post'] = $c->post;
$scoredGoodComments[] = $local;
}
@davidbhayes from ThoughtfulCode.com
Quick Explanation
β€’ flatten will take nested arrays and de-nest them
β€’ PHP doesn't have an native array_flatten, but you can
make one
@davidbhayes from ThoughtfulCode.com
Refactor 2 - collect good comments on good posts
$goodComments = Posts::all()
->filter(function($post) {
return $post->score > 500
})
->map(function($p) {
return $post->comments;
})
->flatten()
->filter(function($c) {
return $c->score > 100;
});
$scoredGoodComments = [];
foreach($goodComments as $c) {
$local['score'] = $c->score;
$local['fluency'] = FluentScorer::score($c->content);
$local['comment'] = $c;
$local['post'] = $c->post;
$scoredGoodComments[] = $local;
}
@davidbhayes from ThoughtfulCode.com
Quick Simplification
β€’ flatMap is a shortcut for map then flatten
@davidbhayes from ThoughtfulCode.com
Refactor 2 - collect good comments on good posts
(again)
$goodComments = Posts::all()
->filter(function($post) {
return $post->score > 500
})
->flatMap(function($p) {
return $post->comments;
})
->filter(function($c) {
return $c->score > 100;
});
$scoredGoodComments = [];
foreach($goodComments as $c) {
$local['score'] = $c->score;
$local['fluency'] = FluentScorer::score($c->content);
$local['comment'] = $c;
$local['post'] = $c->post;
$scoredGoodComments[] = $local;
}
@davidbhayes from ThoughtfulCode.com
Refactor 3 - create new set with a map
$scoredGoodCommments =
Posts::all()
->filter(function($post) {
return $post->score > 500
})
->flatMap(function($p) {
return $post->comments;
})
->filter(function($c) {
return $c->score > 100;
})
->map(function($c) {
return [
'score' => $c->score,
'fluency' => FluentScorer::score($c->content),
'comment' => $c,
'post' => $c->post,
];
});
@davidbhayes from ThoughtfulCode.com
Did it get better? (original)
$posts = Posts::all();
$goodPosts = [];
foreach($posts as $post) {
if ($post->score > 500) {
$goodPosts[] = $post;
}
}
$goodComments = [];
foreach($goodPosts as $post) {
$comments = $post->comments;
foreach($comments as $c) {
if ($c->score > 100) {
$goodComments[] = $c;
}
}
}
$scoredGoodComments = [];
foreach($goodComments as $c) {
$local['score'] = $c->score;
$local['fluency'] = FluentScorer::score($c->content);
$local['comment'] = $c;
$local['post'] = $c->post;
$scoredGoodComments[] = $local;
}
@davidbhayes from ThoughtfulCode.com
Did it get better? (final)
$scoredGoodCommments =
Posts::all()
->filter(function($post) {
return $post->score > 500
})
->flatMap(function($p) {
return $post->comments;
})
->filter(function($c) {
return $c->score > 100;
})
->map(function($c) {
return [
'score' => $c->score,
'fluency' => FluentScorer::score($c->content),
'comment' => $c,
'post' => $c->post,
];
});
@davidbhayes from ThoughtfulCode.com
What makes code hard to
maintain?
β€’ Short-lived variables are clutter
β€’ Disguised intent via foreach
β€’ Deeply nested conditionals
@davidbhayes from ThoughtfulCode.com
Does FP help?
Local and temporary $variables are reduced, especially with
pipelines
@davidbhayes from ThoughtfulCode.com
Does FP help?
Replacing foreaches with map and filter makes it clearer
what each does
@davidbhayes from ThoughtfulCode.com
Does FP help?
Nesting is minimized by small, single purpose functions
@davidbhayes from ThoughtfulCode.com
"A program cannot
change until it is
alive in a
programmer's head."
β€”Jessica Kerr on Ruby Rogues
(via @johnkary)
@davidbhayes from ThoughtfulCode.com
Localizing control and complexity makes it easier for you to
jump in
between meetings, children, life, etc
@davidbhayes from ThoughtfulCode.com
Again
Did it get better? (original)
$posts = Posts::all();
$goodPosts = [];
foreach($posts as $post) {
if ($post->score > 500) {
$goodPosts[] = $post;
}
}
$goodComments = [];
foreach($goodPosts as $post) {
$comments = $post->comments;
foreach($comments as $c) {
if ($c->score > 100) {
$goodComments[] = $c;
}
}
}
$scoredGoodComments = [];
foreach($goodComments as $c) {
$local['score'] = $c->score;
$local['fluency'] = FluentScorer::score($c->content);
$local['comment'] = $c;
$local['post'] = $c->post;
$scoredGoodComments[] = $local;
}
@davidbhayes from ThoughtfulCode.com
Did it get better? (final)
$scoredGoodCommments =
Posts::all()
->filter(function($post) {
return $post->score > 500
})
->flatMap(function($p) {
return $post->comments;
})
->filter(function($c) {
return $c->score > 100;
})
->map(function($c) {
return [
'score' => $c->score,
'fluency' => FluentScorer::score($c->content),
'comment' => $c,
'post' => $c->post,
];
});
@davidbhayes from ThoughtfulCode.com
Thank You!I've been @davidbhayes
I run a site called WPShout
and another called Thoughtful Code
@davidbhayes from ThoughtfulCode.com

More Related Content

PDF
New SPL Features in PHP 5.3
PDF
PHP 7 – What changed internally? (Forum PHP 2015)
PPTX
SPL: The Undiscovered Library - DataStructures
KEY
Spl Not A Bridge Too Far phpNW09
ODP
Intro to The PHP SPL
PDF
PHP 7 – What changed internally?
PDF
DBIx::Class beginners
PDF
OO Perl with Moose
New SPL Features in PHP 5.3
PHP 7 – What changed internally? (Forum PHP 2015)
SPL: The Undiscovered Library - DataStructures
Spl Not A Bridge Too Far phpNW09
Intro to The PHP SPL
PHP 7 – What changed internally?
DBIx::Class beginners
OO Perl with Moose

What's hot (20)

PDF
PHP Unit 4 arrays
Β 
PDF
PHP 7 – What changed internally? (PHP Barcelona 2015)
PDF
Advanced Php - Macq Electronique 2010
PPTX
Arrays in PHP
PPTX
Bioinformatica p6-bioperl
PPTX
Bioinformatics p5-bioperl v2013-wim_vancriekinge
PDF
PHP data structures (and the impact of php 7 on them), phpDay Verona 2015, Italy
PPTX
PHP Functions & Arrays
PDF
Database API, your new friend
PDF
DBIx::Class introduction - 2010
PDF
What I learned from Seven Languages in Seven Weeks (IPRUG)
PPT
Groovy unleashed
PPTX
Chap 3php array part1
PDF
Perl object ?
PDF
Introductionto fp with groovy
PDF
4.1 PHP Arrays
PDF
Your code sucks, let's fix it
PPTX
Chap 3php array part 2
PDF
Marc’s (bio)perl course
KEY
Potential Friend Finder
PHP Unit 4 arrays
Β 
PHP 7 – What changed internally? (PHP Barcelona 2015)
Advanced Php - Macq Electronique 2010
Arrays in PHP
Bioinformatica p6-bioperl
Bioinformatics p5-bioperl v2013-wim_vancriekinge
PHP data structures (and the impact of php 7 on them), phpDay Verona 2015, Italy
PHP Functions & Arrays
Database API, your new friend
DBIx::Class introduction - 2010
What I learned from Seven Languages in Seven Weeks (IPRUG)
Groovy unleashed
Chap 3php array part1
Perl object ?
Introductionto fp with groovy
4.1 PHP Arrays
Your code sucks, let's fix it
Chap 3php array part 2
Marc’s (bio)perl course
Potential Friend Finder
Ad

Similar to Banishing Loops with Functional Programming in PHP (20)

ODP
HTML Templates Using Clear Silver
PDF
TDD with PhpSpec
PDF
Rich Internet Applications con JavaFX e NetBeans
PDF
Solr's Search Relevancy (Understand Solr's query debug)
KEY
Scaling php applications with redis
PDF
Drupal 8: A story of growing up and getting off the island
ODP
Perl Teach-In (part 2)
PPTX
Quality code by design
PDF
WordPress London 16 May 2012 - You don’t know query
Β 
PPTX
Building a horizontally scalable API in php
ODP
Php 102: Out with the Bad, In with the Good
PDF
[WLDN] Supercharging word press development in 2018
PPT
Intro to php
PDF
RubyEnRails2007 - Dr Nic Williams - DIY Syntax
KEY
Intro to Ruby - Twin Cities Code Camp 7
PDF
Becoming a better WordPress Developer
PDF
Icsug dev day2014_road to damascus - conversion experience-lotusscript and @f...
PDF
Modern php
PPT
MIND sweeping introduction to PHP
Β 
PDF
Caldera Learn - LoopConf WP API + Angular FTW Workshop
HTML Templates Using Clear Silver
TDD with PhpSpec
Rich Internet Applications con JavaFX e NetBeans
Solr's Search Relevancy (Understand Solr's query debug)
Scaling php applications with redis
Drupal 8: A story of growing up and getting off the island
Perl Teach-In (part 2)
Quality code by design
WordPress London 16 May 2012 - You don’t know query
Β 
Building a horizontally scalable API in php
Php 102: Out with the Bad, In with the Good
[WLDN] Supercharging word press development in 2018
Intro to php
RubyEnRails2007 - Dr Nic Williams - DIY Syntax
Intro to Ruby - Twin Cities Code Camp 7
Becoming a better WordPress Developer
Icsug dev day2014_road to damascus - conversion experience-lotusscript and @f...
Modern php
MIND sweeping introduction to PHP
Β 
Caldera Learn - LoopConf WP API + Angular FTW Workshop
Ad

Recently uploaded (20)

PPTX
Module 1 - Cyber Law and Ethics 101.pptx
PPTX
presentation_pfe-universite-molay-seltan.pptx
PPTX
Introuction about ICD -10 and ICD-11 PPT.pptx
PDF
Slides PDF The World Game (s) Eco Economic Epochs.pdf
PPTX
SAP Ariba Sourcing PPT for learning material
PPTX
Internet___Basics___Styled_ presentation
PPT
Design_with_Watersergyerge45hrbgre4top (1).ppt
PDF
Sims 4 Historia para lo sims 4 para jugar
PDF
Paper PDF World Game (s) Great Redesign.pdf
PPTX
Job_Card_System_Styled_lorem_ipsum_.pptx
PPTX
PptxGenJS_Demo_Chart_20250317130215833.pptx
PDF
πŸ’° π”πŠπ“πˆ πŠπ„πŒπ„ππ€ππ†π€π πŠπˆππ„π‘πŸ’πƒ π‡π€π‘πˆ 𝐈𝐍𝐈 πŸπŸŽπŸπŸ“ πŸ’°
Β 
PDF
Cloud-Scale Log Monitoring _ Datadog.pdf
PPTX
international classification of diseases ICD-10 review PPT.pptx
PDF
Unit-1 introduction to cyber security discuss about how to secure a system
PDF
Exploring VPS Hosting Trends for SMBs in 2025
PPTX
Digital Literacy And Online Safety on internet
PDF
SASE Traffic Flow - ZTNA Connector-1.pdf
PPTX
Power Point - Lesson 3_2.pptx grad school presentation
Β 
PPTX
Funds Management Learning Material for Beg
Module 1 - Cyber Law and Ethics 101.pptx
presentation_pfe-universite-molay-seltan.pptx
Introuction about ICD -10 and ICD-11 PPT.pptx
Slides PDF The World Game (s) Eco Economic Epochs.pdf
SAP Ariba Sourcing PPT for learning material
Internet___Basics___Styled_ presentation
Design_with_Watersergyerge45hrbgre4top (1).ppt
Sims 4 Historia para lo sims 4 para jugar
Paper PDF World Game (s) Great Redesign.pdf
Job_Card_System_Styled_lorem_ipsum_.pptx
PptxGenJS_Demo_Chart_20250317130215833.pptx
πŸ’° π”πŠπ“πˆ πŠπ„πŒπ„ππ€ππ†π€π πŠπˆππ„π‘πŸ’πƒ π‡π€π‘πˆ 𝐈𝐍𝐈 πŸπŸŽπŸπŸ“ πŸ’°
Β 
Cloud-Scale Log Monitoring _ Datadog.pdf
international classification of diseases ICD-10 review PPT.pptx
Unit-1 introduction to cyber security discuss about how to secure a system
Exploring VPS Hosting Trends for SMBs in 2025
Digital Literacy And Online Safety on internet
SASE Traffic Flow - ZTNA Connector-1.pdf
Power Point - Lesson 3_2.pptx grad school presentation
Β 
Funds Management Learning Material for Beg

Banishing Loops with Functional Programming in PHP

  • 2. Goals To understand... 1. what functional programming is 2. practical substitutions we can make in our code using it 3. why it can be beneficial to make these changes @davidbhayes from ThoughtfulCode.com
  • 3. In the beginning, there was code. And that was all... @davidbhayes from ThoughtfulCode.com
  • 4. As time goes on, we learn that maintaining code is hard @davidbhayes from ThoughtfulCode.com
  • 5. To ponder... Why is maintaining code hard? @davidbhayes from ThoughtfulCode.com
  • 6. Today, there are basically Three Paradigms @davidbhayes from ThoughtfulCode.com
  • 7. Procedural Code β€’ You tell the computer exactly what to do β€’ You might have "functions" which contain your common procedures @davidbhayes from ThoughtfulCode.com
  • 8. OO Code β€’ You use the inherent human understanding of objects to make code more comprehensible β€’ In PHP, you use classes to define blueprint for instances of objects @davidbhayes from ThoughtfulCode.com
  • 9. Functional Code β€’ Mathematical-functions have magic powers, pure functions are more unitary than procedures β€’ All pure functions are stateless and create only their own output @davidbhayes from ThoughtfulCode.com
  • 10. Functional Programming in a bit more depth @davidbhayes from ThoughtfulCode.com
  • 11. Pure functions... Read only their inputs @davidbhayes from ThoughtfulCode.com
  • 12. Pure functions... Affect only their outputs @davidbhayes from ThoughtfulCode.com
  • 13. Only touching inputs and outputs means β€’ No DB changes β€’ No files created β€’ No reading of global state (like time) @davidbhayes from ThoughtfulCode.com
  • 14. It also means... β€’ Functions are completely idempotent β€’ Functions can be composed β€’ Rerunning a function for debugging requires only knowing its name and parameters @davidbhayes from ThoughtfulCode.com
  • 15. Functional programming languages β€’ Haskell β€’ Elm β€’ Clojure β€’ Scala (sort of) β€’ JavaScript (sort of) @davidbhayes from ThoughtfulCode.com
  • 16. Now, Lets Talk About PHP @davidbhayes from ThoughtfulCode.com
  • 17. Most "classic" or "legacy" PHP is procedural @davidbhayes from ThoughtfulCode.com
  • 18. Most modern PHP code is object-oriented with procedural processes inside @davidbhayes from ThoughtfulCode.com
  • 19. I love foreach @davidbhayes from ThoughtfulCode.com
  • 20. foreach is fundamentally procedural (yes, even inside an object method) @davidbhayes from ThoughtfulCode.com
  • 21. A classic PHP pattern: $saved = []; foreach($items as $item) { if ($item->size > 5) { $saved[] = $item; } } $display_titles = []; foreach($saved as $item) { $display_titles[] = ucfirst($item->title); } foreach($display_titles as $title) { echo '<h1>'.$title.'</h1>'; } @davidbhayes from ThoughtfulCode.com
  • 22. How Does Functional Help? @davidbhayes from ThoughtfulCode.com
  • 23. What's map? Transform each entity in a list using a given function @davidbhayes from ThoughtfulCode.com
  • 24. What's filter? Keep items in a list if a function return true when run on an item @davidbhayes from ThoughtfulCode.com
  • 25. Getting Practical FP in PHP @davidbhayes from ThoughtfulCode.com
  • 26. array_map // array_map ( callable $callback , array $array1 [, array $... ] ) $start = [1, 2, 3, 4, 5]; $end = array_map(function ($i) { return $i * 2; }, $start); // [2, 4, 6, 8, 10] @davidbhayes from ThoughtfulCode.com
  • 27. array_map makes this code... $start = [1, 2, 3, 4, 5]; $end = []; foreach($start as $i) { $end[] = $i * 2; } @davidbhayes from ThoughtfulCode.com
  • 28. Into this $start = [1, 2, 3, 4, 5]; $end = array_map(function ($i) { return $i * 2; }, $start); @davidbhayes from ThoughtfulCode.com
  • 29. array_filter // array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ) $start = [1, 2, 3, 4, 5, 6]; $even = array_filter($start, function ($i) { return $i % 2 === 0; }); // [2, 4, 6] @davidbhayes from ThoughtfulCode.com
  • 30. array_filter makes this code... $start = [1, 2, 3, 4, 5, 6]; $even = []; foreach($start as $i) { if ($i % 2 === 0) { $even[] = $i; } } @davidbhayes from ThoughtfulCode.com
  • 31. Into this $start = [1, 2, 3, 4, 5, 6]; $even = array_filter($start, function ($i) { return $i % 2 === 0; }); @davidbhayes from ThoughtfulCode.com
  • 32. Let's Talk about Callables @davidbhayes from ThoughtfulCode.com
  • 33. Simple function declarations are global function hello($name) { return 'Hello, '.$name; } Called like... echo hello('David'); @davidbhayes from ThoughtfulCode.com
  • 34. Anonymous functions can be saved to variables $hello = function($name) { return 'Hello, '.$name; } Called like... echo $hello('David'); @davidbhayes from ThoughtfulCode.com
  • 35. Classes can have static methods class Namer { public static function hello($name) { return 'Hello, '.$name; } } Called like... echo Namer::hello('David'); @davidbhayes from ThoughtfulCode.com
  • 36. Objects can have methods class Namer { public function hello($name) { return 'Hello, '.$name; } } Called like... $namer = new Namer; echo $namer->hello('David'); @davidbhayes from ThoughtfulCode.com
  • 37. All forms can be used with array_map, etc $names = ['David', 'Megan', 'Sierra']; array_map('hello', $names); array_map($hello, $names); array_map('Namer::hello', $names); array_map(['Namer', 'hello'], $names); $namer = new Namer; array_map([$namer, 'hello'], $names); @davidbhayes from ThoughtfulCode.com
  • 38. Let's Talk about use @davidbhayes from ThoughtfulCode.com
  • 40. This does not work! $nonlocal = 7; $greaterThan = function($number) { return $number > $nonlocal; } $greaterThan(1); @davidbhayes from ThoughtfulCode.com
  • 41. This also does not work! global $nonlocal = 7; $greaterThan = function($number) { return $number > $nonlocal; } $greaterThan(1); @davidbhayes from ThoughtfulCode.com
  • 42. This does work! global $nonlocal = 7; $greaterThan = function($number) { global $nonlocal; return $number > $nonlocal; } $greaterThan(1); @davidbhayes from ThoughtfulCode.com
  • 43. But this is better! $nonlocal = 7; $greaterThan = function($number) use ($nonlocal) { return $number > $nonlocal; } $greaterThan(1); @davidbhayes from ThoughtfulCode.com
  • 44. Why this matters $nonlocal = 7; $greaterThan = function($number) use ($nonlocal) { return $number > $nonlocal; } array_filter($array, $greaterThan); @davidbhayes from ThoughtfulCode.com
  • 45. Or else $nonlocal = 7; array_filter($array, function($number) use ($nonlocal) { return $number > $nonlocal; }); @davidbhayes from ThoughtfulCode.com
  • 46. Remember this? $saved = []; foreach($items as $item) { if ($item->size > 5) { $saved[] = $item; } } $display_titles = []; foreach($saved as $item) { $display_titles[] = ucfirst($item->title); } foreach($display_titles as $title) { echo '<h1>'.$title.'</h1>'; } @davidbhayes from ThoughtfulCode.com
  • 47. Let's transform filter in for foreach $saved = array_filter($items, function($i) { return $item->size > 5; }); $display_titles = []; foreach($saved as $item) { $display_titles[] = ucfirst($item->title); } foreach($display_titles as $title) { echo '<h1>'.$title.'</h1>'; } @davidbhayes from ThoughtfulCode.com
  • 48. Let's transform map for capitalizing $saved = array_filter($items, function($i) { return $i->size > 5; }); $display_titles = array_map(function($item) { return ucfirst($item->title); }, $saved); // somewhere else foreach($display_titles as $title) { echo '<h1>'.$title.'</h1>'; } @davidbhayes from ThoughtfulCode.com
  • 49. Hassles with that β€’ Temporary variables at every step β€’ Argument order on array_filter and array_map is inconsistent @davidbhayes from ThoughtfulCode.com
  • 50. But maybe we can solve that... @davidbhayes from ThoughtfulCode.com
  • 52. Collections allow us to streamline filter, map, etc @davidbhayes from ThoughtfulCode.com
  • 54. Easier to read -- we read left to right in English array_map(array_filter()) executes inside out @davidbhayes from ThoughtfulCode.com
  • 55. Skip argument order issues Which one's the array? @davidbhayes from ThoughtfulCode.com
  • 56. No need for temps with fluent interface ->chaining()->methods()->is()->cool(); @davidbhayes from ThoughtfulCode.com
  • 57. Your ORM may already have one... @davidbhayes from ThoughtfulCode.com
  • 58. Using a Laravel Collection We have a Reddit-like site: β€’ Posts have scores β€’ Comments have scores β€’ We have a 'fluency scorer' β€’ We have a hypothesis that good posts get higher scoring and more fluent comments @davidbhayes from ThoughtfulCode.com
  • 59. Without FP $posts = Posts::all(); $goodPosts = []; foreach($posts as $post) { if ($post->score > 500) { $goodPosts[] = $post; } } $goodComments = []; foreach($goodPosts as $post) { $comments = $post->comments; foreach($comments as $c) { if ($c->score > 100) { $goodComments[] = $c; } } } $scoredGoodComments = []; foreach($goodComments as $c) { $local['score'] = $c->score; $local['fluency'] = FluentScorer::score($c->content); $local['comment'] = $c; $local['post'] = $c->post; $scoredGoodComments[] = $local; } @davidbhayes from ThoughtfulCode.com
  • 60. Refactor 1 - filter out posts $goodPosts = Posts::all() ->filter(function($post) { return $post->score > 500 }); $goodComments = []; foreach($goodPosts as $post) { $comments = $post->comments; foreach($comments as $c) { if ($c->score > 100) { $goodComments[] = $c; } } } $scoredGoodComments = []; foreach($goodComments as $c) { $local['score'] = $c->score; $local['fluency'] = FluentScorer::score($c->content); $local['comment'] = $c; $local['post'] = $c->post; $scoredGoodComments[] = $local; } @davidbhayes from ThoughtfulCode.com
  • 61. Quick Explanation β€’ flatten will take nested arrays and de-nest them β€’ PHP doesn't have an native array_flatten, but you can make one @davidbhayes from ThoughtfulCode.com
  • 62. Refactor 2 - collect good comments on good posts $goodComments = Posts::all() ->filter(function($post) { return $post->score > 500 }) ->map(function($p) { return $post->comments; }) ->flatten() ->filter(function($c) { return $c->score > 100; }); $scoredGoodComments = []; foreach($goodComments as $c) { $local['score'] = $c->score; $local['fluency'] = FluentScorer::score($c->content); $local['comment'] = $c; $local['post'] = $c->post; $scoredGoodComments[] = $local; } @davidbhayes from ThoughtfulCode.com
  • 63. Quick Simplification β€’ flatMap is a shortcut for map then flatten @davidbhayes from ThoughtfulCode.com
  • 64. Refactor 2 - collect good comments on good posts (again) $goodComments = Posts::all() ->filter(function($post) { return $post->score > 500 }) ->flatMap(function($p) { return $post->comments; }) ->filter(function($c) { return $c->score > 100; }); $scoredGoodComments = []; foreach($goodComments as $c) { $local['score'] = $c->score; $local['fluency'] = FluentScorer::score($c->content); $local['comment'] = $c; $local['post'] = $c->post; $scoredGoodComments[] = $local; } @davidbhayes from ThoughtfulCode.com
  • 65. Refactor 3 - create new set with a map $scoredGoodCommments = Posts::all() ->filter(function($post) { return $post->score > 500 }) ->flatMap(function($p) { return $post->comments; }) ->filter(function($c) { return $c->score > 100; }) ->map(function($c) { return [ 'score' => $c->score, 'fluency' => FluentScorer::score($c->content), 'comment' => $c, 'post' => $c->post, ]; }); @davidbhayes from ThoughtfulCode.com
  • 66. Did it get better? (original) $posts = Posts::all(); $goodPosts = []; foreach($posts as $post) { if ($post->score > 500) { $goodPosts[] = $post; } } $goodComments = []; foreach($goodPosts as $post) { $comments = $post->comments; foreach($comments as $c) { if ($c->score > 100) { $goodComments[] = $c; } } } $scoredGoodComments = []; foreach($goodComments as $c) { $local['score'] = $c->score; $local['fluency'] = FluentScorer::score($c->content); $local['comment'] = $c; $local['post'] = $c->post; $scoredGoodComments[] = $local; } @davidbhayes from ThoughtfulCode.com
  • 67. Did it get better? (final) $scoredGoodCommments = Posts::all() ->filter(function($post) { return $post->score > 500 }) ->flatMap(function($p) { return $post->comments; }) ->filter(function($c) { return $c->score > 100; }) ->map(function($c) { return [ 'score' => $c->score, 'fluency' => FluentScorer::score($c->content), 'comment' => $c, 'post' => $c->post, ]; }); @davidbhayes from ThoughtfulCode.com
  • 68. What makes code hard to maintain? β€’ Short-lived variables are clutter β€’ Disguised intent via foreach β€’ Deeply nested conditionals @davidbhayes from ThoughtfulCode.com
  • 69. Does FP help? Local and temporary $variables are reduced, especially with pipelines @davidbhayes from ThoughtfulCode.com
  • 70. Does FP help? Replacing foreaches with map and filter makes it clearer what each does @davidbhayes from ThoughtfulCode.com
  • 71. Does FP help? Nesting is minimized by small, single purpose functions @davidbhayes from ThoughtfulCode.com
  • 72. "A program cannot change until it is alive in a programmer's head." β€”Jessica Kerr on Ruby Rogues (via @johnkary) @davidbhayes from ThoughtfulCode.com
  • 73. Localizing control and complexity makes it easier for you to jump in between meetings, children, life, etc @davidbhayes from ThoughtfulCode.com
  • 74. Again Did it get better? (original) $posts = Posts::all(); $goodPosts = []; foreach($posts as $post) { if ($post->score > 500) { $goodPosts[] = $post; } } $goodComments = []; foreach($goodPosts as $post) { $comments = $post->comments; foreach($comments as $c) { if ($c->score > 100) { $goodComments[] = $c; } } } $scoredGoodComments = []; foreach($goodComments as $c) { $local['score'] = $c->score; $local['fluency'] = FluentScorer::score($c->content); $local['comment'] = $c; $local['post'] = $c->post; $scoredGoodComments[] = $local; } @davidbhayes from ThoughtfulCode.com
  • 75. Did it get better? (final) $scoredGoodCommments = Posts::all() ->filter(function($post) { return $post->score > 500 }) ->flatMap(function($p) { return $post->comments; }) ->filter(function($c) { return $c->score > 100; }) ->map(function($c) { return [ 'score' => $c->score, 'fluency' => FluentScorer::score($c->content), 'comment' => $c, 'post' => $c->post, ]; }); @davidbhayes from ThoughtfulCode.com
  • 76. Thank You!I've been @davidbhayes I run a site called WPShout and another called Thoughtful Code @davidbhayes from ThoughtfulCode.com