php-fp/php-fp-maybe

该包已被弃用且不再维护。未建议替代包。

PHP中的Maybe monad实现。

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

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 项目的目标不仅是生成一组功能工具,还要为希望了解更多关于函数式编程的程序员提供学习资源。