timfennis/apply

函数式编程库

0.4.0 2021-02-28 13:30 UTC

README

codecov Build Status

Apply 是一个 PHP 库,旨在将来自不同语言和库(如 Haskell、Scala、Kotlin、Arrow 和 Cats)的函数式编程思想引入 PHP。

稳定性

该库目前仍在不断开发中。任何内容都可能随时改变。

贡献

我目前正在寻找想法、建议、批评和贡献。该库处于草稿状态,一些功能可能无法按预期工作,测试覆盖率仍约为 50%。

  • 如果您有任何在 PHP 中可以良好工作的函数式编程概念,请提出问题或 PR 并参与其中。
  • 如果您发现任何功能没有按预期工作或性能可以改进,请随时提出 PR 或问题并通知我!
  • 如果您有任何想法、长期愿景,或者只是认为整个库很愚蠢?请随时提出问题并通知我!

柯里化函数

集合

该库中所有集合函数都试图在命名和参数顺序方面遵循一种模式。通常,您会找到的名称和参数来自 Haskell 的标准库。每个函数都有一个柯里化版本,其参数顺序与 Haskell 中的顺序相同。在这个参数顺序中,函数的主题总是最后一个参数,这样柯里化函数就可以用来创建可以以不同方式连接的部分应用函数。所有这些函数也有命令式的对应函数,在某些情况下更容易使用和阅读。这些函数的参数顺序相同,只是主题现在是第一个参数,后面跟着其他参数。这个顺序是选择,因为它在命令式代码中更容易阅读,并且与大多数其他语言相似。

返回元素集合的函数始终返回生成器,以尽可能实现惰性。返回单个元素或标量值的函数不是惰性的。

所有

$list = [5, 6, 7, 8, 9, 10];

$gt4 = fn($n) => $n > 4;
$gt5 = fn($n) => $n > 5;

all($gt4)($list); // true because all items are greater than 4
all($gt5)($list); // false because not all items are greater than 5

任何

如果任何(或一些)谓词为真,则返回 true。

$list = [5, 6, 7, 8, 9, 10];

$gt7 = fn($n) => $n > 7;
$gt20 = fn($n) => $n > 20;

any($gt7)($list); // true because all items are greater than 7
any($gt20)($list); // false because none of the items are greater than 20

identityconstant

这两个函数在许多情况下非常有用。constant 返回一个始终返回传递给它的值的函数。而 identity 是一个始终返回其参数的函数。

$numbers = [1,2,3];
map(Functions::identity)($numbers); // [1,2,3]
map(constant(4))($numbers); // [4,4,4]

单子

请参阅 此页面 了解关于单子的良好教程。尽管如此,您并不真正需要理解它们才能(滥用)它们。

尝试

Attempt 表示计算的结果,该计算可能成功时有一个结果,或者在出错时有一个异常。如果计算成功,您将得到一个包含结果的 Success<A>,如果计算出错,您将得到一个包含异常的 Failure

Attempt 很像 Either,但在必须消耗某些库或语言特性并且抛出不希望异常的情况下特别有用。Attempt 可以用来捕获异常,并在结果上执行计算,而无需构建复杂和冗长的 try-catch 块。

function loadFromAPI() {
    throw new InvalidAuthenticationCredentialsException('Your authentication credentials are invalid');
}

$tryLoad = Attempt::of(fn() => loadFromAPI());
$result = $tryLoad->getOrDefault(null); // returns null

if ($tryLoad->isFailure()) {
    // true it went wrong!
}

通常您可能希望折叠计算

$tryLoad = Attempt::of(fn() => rollTheDice());

$number = $tryLoad->fold(
    fn(Throwable $t) => 0,
    fn(int $successValue) => $successValue + 1
);

选项(Maybe)

Option 模态是 schmittjoh/php-option 的现代化重写,可以在 这里 找到。它被设计为与接口高度兼容。

欢迎提出建议。

或者

/**
 * @return Either<string, Response>
 */
function loadFromApi(): Either {
    try { 
        return new Right(httpGet("http://example.com"));
    } catch (RequestException $e) {
        return new Left("Request Error");
    } catch (ResponseException $e) {
        return new Left("Response Error");
    }
}

直接使用 string 作为类型可能不是最佳选择,你可能会想定义自己的错误类型,如 RequestErrorResponseError。这些类型可以拥有有意义的属性,有助于错误处理。

EvalM

用于包装一个惰性计算。

模态推导式

许多编程语言都有一种形式的模态推导式,但它们的名称不同,实现略有差异。在 Haskell 中,你有 'do 语法',在 Scala 中你有 for-推导式,在 JavaScript 中你可以使用 async/await 来实现类似的结果。

在 PHP 中,我们没有太多使用异步编程和 IO 模态的需求,但模态推导式的语法仍然可以为我们提供一种优雅地组合多个模态结果的方法。

首先让我们看看一个看起来不太好的例子。这里有两个可能会以某种方式失败的计算。为了组合这些计算的结果,我们必须将它们从原始的 '包裹' 中取出,组合结果,然后再将它们包装起来。你的代码可能看起来像这样。

$computeA = fn() => Option::fromValue(1);
$computeB = fn() => Option::fromValue(5);

$sum = $computeA()->flatMap(static function ($a) use ($computeB) {
    return $computeB()->flatMap(static function ($b) use ($a) {
        return new Some($a + $b);
    });
});

$sum; // Some(6)

这已经很糟糕了,但想象一下你想要组合 3 个甚至 10 个选项的结果,欢迎来到回调地狱。

解决方案

$computeA = fn() => Option::fromValue(1);
$computeB = fn() => Option::fromValue(5);

$sum = Option::binding(static function () use ($computeA, $computeB) {
    $a = yield $computeA();
    $b = yield $computeB();
    return $a + $b;
}); 

$sum; // Some(6)

这看起来更像是你想要编写的代码!它并不完美,因为我们必须将整个内容包裹在一个可调用的对象中,并将其传递给 binding 函数,但它比我们的第一个例子更容易阅读。尤其是在计算量增加时。

许可证

此项目中的大部分源代码都受 MIT 许可证保护。《Apply\Option》包是从受 Apache-2.0 许可证的 schmittjoh/php-option 衍生的。

SPDX-License-Identifier: Apache-2.0 AND MIT