PHP5.3Lambda函数与闭包

未分类 xiaogua 190℃ 0评论

PHP 5.3 will have a lot of exciting new features. One of the most important one for me is the introduction of lambda functions and closures support. I won’t talk too much about what lambda functions or closures are, as you can find many good blog posts describing them in great details. To sum up, a lambda function is an anonymous PHP function that can be stored in a variable and passed as an argument to other functions or methods. A closure is a lambda function that is aware of its surrounding context.

The first obvious use for lambda functions and closures is in conjunction with thearray_map()array_reduce(), and array_filter() native PHP functions:

PHP 5.3 具备了许多令人兴奋的特性,对我而言最重要的当属 Lambda 函数和闭包的支持,这里我不会说太多关于什么是Lambda和闭包函数,你可以在许多博客帖子里面找到关于这些概念的详细描述。总的来说,Lambda 函数是一个既可以赋值给变量还可以作为其他函数或方法的参数的PHP匿名函数。闭包是一个具备上下文关系的Lambda函数。

最明显的使用Lambda函数和闭包是PHP的原生函数,诸如:array_map(),array_reduce(),和array_filter().


$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

The above example filters the input array by removing all values greater than 2:

上面的例子过滤了传入的数组,并将数组值大于2的元素过滤出来。

$output == array(2 => 3, 3 => 4, 4 => 5)

function ($v) { return $v > 2; } is the lambda function definition and it can be stored in a PHP variable to be reusable:

function($v){ return $v>2;} 是一个 Lambda 函数,且他能被存储为PHP变量并被重复调用。


$max_comparator = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comparator);

But what if I want to change the maximum number allowed in the filtered array? I can either create another lambda function or use a closure:

但要是我想改变过滤时,数组中可被过滤的最大值呢?我要么再写个Lambda函数或者用闭包实现。


$max_comparator = function ($max)
{
 return function ($v) use ($max) { return $v > $max; };
};

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comparator(2));

Now, the $max_comparator function takes the maximum allowed number and returns a function that is different according to this maximum. Even for such a contrived example, I hope you see the great power it gives you!

Closures also opens up a lot of great possibilities, like the implementation of   the cryptic Y-combinator, as demonstrated by Stanislav Malyshev in one of his recent blog post:

好了,$max_comparator 函数能够读取到这个过滤条件maximum,同时也可以返回一个具有不同maximum值的函数。尽管这只是个简单例子,但我希望你能体会到这个特性的强大!:)

闭包也带来了许多不错的特性,像这Y-combinator的实现,比如Stanislav Malyshev 在他最新的博客帖子里面举的例子。


function Y($F)
{
 $func = function ($f) { return $f($f); };

return $func(function ($f) use($F)
 {
 return $F(function ($x) use($f)
 {
 $ff = $f($f);

return $ff($x);
 });
 });
}

Today, I want to talk about yet great another example on how to use lambda functions and closures. You will see that it can simplify your code a lot when used appropriately.

If you have read my blog recently, you know that I am quite obsessed with dependency injection these days. This post will show you how to implement a very simple but still full-featured dependency injection container, thanks to the new features of PHP 5.3.

A dependency injection container must be able to manage two different kind of data: objects and parameters. And objects must be created on-demand the first time they are accessed from the container.

Using a simple class that implements the magic __get() and __set() methods, we can easily manage both the objects and the parameters:

今天,我想要探讨另外一个关于如何使用 Lambda 函数和闭包的例子。你将会看到当你恰当的使用它时,你的代码将精简不少。

如果你也读了我最近的博客,你会发现,我近几日很着迷于依赖注入。这个例子将向你展示如何实现一个简单的,但又充分具备依赖注入特性的容器,这全都归功于PHP5.3的特性。

一个依赖注入容器必须能处理两个不同类型的数据:对象和参数。且对象必须以通过容器首次请求他们时被创建。

例如一个实现了__get() and __set()方法的简单类,我们也能轻松的处理对象和参数。


class DIContainer
{
 protected $values = array();

function __set($id, $value)
 {
 $this->values[$id] = $value;
 }

function __get($id)
 {
 return is_callable($this->values[$id]) ? $this->values[$id]($this) : $this->values[$id];
 }
}

Using the container is quite simple:

使用容器变得很简单:


