The Art of Transduction

51
The Art of Transduction ZendCon 2016 - Oct 19

Transcript of The Art of Transduction

Page 1: The Art of Transduction

The Art of Transduction

ZendCon 2016 - Oct 19

Page 2: The Art of Transduction

PHP Toolbox

Page 3: The Art of Transduction

The Art of Foreach

Page 4: The Art of Transduction

A Silly Example$grades = [98, 77, 100, 62, 90, 95, 82, 68];$sum = 0;$count = 0;foreach ($grades as $grade) { $sum += $grade; $count ++;}echo "Avg: " . $sum / $count . "\n";

Page 5: The Art of Transduction

A Less Silly Example

$grades = [98, 77, 100, 62, 90, 95, 82, 68];$sum = 0;foreach ($grades as $grade) { $sum += $grade;}echo "Avg: " . $sum / count($grades) . "\n";

Page 6: The Art of Transduction

Less Sillier

$grades = [98, 77, 100, 62, 90, 95, 82, 68];$sum = array_sum($grades);echo "Avg: " . $sum / count($grades) . "\n";

Page 7: The Art of Transduction

Grade Buckets$grades = [98, 77, 100, 62, 90, 95, 82, 68];$gradeBuckets = ["A" => 0, "B" => 0, "C" => 0, "F" => 0];foreach ($grades as $grade) { switch (true) { case $grade >= 90: $gradeBuckets['A']++; break; case $grade >= 80: $gradeBuckets['B']++; break; case $grade >= 70: $gradeBuckets['C']++; break; default: $gradeBuckets['F']++; }}

Page 8: The Art of Transduction

Phone Treefunction getNonManagerNumbers(Employee ...$employees){ $phoneNumbers = []; foreach ($employees as $employee) { if ($employee->isManager()) { continue; } $phoneNumbers[] = $employee->getPhoneNumber(); } return $phoneNumbers;}

Page 9: The Art of Transduction

Nav Builderfunction buildNav($links){ $html = '<ul>'; foreach ($links as $link) { $html .= '<li><a href="' . $link->getUrl() . '">' . $link->getTitle() . '</a></li>'; } $html .= '</ul>'; return $html;}

Page 10: The Art of Transduction

Reducefunction reduce(array $items, callable $callback, $initial){ $carryOver = $initial; foreach ($items as $item) { $carryOver = $callback( $carryOver, $item ); } return $carryOver;}

Page 11: The Art of Transduction

Functional Programming

• Map

• Filter

• Reduce

Page 12: The Art of Transduction

Grades as Reduce$avg = reduce( $grades, function ($carry, $item) { $total = $carry['count'] * $carry['avg'] + $item; $carry['count']++; $carry['avg'] = $total / $carry['count']; return $carry; }, ['count' => 0, 'avg' => 0])['avg'];

Page 13: The Art of Transduction

Phone Tree as Reducefunction getNonManagerNumbers($employees){ return reduce( $employees, function ($numbers, $employee) { return $employee->isManager() ? $numbers : array_merge( $numbers, [$employee->getPhoneNumber()] ); }, [] );}

Page 14: The Art of Transduction

Nav Builder as Reducefunction buildNav($links) { return '<ul>' . reduce( $links, function ($html, $link) { return $html . '<li><a href="' . $link->getUrl() . '">' . $link->getTitle() . '</a></li>'; } ) . '</ul>';}

Page 15: The Art of Transduction

What About Transducers?

Page 16: The Art of Transduction
Page 17: The Art of Transduction

What are Transducers?

Page 18: The Art of Transduction

Collection Pipeline

$numbers = collect($employeeService->getAllEmployees()) ->filter(function ($employee) { return ! $employee->isManager(); })->map(function ($employee) { return $employee->getPhoneNumber(); });

Page 19: The Art of Transduction

Installation

composer require mtdowling/transducers

Page 20: The Art of Transduction

Phone Tree as Transducer

