php-fp/php-fp-writer

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

PHP中的Writer单子的实现。

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

This package is not auto-updated.

Last update: 2023-06-05 00:28:46 UTC


README

简介

Writer单子用于在计算中进行日志记录。内部,Writer存储两个值:一个计算值(就像任何其他单子),以及一个存储日志的monoid值。Monoid必须有一个empty值和一个concat方法

<?php

class Monoid
{
    public static function empty()
    {
        return new Monoid([]);
    }

    public function __construct(array $xs)
    {
        $this->value = $xs;
    }

    public function concat($that) : Monoid
    {
        return new Monoid(array_merge($this->value, $that->value));
    }
}

这个简单的monoid内部使用一个数组,空列表是empty值,列表连接是concat。你可能会使用类似这样的monoid,因为日志可以构建一个描述过程的字符串数组

<?php

use PhpFp\Writer\Writer;
// And your monoid from above...

$halve = function ($number) {
    $log = new Monoid(['Halving the number']);

    return Writer::tell($log)->map(
        function () use ($number)
        {
            return $number / 2;
        }
    );
};

list ($xs, $log) = $halve(16)->chain($halve)->run();

assert($xs == 4); // The inner value is halved twice.
assert($log->value == ['Halving the number', 'Halving the number']);

尽管不是记录的唯一方法(你可以返回自己构建的Writer),但我发现tell提供了最易读的方法。关于tell如何工作的更多信息,请参阅下面的API文档。

当然,map会转换内部值而不影响外部结构。换句话说,当不需要记录时使用map,当想要记录某些内容时使用chain(可能使用tell)。这是一个单子,实际上可能最有用,用于解释复杂的计算,虽然你也可以用它做更多!比如在你的应用程序的控制流中,Writer构建一个服务器错误日志?或者一个用户的操作日志?

API

在以下类型签名中,构造函数和静态函数被写为纯语言(如Haskell)中看到的那样。其他包含一个管道,管道前面的类型代表当前Writer实例的类型,管道后面的类型代表函数。

of :: Monoid m => a -> m b -> Writer a (m b)

此方法命名不佳,但通常由类似项目称为of。如果of让你认为这是一个应用构造函数,那么不要相信——它不是。应用构造函数包装一个值,因此只接受一个值。然而,Writer不能不知道monoid是什么而构建,所以必须传递它。

但这是一种产生Writer实例的好方法,无需担心内部结构,这是一个好消息。

<?php

use PhpFp\Writer\Writer;

assert(Writer::of(2, 'PhpFp\Maybe\Maybe')->run() [0] == 2);

由于PHP的类型系统相当奇怪,你可以传入一个实例(这可能是多余的,因为Writer只是调用::empty),或者作为字符串传递monoid类名(记住,这不会关注你的use语句)。

tell :: Monoid m => m b -> Writer a m b

这是使Writer如此有用的技巧。此(静态)函数将返回一个具有null值和给定monoid的Writer。这在chain调用中特别有用,因为它提供了向日志中添加内容的好方法(无需手动构建Writer)。

<?php

use PhpFp\Writer\Writer;

list($x, $log) = Writer::of(2, 'PhpFp\Maybe\Maybe')->chain(
    function ($x)
    {
        return Writer::tell(Maybe::of('BLAH'))->map(
            function ($_) use ($x)
            {
                // We can still access the old value!
                return $x + 2;
            }
        );
    }
)->run();


assert($x == 4);
assert($log->fork(null) == 'BLAH');

construct :: Monoid m => (-> (a, m b)) -> Writer a m b

有时——可能是在链式方法中——你可能需要构建自己的Writers,通常是因为你想要将一些数据添加到日志中。在这种情况下,这个函数就是为你准备的。不幸的是,类型签名稍微有些笨拙:你需要传递一个(没有参数的)函数,该函数返回一个值/幺半群对。

<?php

use PhpFp\Writer\Writer;

list ($x, $log) = new Writer(function () {
    return [2, new Monoid(['Hello!'])];
})->run();

assert($x == 2);
assert($log->value == ['Hello!']);

ap :: Monoid m => Writer (a -> b) m c | Writer a m c -> Writer b m c

这是Writer实例的标准应用,由chain派生而来。这可以像任何适用性函子(尽管在类型安全的情况下必须匹配幺半群类型;如果不匹配,行为是未定义的)的应用一样使用。

<?php
$a = Writer::of(5, new Monoid([]));

list ($x, $log) = Writer::of(
    function ($x) {
        return $x + 2;
    },
    new Monoid([])
)->ap($a)->run();

assert ($x == 7);
assert ($log->value == []);

chain :: Monoid m => Writer a m c | (a -> Writer b m c) -> Writer b m c

链式Writer的过程很简单:创建一个新的操作,其中执行到目前为止的操作以生成$value1$log1。然后,在$value1上调用链式函数以生成$value2$log2。最后,返回的Writer持有[$value2, $log2->concat($log1)]

如果这看起来很复杂,不要担心——你现在可以用tell做所有你需要做的事情,并且可以无忧无虑地使用chain Writer返回的方法!

<?php

use PhpFp\Writer\Writer;

list ($x, $log) = Writer::of(2, 'PhpFp\Maybe\Maybe')
    ->chain(
        function ($x) {
            return Writer::of(
                2 * $x,
                'PhpFp\Maybe\Maybe'
            );
        }
    )
    ->run();

assert($x == 4);
assert($log == PhpFp\Maybe\Maybe::empty());

map :: Monoid m => Writer a m c | (a -> b) -> Writer b m c

Writer像所有单子一样是函子,并且可以使用chain推导出map方法。映射允许你转换内部值,但不能转换日志:那只能添加。更复杂的作用可以通过State单子实现。

<?php

use PhpFp\Writer\Writer;
use PhpFp\Maybe\Maybe;

list ($x, $log) = Writer::of(2, 'PhpFp\Maybe\Maybe')->map(
    function ($x)
    {
        return $x * 2;
    }
)->run();

assert($x == 4);
assert($log == Maybe::empty());

run :: Monoid m => Writer a m b | (a, m b)

在计算结束时,你将想要从Writer实例中获取结果。你将从run中获得一个对:值和幺半群日志。PHP的list构造函数使得这看起来相当整洁。

<?php

use PhpFp\Writer\Writer;
use PhpFp\Maybe\Maybe;

list ($x, $log) = Writer::of(2, 'PhpFp\Maybe\Maybe')->run();

assert($x == 2);
assert(Maybe::empty()->equals($log));

贡献

除非人们想要添加更多的类型类,否则我不期望有很多代码更改,但绝对欢迎提交PR!文档修复和改进始终受到欢迎,以及关于示例中某些清晰度的问题。我希望使这个项目尽可能有帮助!