php-fp / php-fp-maybe
PHP中的Maybe monad实现。
Requires (Dev)
- phpunit/phpunit: ^5.7
This package is not auto-updated.
Last update: 2023-06-06 14:18:28 UTC
README
简介
Maybe monad是你在阅读关于monadic functional programming以及更通用的基本错误处理时很可能会遇到的第一种monad。它的目的是模拟早期中止计算。以下是一个示例
<?php function prop($key, $xs) { return $xs[$key]; } $xs = [ 'a' => [ 'b' => ['c' => 3] ] ]; prop('c', prop('b', prop('a', $xs))); // 3
在这段代码中,我们可以看到我们的prop
方法从一个数组中提取一个给定的键,并且我们可以嵌套这些prop
调用以提取更深层级的值。当数据不符合我们期望的形状时,这个模型中的缺陷就会显现出来。在这种情况下,你会在未定义的数组索引上遇到问题。这正是我们的Maybe monad发挥作用的地方
<?php use PhpFp\Maybe\Maybe; function prop($key) { return function ($xs) { return isset($xs[$key]) ? Maybe::of($xs[$key]) : Maybe::nothing(); ); }; } $xs = [ 'a' => [ 'b' => ['c' => 3] ] ]; prop('a')($xs) ->chain(prop('b')) ->chain(prop('c')); // Just 3 prop('a')($xs) ->chain(prop('b')) ->chain(prop('d')); // Nothing prop('immediate_mistake')($xs) ->chain(prop('b')) ->chain(prop('d')); // Nothing
我们可以看到,当发生'错误'时,我们得到了一个Nothing
值,它可以像我们的Just
值一样使用。关键的区别在于Nothing
忽略了链式函数并再次返回Nothing
。这意味着我们可以写出完整的嵌套计算,而无需在每个键处检查下一个键是否存在。当我们确实找到一个不存在的键时,所有后续的计算都将被跳过(因此称为'早期中止')。
其优点应该是显而易见的:我通常给出的第一个例子是一个从数据库中返回单行数据(如果查询有任何结果)的函数:当成功时,你的函数可以返回一个包含行的Just构造函数,否则返回Nothing。
API
以下类型签名中,构造函数和静态函数的写法类似于纯语言(如Haskell)中的写法。其他包含一个管道,管道前面的类型表示当前Maybe实例的类型,管道后面的类型表示函数。
of :: a -> Maybe a
这是Maybe monad的应用构造函数。它将值以Just实例的形式返回,无论其是什么。
<?php use PhpFp\Maybe\Maybe; assert(Maybe::of('test')->fork('default') == 'test');
empty :: Maybe a
返回一个Nothing实例,这在需要初始累加器时(例如使用reduce进行连接)很有用。它也是Monoid
定义(与concat一起)所必需的。
<?php use PhpFp\Maybe\Maybe; $value = Maybe::empty() ->concat(Maybe::of('blah')) ->fork(null); assert($value == 'blah');
just :: a -> Just a
Just
实例的标准构造函数。通常你应该调用Maybe::of
。
nothing :: a -> Nothing a
Nothing
实例的标准构造函数。通常你应该调用Maybe::nothing
。
ap :: Maybe (a -> b) | Maybe a -> Maybe b
将 Maybe 包装的参数应用于 Maybe 包装的函数。Nothing 值作为恒等函数,如下所示
<?php use PhpFp\Maybe\Maybe; $add = function ($x) { return function ($y) { return $x + $y; }; }; $value = Maybe::of($add) ->ap(Maybe::of(2)) ->ap(Maybe::of(8)); assert($value->fork(10000) == 10); assert(Maybe::empty()->ap(Maybe::of(2))->fork(10000) == 2);
concat :: Maybe a | Maybe a -> Maybe a
Maybe 值的 Semigroup 连接。此函数应用于将两个 Maybe 值合并为一个。
- 对于两个 Just 实例,两个值将被连接并以 Just 构造函数包装。
- 对于一个 Just 和一个 Nothing,返回 Just 值。
- 对于两个 Nothing 值,返回 Nothing。
<?php use PhpFp\Maybe\Maybe; $maybes = [Maybe::empty(), Maybe::of(2), Maybe::empty()]; $acc = array_reduce( $maybes, function ($acc, $x) { return $acc->concat($x); }, Maybe::empty() ); assert($acc->fork(5) == 2);
chain :: Maybe a | (a -> Maybe b) -> Maybe b
这是 Haskell 的 >>=
(绑定) 操作在这个 Maybe 实现中的等价操作。它在这个 Maybe 的内部值上运行一个返回 Maybe 的函数,并返回结果。这是介绍中的示例背后的重要机制。
<?php use PhpFp\Maybe\Maybe; function safeHalf($x) { return $x % 2 == 0 ? Maybe::of($x / 2) : Maybe::empty(); } assert(safeHalf(16)->chain(safeHalf)->fork(null) == 4);
equals :: Maybe a | Maybe a -> Bool
Maybe 的 Setoid 等价。首先,检查两个构造函数是否相等。如果它们都是 Nothing
,则它们相等。如果它们都是 Just
,则比较内部值。
<?php use PhpFp\Maybe\Maybe; class Value { public function __construct($x) { $this->value = $x; } public function equals($that) { return $this->value === $that->value; } } $a = Maybe::empty(); $b = Maybe::of(new Value(2)); $c = Maybe::of(new Value(3)); $d = Maybe::of(new Value(3)); assert($a->equals($a) == true); assert($a->equals($b) == false); assert($b->equals($c) == false); assert($c->equals($d) == true);
map :: Maybe a | (a -> b) -> Maybe b
标准函子 map:转换 Maybe 内部的值,并返回一个更新的 Maybe。
<?php use PhpFp\Maybe\Maybe; $add2 = function ($x) { return $x + 2; }; assert(Maybe::empty()->map($add2)->fork(-1) == -1); assert(Maybe::of(2)->map($add2)->fork(-1) == 4);
reduce :: Maybe a | (b -> a -> b) -> Maybe a -> b
Maybe 是一个可折叠类型。对于 Nothing 值,reduce
返回提供的任何初始累加器值。对于 Just
类型,结果是使用累加器和包装的值评估的归约函数。
<?php use PhpFp\Maybe\Maybe; $append = function ($xs) { return function ($x) use ($xs) { return array_merge($xs, [$x]); }; }; assert(Maybe::empty()->reduce($append, []) == []); assert(Maybe::of(2)->reduce($append, []) == [2]);
fork :: Maybe a | a -> a
从 Maybe 中提取值。在您的失败计算结束时,您通常希望从 Maybe 中提取值。为了类型安全,此函数需要一个参数来替换 Nothing
值(当然,您可以直接返回 null
)
<?php use PhpFp\Maybe\Maybe; assert(Maybe::of(2)->fork(4) == 2); assert(Maybe::empty()->fork(4) == 4);
贡献
这个 monad 的实现比 IO 更多样化,因为它可以应用于许多(常用)类型类,因此请随时为您的最爱添加实现。除此之外,底层机制可能很少发生变化。
我认为文档更改的可能性更大,并且也欢迎这些更改:PhpFp 项目的目标不仅是生成一组功能工具,还要为希望了解更多关于函数式编程的程序员提供学习资源。