use Transducers as t;$employees = (new EmployeeService)->getAllEmployees();$getNonManagerPhones = t\comp( t\filter(function ($employee) { return ! $employee->isManager(); }), t\map(function ($employee) { return $employee->getPhoneNumber(); }));$numbers = t\to_array($getNonManagerPhones, $employees);

Page 21: The Art of Transduction

The DataName Number Manager

Bob 303-555-1212 Yes

Sue 303-555-1234 No

Barb 303-555-1111 No

Spongebob 303-555-1001 Yes

Arnold 303-555-1313 No

Page 22: The Art of Transduction

Collection Data Pipeline

Name Number ManagerBob 303-555-1212 YesSue 303-555-1234 NoBarb 303-555-1111 No

Spongebob 303-555-1001 YesArnold 303-555-1313 No

Filter

Name Number ManagerSue 303-555-1234 NoBarb 303-555-1111 No

Arnold 303-555-1313 No

Map

Number303-555-1234303-555-1111303-555-1313

Page 23: The Art of Transduction

Transducer Data Flow

Name Number ManagerBob 303-555-1212 YesSue 303-555-1234 NoBarb 303-555-1111 No

Spongebob 303-555-1001 YesArnold 303-555-1313 No

Number303-555-1234303-555-1111303-555-1313

filtermap

NO

Page 24: The Art of Transduction

Transducer Data Sources

• Anything that you can use foreach on

• Arrays

• \Iterators

• Traversables

• Generators

Page 25: The Art of Transduction

Transducer Output• Eager

• transduce()

• into()

• to_array()

• to_assoc()

• to_string()

Page 26: The Art of Transduction

Transducer Output

• Lazy

• to_iter()

• xform()

• stream filters

Page 27: The Art of Transduction

A Bigger Example

• Incoming TSV, but should be CSV

• Date format is wrong

• Names are not capitalized

• We need days from or until birthdate, for reasons

Page 28: The Art of Transduction

Transformeruse transducers as t;

/* SNIP Definition of the functions used below */

