DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations

Reuse your closures with functors

Giorgio Sironi user avatar by
Giorgio Sironi
·
Dec. 28, 10 · Interview
Like (0)
Save
Tweet
Share
11.22K Views

Join the DZone community and get the full member experience.

Join For Free
I like PHP closures and their superset, anomyous functions, as they implement the Command pattern very well when used correctly. However I feel that sometimes they are:
  • difficult to reuse: if you want to reuse a closure or anonymous function in two different places, you'll have to share the creation code by putting it in a shared class or method. We have no separation between the creation code (which for an object would be a new operator and the code of the closure itself. It's a bit like eval().
  • Difficult to force contracts on. You can't type hint a closure: even if a closure takes five arguments and another no arguments at all, you can't differentiate between them as parameters in a method signature.
For the latter case, you can just use the undocumented Closure type hint, but it will just catch if an object is passed to the method in question, not if the wrong closure is.
function takesAsInputAnotherFunction(Closure $closure)

This hack is explicitly stated as an implementation detail which should not be relied upon in the PHP manual

__invoke() to the rescue

What if we wanted instead, a closure which we can instance even more than one time (maybe with different variables), and that we could type hint?
In this case, we need to return to the object-oriented paradigm by defining a class, and then use __invoke() to keep the syntactic sugar of being able to call it very simply:

$object($argument1, $argument2);$object($argument1, $argument2);

This kind of object is called a functor.

The definition of a class is longer than the creation code of a closure, so there is a trade-off: we have better specified code but we lose conciseness. Type hints are not only a defensive programming construct: they serve also as documentation as the next programmer reading a method's signature would learn what he can pass in just from the parameters hints instead of going reading the tests or grepping for the method in his working copy:

grep -r '->methodName(' .

becomes just a quick look at the definition

public function methodName(AdderCallback $callback) { // ...

However the expressing power of an object implementing __invoke() and of a closure are equivalent, and when you find yourself rewriting the same closure over and over, you may want to keep it in only one place. You'll have to create a class to hold the creation code anyway, so why not making it a class of its own?

For the PHP interpreter, closure and functors are really the same thing, and are even more swappable than callbacks built with array($object, 'methodName'). is_callable() returns true for both of them, and you can write $closure() as well as $functor().

Example

Here is some code that you can hack if you want to play with closures and functors.

<?php
$square = function($value) {
return $value * $value;
};

var_dump($square(3));

/**
* a little more noisy, but equivalent in usage and functionality-
*/
class SquareCallback
{
public function __invoke($value)
{
return $value * $value;
}
}

$squareObject = new SquareCallback;
var_dump($squareObject(3));

// using PHP utilities which works on callback: total equivalency
var_dump(is_callable($squareObject));

$array = array(0, 1, 2);
var_dump(array_map($square, $array));
var_dump(array_map($squareObject, $array));

// currying vs collaborators: total equivalency
$toAdd = 5;
$adder = function($value) use ($toAdd) {
return $value + $toAdd;
};
var_dump($adder(3));

/**
* A LOT more noisy
*/
class AdderCallback
{
private $numberToAdd;

public function __construct($numberToAdd)
{
$this->numberToAdd = $numberToAdd;
}

public function __invoke($value)
{
return $value + $this->numberToAdd;
}
};
$adder = new AdderCallback(5);
var_dump($adder(3));

// but only classes can do this
function someMethodOrFunction(AdderCallback $adder)
{
// ...
}
Functor

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Distributed Tracing: A Full Guide
  • Software Maintenance Models
  • Solving the Kubernetes Security Puzzle
  • Metrics Part 2: The DORA Keys

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: