Using Generators to Beat Memory Bloat

By: Korvin Szanto

github.com/korvinszanto

@korvinszanto

php.ug/slackinvite​

What is a List?

  • Includes arrays and Iterators (Traversable)
  • Can be finite or infinite
  • You can hint for lists with the `iterable` keyword

Working with Lists

  • Each:        Iterate over the items in a list
  • Map:        Alter items in a list using a callback
  • Filter:       Filter out items in a list using a callback
  • Slice:        Get a segment of a list

Generators

  • Added in PHP 5.5
  • They are a compromise
  • They are way better 
/**
 * Run a callback on each item in an array
 */
function each(array $array, callable $callback) {

    // Loop over items in an array and send them to a callback
    foreach ($array as $key => $item) {
        $callback($item, $key);
    }

    // Pass back the given array
    return $array;

}
// Create a finite list
$list = [1, 2, 3, 4];

// For each item in the list, echo out the value
custom_each($list, function($value) {
    echo "{$value} - ";
});
/**
 * Returns an array with values mapped through a callback
 */
function custom_map(callable $callback, array $array) {

    $results = [];

    // Run all items in the array through a callable
    foreach ($array as $key => $item) {
        // Store the result
        $results[$key] = $callback($item);
    }

    // Return the mapped array
    return $results;

}

// Create a finite list
$list = [1, 2, 3, 4];

// For each item in the list, echo out the value
$mappedList = custom_map(function($value) {
    return $value * 2;
}, $list);

// Output the mapped list as json
echo json_encode($mappedList);
/**
 * Filter an array through a callback
 */
function custom_filter(array $array, callable $callback) {
    
    $results = [];

    // Run all items in the array through a callable
    foreach ($array as $key => $item) {

        // Test the item with the filter callback
        if ($callback($item)) {
            // Store the result
            $results[$key] = $item;
        }
    }

    // Return the filtered array
    return $results;
    
}
// Create a finite list
$list = [1, 2, 3, 4];

// For each item in the list, echo out the value
$mappedList = custom_filter($list, function($value) {
    // Check if the value is odd
    return $value % 2;
});

// Output the mapped list as json
echo json_encode($mappedList);
// Make a list of user id's to check
$users = range(1, 20);
// Make a list of user id's to check
$users = range(1, 20);

// Filter out non-numeric stuff
$forceNumeric = 'is_numeric';
// Make a list of user id's to check
$users = range(1, 20);

// Filter out non-numeric stuff
$forceNumeric = 'is_numeric';

// Map ids to the user inflate method
$inflateUsers = 'User::inflateByID';

// Make a list of user id's to check
$users = range(1, 20);

// Filter out non-numeric stuff
$forceNumeric = 'is_numeric';

// Map ids to the user inflate method
$inflateUsers = 'User::inflateByID';

// Ensure that we don't have any null values
$forceObjects = 'is_object';
// Make a list of user id's to check
$users = range(1, 20);

// Filter out non-numeric stuff
$forceNumeric = 'is_numeric';

// Map ids to the user inflate method
$inflateUsers = 'User::inflateByID';

// Ensure that we don't have any null values
$forceObjects = 'is_object';

// Filter out non-admins
$onlyAdmins = function(User $user) {
    return $user->isAdmin();
};
// Make a list of user id's to check
$users = range(1, 20);

// Filter out non-numeric stuff
$forceNumeric = 'is_numeric';

// Map ids to the user inflate method
$inflateUsers = 'User::inflateByID';

// Ensure that we don't have any null values
$forceObjects = 'is_object';

// Filter out non-admins
$onlyAdmins = function(User $user) {
    return $user->isAdmin();
};

// filter, map, filter, filter
$filtered = custom_filter($users, $forceNumeric);
$mapped   = custom_map($inflateUsers, $filtered);
$filtered = custom_filter($mapped, $forceObjects);
$admins   = custom_filter($filtered, $onlyAdmins);
// Make a list of user id's to check
$users = range(1, 20);

// Filter out non-numeric stuff
$forceNumeric = 'is_numeric';

// Map ids to the user inflate method
$inflateUsers = 'User::inflateByID';

// Ensure that we don't have any null values
$forceObjects = 'is_object';

// Filter out non-admins
$onlyAdmins = function(User $user) {
    return $user->isAdmin();
};

// filter, map, filter, filter
$admins = custom_filter(
    custom_filter(
        custom_map(
            $inflateUsers,
            custom_filter($users, $forceNumeric)),
        $forceObjects),
    $onlyAdmins);

There has to be a better way.

Enter Collections

// filter, map, filter, filter
$admins = custom_filter(
    custom_filter(
        custom_map(
            $inflateUsers,
            custom_filter($users, $forceNumeric)),
        $forceObjects),
    $onlyAdmins);
// Turn our list into a Collection object
$users = new Collection($users);

// filter, map, filter, filter
$admins = $users
    ->filter($forceNumeric)
    ->map($inflateUsers)
    ->filter($forceObjects)
    ->filter($onlyAdmins);

CODE

 

We've done it!

We're working with lists now!

You start thinking...

What happens when

your dataset

starts getting 

Bigger?

CODE

 

This Won't Scale

Enter Generators

/**
 * Returns an array with values mapped through a callback
 */
function custom_map(callable $callback, iterable $array) {

    $results = [];

    // Run all items in the array through a callable
    foreach ($array as $key => $item) {
        // Store the result
        $results[$key] = $callback($item);
    }

    // Return the mapped array
    return $results;
}
/**
 * Returns an array with values mapped through a callback
 */
function custom_map(callable $callback, iterable $array) {



    // Run all items in the array through a callable
    foreach ($array as $key => $item) {
        // Yield the result
        yield $key => $callback($item);
    }



}

Generators are confusing

A function that contains a yield statement works differently

Once they "close" they cannot be reopened

Certain things are backwards

Iterators are harder

Iterators are annoying

They require a lot of code to do simple things

When they get complex, you probably need multiple iterators instead of one

Wrapping your mind around how they iterate takes time

But.. PHP comes with a ton of them already

Iterators already solve a lot of our problems

CODE

 

Generator Powered Collections

buttress/collecterator

in composer

github.com/korvinszanto

@korvinszanto

php.ug/slackinvite​

https://joind.in/talk/54236

Review this talk

Using Generators to Beat Memory Bloat

By Korvin Szanto

Using Generators to Beat Memory Bloat

  • 1,136