php-fp/php-fp-reader

此包已被废弃,不再维护。未建议替代包。

PHP中的Reader monad实现。

dev-master 2016-04-18 00:00 UTC

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 来使文档更清晰,以及如果有任何部分让你感到困惑的问题。这是一个学习资源,也是一个包!