php 编程规范 函数
I was so excited about my previous article about PHP macros, that I thought it would be fun for us to explore the intersection of macros and functional programming.
我对上一篇有关PHP宏的文章感到非常兴奋,以为探索宏与函数式编程的交集对我们来说很有趣。
PHP is already full of functions, with object oriented patterns emerging relatively late in its life. Still, PHP’s functions can be cumbersome, especially when combined with variable scope rules…
PHP已经充满了功能,面向对象的模式在其生命后期才出现。 不过,PHP的功能可能很繁琐,尤其是与可变范围规则结合使用时……
Consider the following ES6 (JavaScript) code:
考虑以下ES6(JavaScript)代码:
let languages = [
{"name": "JavaScript"},
{"name": "PHP"},
{"name": "Ruby"},
];
const prefix = "language: ";
console.log(
languages.map(
language => prefix + language.name
)
);
In this example, we see languages
defined as a list of programming languages. Each programming language is combined with a constant prefix and the resulting array is logged to the console.
在此示例中,我们将languages
定义为编程语言列表。 每种编程语言都与常量前缀结合在一起,并将结果数组记录到控制台。
This is about as much JavaScript as we’re going to see. If you’d like to learn more ES6, check out the documentation for BabelJS.
这与我们将要看到JavaScript差不多。 如果您想了解更多ES6,请查阅BabelJS的文档 。
Compare this to similar PHP code:
将此与类似PHP代码进行比较:
$languages = [
["name" => "JavaScript"],
["name" => "PHP"],
["name" => "Ruby"],
];
$prefix = "language: ";
var_dump(
array_map(function($language) use ($prefix) {
return $prefix . $language;
}, $languages);
);
It’s not significantly more code, but it isn’t as clear or idiomatic as the JavaScript alternative. I often miss JavaScript’s expressive, functional syntax, when I’m building PHP things. I want to try and win back some of that expressive syntax!
它不是明显更多的代码,但是它不像JavaScript替代品那样清晰明了。 在构建PHP东西时,我经常会错过JavaScript的表达性功能语法。 我想尝试赢回一些表达语法!
入门 (Getting Started)
As in the previous article, we’re going to use Márcio Almada’s Yay library. We can install it with:
与上一篇文章一样,我们将使用MárcioAlmada的Yay库 。 我们可以通过以下方式安装它:
composer require yay/yay:*
We’ll put our Yay code (very similar to PHP, but with macro support) in files ending in .yphp
and compile them to regular PHP with:
我们将Yay代码(与PHP非常相似,但具有宏支持)放在.yphp
结尾的文件中,并使用以下命令将其编译为常规PHP:
vendor/bin/yay before.yphp > after.php
一个简单的解决方案 (A Simple Solution)
The first thing I want to try is mapping over a collection of things, with syntax more similar to JavaScript. I want something like:
我想尝试的第一件事是使用一组与JavaScript更相似的语法来映射一组事物。 我想要类似的东西:
$list->map(function($item) {
return strtoupper($item);
});
We can already do that, but it requires creating a special PHP class. We can’t use normal array functions on this special class, so the class also needs to have methods to convert to and from native PHP arrays.
我们已经可以做到,但是它需要创建一个特殊PHP类。 我们不能在这个特殊的类上使用普通的数组函数,因此该类还需要具有与本机PHP数组进行相互转换的方法。
Instead, let’s make a macro:
相反,让我们创建一个宏:
macro {
T_VARIABLE·A->map(···expression)
} >> {
array_map(···expression, T_VARIABLE·A)
}
This macro will match anything that looks like a variable, followed by ->map(...)
; and convert it to the array_map(...)
alternative. So, if we feed it this code:
这个宏将匹配任何看起来像变量的东西,然后是->map(...)
; 并将其转换为array_map(...)
替代。 因此,如果我们将以下代码提供给它:
$languages = [
["name" => "JavaScript"],
["name" => "PHP"],
["name" => "Ruby"],
];
var_dump(
$languages->map(function($language) {
return strtoupper($language["name"]);
})
);
…it will compile to:
…它将编译为:
$languages = [
["name" => "JavaScript"],
["name" => "PHP"],
["name" => "Ruby"],
];
var_dump(
array_map(function($language) {
return strtoupper($language["name"]);
}, $languages)
);
If you haven’t already, now is a good time to read the previous article, which explains how this code works. You definitely shouldn’t try the following examples if you’re not comfortable with what you’ve seen so far…
如果您还没有的话,现在是阅读上一篇文章的好时机, 该文章解释了此代码的工作方式。 如果您不满意到目前为止所看到的内容,则绝对不应尝试以下示例……
复杂的解决方案 (A Complex Solution)
We’re off to a good start! There’s still a significant difference between JavaScript and PHP: variable scope. If we wanted to use that prefix, we would have to bind it to the callback given to array_map(...)
. Let’s work around that.
我们有一个良好的开端! JavaScript和PHP之间仍然存在显着差异:变量范围。 如果要使用该前缀,则必须将其绑定到给定array_map(...)
的回调。 让我们解决这个问题。
To begin with, we’ll re-define the stringify macro we created last time:
首先,我们将重新定义上次创建的stringify宏:
macro {
→(···expression)
} >> {
··stringify(···expression)
}
This will replace →(...)
with a string version of whatever is matched between the parens. Then we need to define a pattern for matching arrow function syntax:
这将用括号之间匹配的字符串版本替换→(...)
。 然后,我们需要定义一个模式以匹配箭头函数语法:
macro {
T_VARIABLE·A->map(
T_VARIABLE·parameter1
T_DOUBLE_ARROW·arrow
···expression
)
} >> {
// ...replacement definition
}
This will match $value => $return
. We also need to match variations of this which accept keys:
这将匹配$value => $return
。 我们还需要匹配接受密钥的变量的变体:
macro {
T_VARIABLE·A->map(
T_VARIABLE·parameter1, T_VARIABLE·parameter2
T_DOUBLE_ARROW·arrow
···expression
)
} >> {
// ...replacement definition
}
This will match $key, $value => $return
. We could combine these into a single matcher, but that would get complicated pretty quickly. So, I’ve opted for two macros, instead.
这将与$key, $value => $return
匹配。 我们可以将它们组合成一个匹配器,但是很快就会变得很复杂。 因此,我选择了两个宏。
The next step is to capture the variable context, and the mapping function:
下一步是捕获变量上下文和映射函数:
macro {
// ...capture pattern
} >> {
eval('
$context = get_defined_vars();
return array_map(
function($key, $value) use ($context) {
// ...give context to map function
},
array_keys(' . →(T_VARIABLE·A) . '),
array_values(' . →(T_VARIABLE·A) . ')
);
')
}
We use the get_defined_vars()
function to store all currently defined variables in an array. The scope of these is the same as that in which eval
is executed. We then pass the context as a bound parameter to the closure. We also provide both keys and values for the source list.
我们使用get_defined_vars()
函数将所有当前定义的变量存储在数组中。 它们的范围与执行eval
的范围相同。 然后,我们将上下文作为绑定参数传递给闭包。 我们还为源列表提供键和值。
This is an odd side-effect of how array_map
works. We’re kinda depending on the order being the same for the return values of array_keys
and array_values
, but it’s a pretty safe bet.
这是array_map
工作方式的一个奇怪的副作用。 我们有点依赖于array_keys
和array_values
返回值的顺序相同,但这是一个相当安全的选择。
Finally, we need to work out how to use the context in the call to array_map
:
最后,我们需要弄清楚如何在调用array_map
使用上下文:
macro {
// ...capture pattern
} >> {
eval('
$context = get_defined_vars();
return array_map(
function($key, $value) use ($context) {
extract($context);
' . →(T_VARIABLE·parameter1) . ' = $value;
return (' . →(···expression) . ');
},
array_keys(' . →(T_VARIABLE·A) . '),
array_values(' . →(T_VARIABLE·A) . ')
);
')
}
We use the extract
function to create local variables for each of the keys and values in the $context
array. This means every variable in scope outside of array_map
will be available inside as well. We return the value of ···expression
, just after re-setting the user-defined value variable.
我们使用extract
函数为$context
数组中的每个键和值创建局部变量。 这意味着array_map
之外范围内的每个变量也将在内部可用。 在重新设置用户定义的值变量之后,我们返回· ···expression
值。
For the key/value variation of this, we need to set both variables. The complete macros, for both variants, are:
对于这种键/值的变化,我们需要设置两个变量。 两种变体的完整宏是:
macro {
T_VARIABLE·A->map(
T_VARIABLE·parameter1
T_DOUBLE_ARROW·arrow
···expression
)
} >> {
eval('
$context = get_defined_vars();
return array_map(
function($key, $value) use ($context) {
extract($context);
' . →(T_VARIABLE·parameter1) . ' = $value;
return (' . →(···expression) . ');
},
array_keys(' . →(T_VARIABLE·A) . '),
array_values(' . →(T_VARIABLE·A) . ')
);
')
}
macro {
T_VARIABLE·A->map(
T_VARIABLE·parameter1, T_VARIABLE·parameter2
T_DOUBLE_ARROW·arrow
···expression
)
} >> {
eval('
$context = get_defined_vars();
return array_map(
function($key, $value) use ($context) {
extract($context);
' . →(T_VARIABLE·parameter1) . ' = $key;
' . →(T_VARIABLE·parameter2) . ' = $value;
return (' . →(···expression) . ');
},
array_keys(' . →(T_VARIABLE·A) . '),
array_values(' . →(T_VARIABLE·A) . ')
);
')
}
These macros will accept the following input:
这些宏将接受以下输入:
$languages = [
["name" => "JavaScript"],
["name" => "PHP"],
["name" => "Ruby"],
];
$prefix = "language: ";
var_dump(
$languages->map(
$language => $prefix . $language["name"]
)
);
var_dump(
$languages->map(
$key, $value => ($key + 1) . ": " . $value["name"]
)
);
… and generate code resembling the following:
…并生成类似于以下内容的代码:
$languages = [
["name" => "JavaScript"],
["name" => "PHP"],
["name" => "Ruby"],
];
$prefix = "language: ";
var_dump(
eval('
$context = get_defined_vars();
return array_map(
function($key, $value) use ($context) {
extract($context);
' . '$language' . ' = $value;
return (' . '$prefix . $language["name"]' . ');
},
array_keys(' . '$languages' . '),
array_values(' . '$languages' . ')
);
')
);
var_dump(
eval('
$context = get_defined_vars();
return array_map(
function($key, $value) use ($context) {
extract($context);
' . '$key' . ' = $key;
' . '$value' . ' = $value;
return (' . '($key + 1) . ": " . $value["name"] . ' . ');
},
array_keys(' . '$languages' . '),
array_values(' . '$languages' . ')
);
')
);
Once again, we can see that the generated code isn’t the prettiest. But it does overcome a rather annoying variable scope limitation, and allows shorthand syntax for the traditional array_map
approach.
再一次,我们可以看到生成的代码不是最漂亮的。 但是它确实克服了一个相当烦人的变量范围限制,并允许传统的array_map
方法使用简写语法。
Are you impressed? Disgusted? Let us know in the comments below!
您印象深刻吗? 恶心吗? 在下面的评论中让我们知道!
翻译自: https://www.sitepoint.com/functional-programming-in-php-with-macros/
php 编程规范 函数