$transformer = t\comp( t\drop(1), // Get rid of the header t\map($convertToArray), // Turn TSV to Array t\map($convertToDate), // Change to DateTimeImmutable Object t\map($addDaysFromBirthday), // Date math t\map($fixDateFormat), // Format DateTimeImmutable // to Y-m-d string $fixNames, // Capitalize names);

Page 29: The Art of Transduction

Convert TSV to Array

$convertToArray = function ($tsvRow) { $arrayRow = explode("\t", $tsvRow); $columns = ['id', 'first', 'last', 'dob']; return array_combine($columns, $arrayRow);};

Page 30: The Art of Transduction

What it does42 \t david \t stockton \t 1/1/1999

[ 'id' => 42, 'first' => 'david', 'last' => 'stockton', 'dob' => '1/1/1999']

Page 31: The Art of Transduction

Convert Date to Object$convertToDate = function ($row) { $date = DateTimeImmutable::createFromFormat( 'm/d/Y', trim($row['dob'] ) ); $row['dob'] = $date; return $row;};

Page 32: The Art of Transduction

Add Days from Birthday$now = new DateTimeImmutable();$thisYear = $now->format('Y');$addDaysFromBirthday = function($row) use ($now, $thisYear) { $dob = $row['dob']; $birthday = DateTimeImmutable::createFromFormat( 'Y-m-d', $dob->format("$thisYear-m-d") ); $timeUntilBirthday = $now->diff($birthday); $row['time_until_bday'] = $timeUntilBirthday->invert ? $timeUntilBirthday->format('%m months, %d days ago') : $timeUntilBirthday->format('%m months, %d days'); return $row;};

Page 33: The Art of Transduction

Fix Date Formatting

$fixDateFormat = function ($row) { $row['dob'] = $row['dob']->format('Y-m-d'); return $row;};

Page 34: The Art of Transduction

Uppercasing Names$capFirst = function ($row) { $row['first'] = ucfirst($row['first']); return $row;};

$capLast = function ($row) { $row['last'] = ucfirst($row['last']); return $row;};

Page 35: The Art of Transduction

Function to Build a Function

// Function to return a function$ucField = function($field) { return function ($row) use ($field) { $row[$field] = ucfirst($row[$field]); return $row; };};

Page 36: The Art of Transduction

Functionally Functional

$mungeField = function ($field, $munger) { return function ($row) use ($field, $munger) { $row[$field] = $munger($row[$field]); return $row; };};

Page 37: The Art of Transduction

Name Capitalization$fixNames = t\comp( t\map($ucField('first')), t\map($ucField('last')));$fixNamesMunge = t\comp( t\map($mungeField('first', 'ucfirst')), t\map($mungeField('last', 'ucfirst')));

Page 38: The Art of Transduction

Revisit Transformeruse transducers as t;

/* SNIP Definition of the functions used below */

$transformer = t\comp( t\drop(1), // Get rid of the header t\map($convertToArray), // Turn TSV to Array t\map($convertToDate), // Change to DateTimeImmutable Object t\map($addDaysFromBirthday), // Date math t\map($fixDateFormat), // Format DateTimeImmutable // to Y-m-d string $fixNames, // Capitalize names);

Page 39: The Art of Transduction

Where We Are

Data converted from TSV to Array

Page 40: The Art of Transduction

Where We Are

function array_to_csv($data){ $fh = fopen('php://temp', 'rw'); fputcsv($fh, $data); rewind($fh); $csv = stream_get_contents($fh); fclose($fh); return $csv;}

Page 41: The Art of Transduction

Reuse

$transformToCsv = t\comp( $transformer, t\map('array_to_csv'));

Page 42: The Art of Transduction

Data Source

$fh = fopen(__DIR__ . '/ZendconData.tsv', 'r');$reader = function () use ($fh) { while ($row = fgets($fh)) { yield $row; }};

Page 43: The Art of Transduction

Output

$write = fopen(__DIR__ . '/../data/Zendcon.csv', 'w');

Page 44: The Art of Transduction

TRANSFORM!!!11!

t\into($write, $reader(), $transformToCsv);

Page 45: The Art of Transduction

Included Transducer Functions

• map($f) - Apply $f function to each value in a collection

• filter($predicate) - If predicate returns true, retain the value, otherwise discard

• remove($predicate) - Removes items that satisfy the predicate function

• cat() - Concatenates items from nested lists

Page 46: The Art of Transduction

More Included Functions• partition($size) - Splits the source into arrays of the

specified size

• partition_by($predicate) - Splits input into arrays when value returned by $predicate changes

• take($n) - Takes $n items from the collection

• take_while($predicate) - Takes items from the collection while the $predicate is true

Page 47: The Art of Transduction

Even Moar!• take_nth($n) - Takes every $n values from the

collection

• drop($n) - Drops $n items from the start of a sequence

• drop_while($predicate) - Drops items from the collection as long as $predicate returns true

• replace(array $map) - Replaces values in the sequence according to the $map

Page 48: The Art of Transduction

Ermegerhd, even more?!• keep($f) - Keeps items when $f does not

return null

• keep_indexed($f) - Returns the non-null results of calling $f($index, $value)

• dedupe - Removes values that are the same as the previous value in an ordered sequence

• interpose($separator) - Adds the separator between each value in a sequence

Page 49: The Art of Transduction

Last list, I promise• tap($interceptor) - "Taps" into the chain, in

order to do something with the intermediate result. Does not change the sequence

• compact() - Trims out all "falsey" values from the sequence

• words() - Splits input into words

• lines() - Splits input by lines

Page 50: The Art of Transduction

Transducers

• Compose powerful data processing functions

• Interact with streams of data

• Easy to understand

• Simple to test

Page 51: The Art of Transduction

Questions?