php-fp / php-fp-reader
PHP中的Reader monad实现。
Requires (Dev)
- phpunit/phpunit: ^5.3
This package is not auto-updated.
Last update: 2023-06-10 12:11:38 UTC
README
简介
有时,当你将多个函数组合成管道时,可能会有一些值需要在整个过程中使用——比如在创建Web应用响应时可能需要Request
对象。无论如何,将此值作为参数传递给需要它的函数可能会变得混乱,如果我们还不知道它的值,几乎不可能做到!这就是Reader的作用所在。
Reader允许你构建依赖于值的计算——称为环境——而不需要指定它。然后,当你希望运行计算时,只需调用run($environment)
,这个值就会在整个计算过程中可用。如果你真的必须,可以将其视为类型安全的依赖注入。Web服务器的类比是一个很好的例子:你可以将整个请求生命周期作为Reader计算来编写,然后只需在每个新请求时以新的环境运行它!这可能会看起来像这样
<?php function update($key, $value) { return function ($xs) use ($key, $value) { return array_merge($xs, [$key => $value]); }; } Reader::of([]) ->map(update('status', 200)) // Add a response status. ->chain( function ($res) // Currently ['status' => 200] { return Reader::ask()->map( function ($req) use ($res) { return update( 'message', 'This is ' + $req['path'], ) ($response); } ); } ) // ['status' => 200, 'message' => 'This is /hello'] ->run(['path' => '/hello']);
在这个例子中,我们可以看到对Reader::ask()
的映射给我们提供了访问尚未指定的环境的权限,因此我们可以将其添加到响应对象中。在底层,这与IO类似:计算被排队,而run
最终触发过程并评估它们。所以,当只关心Reader的值时使用map
,当也关心Reader的环境时使用chain
。你可能会得到一些非常棒的需求生命周期处理器
<?php return Reader::of([]) ->chain($authenticate) // Requires the Request. ->map($addStatus) // Only cares about the response. ->map($addToken) // Doesn't care about the Request. ->chain($setContentType); // Checks requested type in the Request.
好看吗?这些精确描述了数据通过项目的流动的漂亮管道。
API
以下类型签名中,构造函数和静态函数被写成纯语言(如Haskell)中看到的样子。其他包含一个管道,管道前面的类型表示当前Reader实例的类型,管道后面的类型表示函数。
of :: a -> Reader b a
Reader实例的应用性构造函数。Reader涉及到两种类型,但我们只需要在这里声明其中一种——另一种可以在稍后通过链函数或run
推断出来。
<?php use PhpFp\Reader\Reader; assert(Reader::of(2)->run(null) == 2);
ask :: -> Reader b b
Reader monad的主要魔法:环境访问。这实际上只是一个Reader包装的恒等函数,因此实际上并不难理解这个魔法的原理(使用相同的大致模式用于Writer::tell()
)。
<?php use PhpFp\Reader\Reader; $reader = Reader::of(2)->chain( function ($x) { return Reader::ask()->map( function ($y) use ($x) { return $x + $y; } ); } ); assert($reader->run(4) == 6);
这必须是一个函数,因为PHP的限制:我们无法将类实例作为静态属性添加到类中。
chain :: Reader a c | (a -> Reader b c) -> Reader b c
与所有单子一样,chain
是其中施展魔法的地方。在 ask
的示例和介绍中展示了这种用法最常见的情况,但当然也可以在不使用便利值的情况下使用。
<?php use PhpFp\Reader\Reader; $reader = Reader::of(2)->chain( function ($x) { return Reader::of($x + 2); } ); assert($reader->run(null) == 4);
map :: Reader a c | (a -> b)
标准函子映射:这只是在 Reader 中转换值。请注意(再次)Reader 函数与 IO 类似,映射函数实际上只有在调用 run($env)
时才会被调用,但这不应该影响你编写纯函数式代码!
<?php use PhpFp\Reader\Reader; $reader = Reader::of(2)->map( function ($x) { return $x * 22; } ); assert($reader->run(null) == 44);
ap :: Reader (a -> b) c | Reader a c -> Reader b c
标准应用函数应用。如果你有一个被 Reader 包裹的函数,以及一个被 Reader 包裹的参数,这就是适合你的函数(记住,你的映射函数始终可以返回一个函数!)
<?php use PhpFp\Reader\Reader; $reader = Reader::of( function ($x) { return $x + 5; } )->ap(Reader::of(2)); assert($reader->run(null) == 7);
run :: Reader a b | b -> a
用于分割 Reader 单子的方法。这个类型签名很有趣:本质上,Reader 是由一个类型 a
定义的,它依赖于另一个类型 b
,这也是函数 b -> a
的良好描述。在许多方面,Reader 只是复杂且灵活的函数组合,这就是为什么它被称为 '箭头单子'(欢迎自由 Google 这个!)
<?php use PhpFp\Reader\Reader; assert(Reader::of('hello')->run(null) == 'hello');
贡献
Reader 单子可能不会对代码库产生重大更改,但如果你有任何想法,请随时提交 PR(我猜它们可能是更多类型类的实现……)
与其他单子一样,你随时欢迎提交 PR 来使文档更清晰,以及如果有任何部分让你感到困惑的问题。这是一个学习资源,也是一个包!