php-fp / php-fp-writer
PHP中的Writer单子的实现。
Requires (Dev)
- phpunit/phpunit: ^5.3
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!文档修复和改进始终受到欢迎,以及关于示例中某些清晰度的问题。我希望使这个项目尽可能有帮助!