Presented by Larry Garfield (@Crell)
Warning, hard core geek action
Please pardon the nerd
But first a little history
1912-1954
1903-1957
This is what all modern hardware does
Imperative Programming is following a recipe for a cake
if-then
while
for
Var MyList [5, 7, 2, 9, 2]
Var Biggest
Procedure FindBiggest
For MyList As Item
If Item > Biggest Then
Biggest = Item
End If
End For
Return
End Procedure
Call FindBiggest
Print Biggest
Imperative Programming is following a recipe for a cake
Procedural Programming is singing a song with a refrain
Imperative Programming defines
how a program should work.
Declarative Programming defines
what a program should accomplish.
"Can Programming Be Liberated From the von Neumann Style? A Functional Style and its Algebra of Programs"
Declare your algorithm
The compiler optimizes for you
Imperative Programming is following a recipe for a cake
Procedural Programming is singing a song with a refrain
Functional Programming is expressions in a spreadsheet
So what?
Functional languages enforce what is
simply "good code" in other languages
function theme_list(array $items, $type = 'ul') {
$output = '';
foreach ($items as $item) {
$output .= '' . $item . ' ';
}
return "<{$type}>" . $output . '<' . '/' . $type . '>';
}
Where have we seen this before...?
Good service objects are pure functions
function foo(Something $a, Formatter $formatter) {
$bar = get_bar($a, 'bar');
$formatted = $formatter->format($a);
$a = make_changes($a, $bar);
Output::send($formatted);
}
$a = new Something();
$a->bar = 'value1';
foo($a);
print $a->bar . PHP_EOL;
lulz
$function = function($value) {
return $value * 5;
};
$function($a);
Or just a really cool shorthand for objects
$find_pair = function ($card1, $card2) {
return ($card1->value == $card2->value);
};
$is_pair = $find_pair(new Card('2H'), new Card('8D'));
class FindPair {
public function __invoke($card1, $card2) {
return ($card1->value == $card2->value);
}
}
$find_pair = new FindPair();
$is_pair = $find_pair(new Card('2H'), new Card('8D'));
$wild = new Card('5H');
$find_pair = function ($card1, $card2) use ($wild) {
return ($card1->value == $card2->value
|| $wild->value == $card1->value && $wild->suit == $card1->suit
|| $wild->value == $card2->value && $wild->suit == $card2->suit);
};
$is_pair = $find_pair(new Card('2H'), new Card('8D'));
$wild = new Card('5H');
class FindPair {
protected $wild;
public function __construct($wild) {
$this->wild = $wild;
}
public function __invoke($card1, $card2) {
return ($card1->value == $card2->value
|| $this->wild->value == $card1->value && $this->wild->suit == $card1->suit
|| $this->wild->value == $card2->value && $this->wild->suit == $card2->suit);
}
}
$find_pair = new FindPair($wild);
$is_pair = $find_pair(new Card('2H'), new Card('8D'));
$wild = new Card('5H');
$is_wild = function($card) use ($wild) {
return $wild->value == $card->value && $wild->suit == $card->suit;
}
$find_pair = function ($c1, $c2) use ($is_wild) {
return ($c1->value == $c2->value || $is_wild($c1) || $is_wild($c2));
};
$is_pair = $find_pair(new Card('2H'), new Card('8D'));
Where have we seen this before...?
use Symfony\Component\HttpFoundation\StreamedResponse;
class MyController {
public function helloCsv() {
$lots_of_data = get_lots_of_data();
$response = new StreamedResponse();
$response->headers->set('Content-Type', 'text/csv');
$response->setCallback(function() use ($lots_of_data) {
foreach ($lots_of_data as $record) {
print implode(', ', $record) . PHP_EOL;
}
});
return $response;
}
}
class Container {
protected $factories = array();
public function __set($key, $func) {
$this->factories[$key] = $func;
}
public function __get($key) {
return $this->factories[$key]($this);
}
}
$container = new Container();
$container->conn = function($c) {
return new DatabaseConnection($c->info);
};
$container->info = function() { return ['user'=>'me', 'pass'=>'hi']; };
$conn = $container->conn;
$conn->query('...');
Hey, look, a Dependency Injection Container!
class Importer {
public function __construct(MapInterface $map) { $this->map = $map; }
public function mapData($source) {
$dest = $this->repository->create('someType');
foreach ($this->map->mapping() as $field => $callback) {
$dest->$field = $callback($source);
}
return $dest;
}
}
class MyMapper implements MapInterface {
public function mapping() {
$proc = $this->someUtilityService;
$map['title'] = function($source) use ($proc) {
return strip_tags($proc->extract('label', $source));
};
$map['body'] = function($source) { return $source->body; };
$map['extra'] = function($source) { return "Some hard coded value"; };
return $map;
}
}
$importer = new Importer(new MyMapper());
$my_object = $importer->mapData($fancy_external_data_object);
Could get very complicated, or...
$result = array_map($callback, $array);
$result = array_map(function($a) {
// Your logic here.
}, $array);
$result = array_map(function($a, $b) {
// Your logic here.
}, $array1, $array2);
$result = array_map([$obj, 'aMethod'], $array);
foreach()
, but separates application from loop
$importer = new Importer(new MyMapper());
foreach ($array_of_external_objects as $object) {
$my_objects[] = $importer->mapData($object);
}
function apply($callback, Iterator $i) {
$ret = [];
foreach ($i as $object) {
$ret[] = $callback($object);
}
return $ret;
}
$importer = new Importer(new MyMapper());
$my_objects = apply([$importer, 'mapData'], $iter_of_objects);
function expensive($key) {
static $keys = array();
if (empty($keys[$key])) {
$keys[$key] = /* some expensive operation */
}
return $keys[$key];
}
$factorial = function($a) use (&$factorial) {
print "Called function with $a" . PHP_EOL;
if ($a == 1) {
return 1;
}
return $a * $factorial($a - 1);
};
print "Result: " . $factorial(3) . PHP_EOL;
print "Result: " . $factorial(4) . PHP_EOL;
Called function with 3 Called function with 2 Called function with 1 Result: 6 Called function with 4 Called function with 3 Called function with 2 Called function with 1 Result: 24
function memoize($function) {
return function() use ($function) {
static $results = array();
$args = func_get_args();
$key = serialize($args);
if (empty($results[$key])) {
$results[$key] = call_user_func_array($function, $args);
}
return $results[$key];
};
}
$factorial = memoize($factorial);
print "Result: " . $factorial(3) . PHP_EOL;
print "Result: " . $factorial(4) . PHP_EOL;
Called function with 3 Called function with 2 Called function with 1 Result: 6 Called function with 4 Result: 24
interface FancyInterface {
public function compute($key);
}
class Fancy implements FancyInterface {
public function compute($key) { }
}
class FancyCache implements FancyInterface {
public function __construct(FancyInterface $wrapped) {
$this->wrapped = $wrapped;
}
public function compute($key) {
if !($this->cache[$key]) {
$this->cache[$key] = $this->wrapped->compute($key);
}
return $this->cache[$key];
}
}
$fancy = new FancyCache(new Fancy());
$fancy->compute();
$fancy->compute();
interface FancyInterface {
public function compute($key);
}
class Fancy implements FancyInterface {
public function compute($key) { }
}
$f = new Fancy();
$callable = [$f, 'compute'];
$f_cached = memoize($callable);
// And it really really works.
$f_cached($key);
Yet another crazy smart mathematician
Splitting a multi-parameter function
into multiple single-parameter functions
A special case of partial-application
function add($a, $b) {
return $a + $b;
};
function partial_add($a) {
return function($b) use ($a) {
return $a + $b;
};
};
$add1 = partial_add(1);
$add5 = partial_add(5);
$x = $add5($y);
$adder = partial_add(get_config_value('increment_amount'));
$x = $adder($y);
$values = array_map($adder, $array);
function partial($func, $arg1) {
$func_args = func_get_args();
$args = array_slice($func_args, 1);
return function() use($func, $args) {
$full_args = array_merge($args, func_get_args());
return call_user_func_array($func, $full_args);
};
}
$date_formatter = partial('date', 'Y-m-d h:i:s');
print $date_formatter(1372914000);
2013-07-04 12:00:00
$wild = new Card('5H');
$is_wild = function($card) use ($wild) {
return $wild->value == $card->value && $wild->suit == $card->suit;
}
$find_pair = function ($c1, $c2) use ($is_wild) {
return ($c1->value == $c2->value || $is_wild($c1) || $is_wild($c2));
};
$is_pair = $find_pair(new Card('2H'), new Card('8D'));
function is_equal($a, $b) {
return $a->value == $b->value && $a->suit == $b->suit;
}
function find_pair($is_wild, $c1, $c2) {
return ($c1->value == $c2->value || $is_wild($c1) || $is_wild($c2));
};
$wild_checker = partial('is_equal', new Card('5H'));
$pair_checker = partial('find_pair', $wild_checker);
$is_pair = $pair_checker(new Card('2H'), new Card('8D'));
$cached_checker = memoize($pair_checker);
$is_pair = $cached_checker(new Card('2H'), new Card('8D'));
function is_three_kind($find_pair, array $cards) {
return $find_pair($cards[0], $cards[1]) && $find_pair($cards[1], $cards[2]);
}
$three_checker = memoize(partial('is_three_kind', $pair_checker));
And keep on going...
Pure functional languages have this advantage: all flow of data is made explicit. And this disadvantage: sometimes it is painfully explicit.
—Philip Wadler
Functional programming isn't a language
It's just good code
Senior Architect, Palantir.net
Let's Make Something Good Together
Keep tabs on our work at @Palantir
Want to hear about what we're doing?
Rate this session: https://joind.in/11748