Transducers in PHP Made Easy

Share this article

Have you heard of functional programming, high order functions, etc. before? Probably, right? However, when you hear “transducers”, do you know what those are?

Input/output vector illustration

The Definition of Transducers

We can’t define transducers without talking about reducers first. Quoting Rich Hickey:

A reducing function is just the kind of function you’d pass to reduce – it takes a result so far and a new input and returns the next result-so-far.

A transducer is a function that takes one reducing function and returns another.

Transducers were first introduced into Clojure by Rich Hickey, and ported to PHP by Michael Dowling. Transducers are a powerful way to build algorithmic transformations that you can reuse in many contexts. In this article, we’re going to take a look at how they could be useful through a set of practical examples.

Examples

We need to install the Transducers package via Composer before going any further.

composer require mtdowling/transducers

We’ll use a simple User class for the following examples.

class User
{
    public $id;
    public $name;
    public $age;

    public function __construct($id, $name, $age)
    {
        $this->id = $id;
        $this->name = $name;
        $this->age = $age;
    }

    public function __toString()
    {
        return sprintf("\n%d - %s - %d", $this->id, $this->name, $this->age);
    }
}

// demo data
$data = [
    new User(1, "younes", 24),
    new User(2, "youssef", 26),
    new User(3, "hamza", 25),
    new User(4, "ismail", 17),
];
use Transducers as t;

$uppercase = t\map(function($user) { 
    return new User($user->id, ucfirst($user->name), $user->age); 
});

$result = t\xform($data, $uppercase);

var_dump($result);

The map function is similar to the array_map PHP function: we pass a callable which, in this case, will uppercase the first letter of the user name.

We use the xform function to apply our uppercase transducer. It takes our data for the first parameter and a transducer for the second.

// output
array(4) {
  [0]=>
  object(User)#14 (3) {
    ["id"]=>
    int(1)
    ["name"]=>
    string(6) "Younes"
    ["age"]=>
    int(24)
  }
  [1]=>
  object(User)#15 (3) {
    ["id"]=>
    int(2)
    ["name"]=>
    string(7) "Youssef"
    ["age"]=>
    int(26)
  }
  [2]=>
  object(User)#16 (3) {
    ["id"]=>
    int(3)
    ["name"]=>
    string(5) "Hamza"
    ["age"]=>
    int(25)
  }
  [3]=>
  object(User)#17 (3) {
    ["id"]=>
    int(4)
    ["name"]=>
    string(6) "Ismail"
    ["age"]=>
    int(17)
  }
}

xform returns the same type as the data parameter (array in this case). We can also use to_array if you strictly want to output an array.

// ...
$result = t\to_array($data, $uppercase);
// ...

We can use to_string as well, to convert the output to a string, or into($target, $coll, callable $xf) to convert the output to a specific type. Check the documentation for more details.

use Transducers as t;

$uppercase = t\map(function($user) { 
    return new User($user->id, ucfirst($user->name), $user->age); 
});

$result = t\to_string($data, $uppercase);

var_dump($result);
// output
string(64) "
1 - Younes - 24
2 - Youssef - 26
3 - Hamza - 25
4 - Ismail - 17"

The best part about Transducers is that we can compose multiple transformations into a single transducer. For instance, let’s uppercase the first letter of the user name and remove minors.

$uppercase = t\map(function($user) { 
    return new User($user->id, ucfirst($user->name), $user->age); 
});
$removeMinors = t\filter(function($user) { 
    return $user->age >= 18;
});

$comp = t\comp(
    $uppercase,
    $removeMinors
);

$result = t\to_string($data, $comp);

var_dump($result);

The filter function is similar to the array_filter PHP function. The comp function creates a transducer from a list of transducers, in this case uppercase (using map) and removeMinors (using filter).

// output
string(48) "
1 - Younes - 24
2 - Youssef - 26
3 - Hamza - 25"

Now we have a reusable transducer composition that we can use whenever we want to reduce our data using this criteria. Check out the documentation for the list of available reducing functions.

Creating a Transducer

A reducing function takes a value as a parameter and returns a reducing function array, which must contain three elements:

  • init: A function that returns an initial value for the transducer. It’s only called at first if no initial value is provided.
  • result: The result function is called to build the final result from the call stack.
  • step: This is where you write your reduction logic – you may call it zero or many times depending on your reducer logic.

This becomes really confusing without showing some actual code, so let’s use the take transducer function as an example. It takes n items from the top of the data array.

// ....
$comp = t\comp(
    $uppercase,
    $removeMinors,
    t\take(2)
);

$result = t\to_string($data, $comp);

var_dump($result);
// output
string(33) "
1 - Younes - 24
2 - Youssef - 26"

Here is the take reducer function’s source code.