$container = new DIContainer();

// define the parameters
$container->cookie_name = 'SESSION_ID';
$container->storage_class = 'SessionStorage';

// defined the objects
$container->storage = function ($c)
{
 return new $c->storage_class($c->cookie_name);
};
$container->user = function ($c)
{
 return new User($c->storage);
};

// get the user object
$user = $container->user;

// the above call is roughly equivalent to the following code:
// $storage = new SessionStorage('SESSION_ID');
// $user = new User($storage);

The definition of an object is done by defining a lambda function that returns an instance of the object.

通过定义返回对象实例的闭包函数来实现对象。

The __get() method checks if the value associated with a key is a PHP callable (and lambda functions are callables) to make the difference between an object definition and a parameter.

Also notice how we call the lambda:

__get() 方法会检测key所对应的value是否能被调用(Lambda函数是能被调用的),来区别一个对象定义和普通参数。

我们应该这样调用Lambda


$this->values[$id]($this)

The trick here is to pass the container as an argument of the lambda function so that it can use the container to access parameters and other objects managed by the container.

We can make the container a bit better by adding error support:

该技巧是将Lambda函数的参数丢给容器,以便于用容器获取参数和其他对象。我们给这个容器加了点容错处理。


class DIContainer
{
 protected $values = array();

function __set($id, $value)
 {
 $this->values[$id] = $value;
 }

function __get($id)
 {
 if (!isset($this->values[$id]))
 {
 throw new InvalidArgumentException(sprintf('Value "%s" is not defined.', $id));
 }

return is_callable($this->values[$id]) ? $this->values[$id]($this) : $this->values[$id];
 }
}

Defining objects with lambda functions is great because the developer can do whatever he wants to actually create and configure instances. But we still need to implement one important feature of any dependency injection container: shared instances. This can be done manually like this:

用Lambda函数生成对象真是太棒了,因为开发者能够做任何他想要实现和配置实例。但我们仍需实现一个任何依赖注入容器的重要特性:shared instances.比如可以这样做:


$container->user = function ($c)
{
 static $object;

if (is_null($object))
 {
 $object = new User($c->storage);
 }

return $object;
};

By declaring a static variable in the lambda function, the first time it is called, the object is created and returned. For all subsequent calls, the same instance will always be returned.

It works as expected, but this is quite repetitive, tedious, and error prone. For all instances that must be unique, we need to add this boilerplate code. Instead, I want to support shared instances as a native feature of the container itself. Thanks to closures, that’s quite easy to accomplish:

在Lambda函数中声明一个静态变量,首次调用时,该对象被实例化并返回。在其他调用中,就一直使用相同的示例。

现在它能正常调用,但这太冗余,也容易出错。为了让所有实例保持唯一性。我们需要增加模块代码。相反,我想要具备一个可分享的示例来增加该容易的原生特性。得益于闭包,我们能很轻松的实现:


class DIContainer
{
 // ...

function asShared($callable)
 {
 return function ($c) use ($callable)
 {
 static $object;

if (is_null($object))
 {
 $object = $callable($c);
 }

return $object;
 };
 }
}

The asShared() method wraps the given lambda function to add the needed logic we have seen before. Declaring an object as unique for a given container is now dead simple:

asShared()方法包裹了一层原先我们看到的所需的逻辑到Labda函数。现在声明对于容器来说声明一个唯一的对象将简单得要死:


$c->user = $c->asShared(function ($c) { return new User($c->storage); });

In less than 30 lines of PHP, we have coded a full-featured dependency injection container. Quite impressive!

If we remove the need for shared instance, and if we define parameters and objects as lambdas, we can even compact the code so that it fits in a tweet:

这样至少少去30行PHP代码,我们已经编制了一个完整的特性的DIC。好感动。

要是我们要移除这个可共享的实例,又或是我们将参数或对象定义为Lambdas.我们甚至可以压缩编码,以便于匹配tweet.


class Container {
 protected $s=array();
 function __set($k, $c) { $this->s[$k]=$c; }
 function __get($k) { return $this->s[$k]($this); }
}

PHP 5.3 is really a great step forward for the PHP language.

PHP5.3 真是PHP语言的一次大飞跃!

转载请注明:南瓜的小窝 » PHP5.3Lambda函数与闭包

喜欢 (1)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址