function take($n)
{
    return function (array $xf) use ($n) {
        $remaining = $n;
        return [
            'init'   => $xf['init'],
            'result' => $xf['result'],
            'step'   => function ($r, $input) use (&$remaining, $xf) {
                $r = $xf['step']($r, $input);
                return --$remaining > 0 ? $r : ensure_reduced($r);
            }
        ];
    };
}

The take function is being called several times with the result and the input parameters. On every call, it decrements the remaining variable and tests if it’s less than zero. In that case, we return a Reduced object instance, which indicates a stopping point.

Our transducer function example will drop null elements from the data. Using the previous explanation of how transducers work, we can access the $input variable, and decide whether to call the next step callback or simply return the value.

function dropNull()
{
    return function (array $xf) {
        return [
            'init'   => $xf['init'],
            'result' => $xf['result'],
            'step'   => function ($result, $input) use ($xf) {
                return $input === null
                    ? $result
                    : $xf['step']($result, $input);
            }
        ];
    };
}

We can test this by adding some null items to our $data variable.

$data = [
    null,
    new User(1, "younes", 24),
    new User(2, "youssef", 26),
    new User(3, "hamza", 25),
    new User(4, "ismail", 17),
    null
];
$result = t\to_string($data, t\dropNull());

var_dump($result);
// output
string(64) "
1 - younes - 24
2 - youssef - 26
3 - hamza - 25
4 - ismail - 17"

Conclusion

In this article, we got acquainted with a new aspect of the functional programing world called transducers. We’ve gone over the purpose of transducers, which is to make the transformation of data easier. We also went over some examples to better demonstrate the value of transducers. You now have a new tool in your developer belt or, at least, a better understanding of the transducer concept.

If you have any questions about transducers, you can post them below!

Frequently Asked Questions about Transducers in PHP

What is the basic concept of transducers in PHP?

Transducers in PHP are a type of data processing technique that allows you to create composable and reusable software components. They are essentially functions that take a reducer and return a new reducer. This concept is borrowed from Clojure and JavaScript and has been adapted to PHP. Transducers allow you to separate the process of transforming data from the actual data source, making your code more modular and easier to maintain.

How do transducers differ from traditional PHP functions?

Traditional PHP functions often combine the process of obtaining data and transforming it, which can lead to code that is difficult to maintain and reuse. Transducers, on the other hand, separate these two processes. This means you can create a transducer that transforms data in a specific way, and then use it with any data source, making your code more flexible and reusable.

Can you provide an example of a transducer in PHP?

Sure, let’s consider a simple example. Suppose you have an array of numbers and you want to increment each number by one. You could create a transducer that does this:

$increment = function($number) {
return $number + 1;
};

$transducer = function($reducer) use ($increment) {
return function($result, $input) use ($reducer, $increment) {
return $reducer($result, $increment($input));
};
};

You can then use this transducer with any reducer function and data source.

How can I use transducers with different data sources?

Transducers are designed to be used with any data source. This is because they operate on the level of individual data items, not the data source as a whole. So you can use a transducer with an array, a database query result, a stream of data from a network connection, and so on. The only requirement is that your data source must be able to work with a reducer function.

What are the benefits of using transducers in PHP?

Transducers offer several benefits. They allow you to separate the process of transforming data from the actual data source, making your code more modular and easier to maintain. They also allow you to create reusable software components that can be used with any data source. Finally, because transducers operate on individual data items, they can be more efficient than traditional PHP functions when working with large data sets.

Are there any drawbacks to using transducers in PHP?

While transducers offer many benefits, they can also be more complex to understand and use than traditional PHP functions. This is because they involve concepts from functional programming, which may be unfamiliar to some PHP developers. However, once you understand how transducers work, they can be a powerful tool for writing clean, efficient, and reusable code.

Can transducers be used with PHP frameworks?

Yes, transducers can be used with any PHP framework. They are a general-purpose data processing technique and do not depend on any specific framework features. However, some frameworks may provide their own utilities for working with transducers, which can make them easier to use.

How can I learn more about transducers in PHP?

There are many resources available for learning about transducers in PHP. You can start by reading articles and tutorials online, such as the one on SitePoint. There are also several books and online courses that cover the topic in more depth. Finally, you can experiment with writing your own transducers and using them in your PHP projects.

Are transducers used in other programming languages?

Yes, the concept of transducers originated in the Clojure programming language and has since been adopted by several other languages, including JavaScript and PHP. Each language implements transducers in its own way, but the basic concept is the same.

Can I use transducers with PHP’s built-in array functions?

Yes, you can use transducers with PHP’s built-in array functions. However, keep in mind that these functions may not be as efficient as using a transducer, especially when working with large arrays. This is because PHP’s array functions often create new arrays, whereas a transducer can transform data in place.

Younes RafieYounes Rafie
View Author

Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.

BrunoSfunctional programmingOOPHPPHPreducertransducer
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week