php-fp/php-fp-state

此包已被废弃,不再维护。没有建议的替代包。

PHP中State monad的实现。

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

This package is not auto-updated.

Last update: 2023-06-05 06:50:35 UTC


README

简介

纯函数编程没有状态。毕竟,状态是杂质,而纯洁性是我们所追求的。好吧,让我们暂时考察一下状态。

在给定状态下执行的指令导致状态更新。如果我们正在编写解释器,我们可能会将执行函数的类型签名写为 State -> Function -> State。旧状态和指令组合起来产生新状态。

现在,如果我们认为程序是一系列指令,期望的输出是状态,我们得到 State -> [Instruction] -> State。我们有一个初始状态,我们依次将这些指令与状态结合,最终给出最终状态。

exec :: (State -> Function -> State) -> State -> [Instruction] -> State

这看起来熟悉吗?

reduce :: (b -> a -> b) -> b -> [a] -> b

嘿,我们确实可以用 reduce 实现状态的概念,从而得到一个功能强大的纯函数流。但这并不意味着我们应该这样写所有程序:还有很多地方根本不需要状态,我们应该尽量避免它们。

我们真正想要做的是为特定的计算保留状态,而恰好 monads 对于此目的非常完美!每个链式函数应限制为一次状态交互,这迫使你明确你的过程。

API

以下类型签名中,构造函数和静态函数被写成纯语言(如 Haskell)中常见的格式。其他包含一个管道,管道前面的类型表示当前 State 实例的类型,管道后面的类型表示函数。

of :: a -> State a b

State monad 有一个应用函子来包装 State monad 中的值。函数 evalState 是其补数

<?php

use PhpFp\State\State;

assert(State::of('boo')->evalState(null) == 'boo');

get :: -> State a b

与 Reader monad 的 ask 方法类似,get 提供了对应用程序状态的只读访问。在链式方法中,可以映射 get State 来访问当前应用程序状态。

<?php

use PhpFp\State\State;

$state = State::of(2)->chain(
    function ($x)
    {
        return State::get()->map(
            function ($y) use ($x)
            {
                return $x + $y;
            }
        );
    }
);

assert($state->evalState(6) == 8);

modify :: State a b | (b -> c) -> State a c

与 Reader monad 不同,计算状态可以被更新。有两种方法可以实现这一点,其中第一种是 modify 方法。此方法接受一个映射状态的函数,并返回一个 State monad。请记住,这需要映射以将值重新插入新 State 对象。

<?php

use PhpFp\State\State;

$state = State::of(2)->chain(
    function ($x)
    {
        $add = function ($x)
        {
            return $x + 1;
        };

        return State::modify($add)->map(
            function ($_) use ($x)
            {
                return $x;
            }
        );
    }
);

assert($state->run(-2) == [2, -1]);

put :: 状态 a b | c -> 状态 a c

put 方法用于完全替换状态,而不是转换状态。给定一个值,将返回一个状态 monad,其中状态已被替换。与 modify 一样,需要再次添加当前的 State 值。

<?php

use PhpFp\State\State;

$state = State::of('hello')->chain(
    function ($x)
    {
        return State::put(5)->map(
            function ($_) use ($x)
            {
                return $x;
            }
        );
    }
);

assert($state->run('hello') == ['hello', 5]);

__construct :: (a -> (a, b)) -> 状态 a b

常规构造函数接受一个返回 [值,状态] 对的单一函数。与其他类似,这可能是你不太常用的构造函数。

<?php

use PhpFp\State\State;

$state = new State(
    function ($s)
    {
        return [2, $x];
    }
);

assert($state->evalState(null) == 2);
assert($state->evalState(5) == 5);

chain :: 状态 a b | (a -> 状态 c b) -> 状态 c b

chain 方法几乎总是与上述三个函数之一一起使用,因为这些都是状态的基本操作。然而,有时你可能想做一些更复杂的事情

<?php

use PhpFp\State\State;

$state = State::of(3)->chain(
    function ($x)
    {
        // Why? Who knows?
        return State::of($x + 1);
    }
);

assert($state->evalState(null) == 4);

map :: 状态 a b | (a -> c) -> 状态 c b

map 函数是一个人们期望的函子映射 - 内部值被转换,状态不受影响。这里没有特别之处

<?php

use PhpFp\State\State;

$inc = function ($x)
{
    return $x + 1;
}

assert(State::of(2)->map($inc)->evalState(null) == 3);

ap :: 状态 (a -> c) b | 状态 a b -> 状态 c b

此函数可用于将包装参数应用于此 monad 的包装函数。此实现,就像 map 的实现一样,可以从 chain 方法中推导出来

<?php

use PhpFp\State\State;

$inc = function ($x)
{
    return $x + 1;
}

assert(State::of($inc)->ap(State::of(2))->evalState(null) == 3);

evalState :: 状态 a b | b -> a

此方法接收初始状态,运行计算,并返回结果值(丢弃最终状态)

<?php

use PhpFp\State\State;

assert(State::of(2)->evalState(3) == 2);

exec :: 状态 a b | b -> b

此方法接收初始状态,运行计算,并返回最终状态(丢弃最终值)

<?php

use PhpFp\State\State;

assert(State::of(2)->exec(3) == 3);

run :: 状态 a b | b -> (a, b)

此方法接收初始状态,运行计算,并返回最终值和最终状态的配对

<?php

use PhpFp\State\State;

assert(State::of(2)->run(3) == [2, 3]);

贡献

我怀疑按照文档标准,无法判断这些 monad 的编写顺序,这不应该如此。如果有任何不清楚的地方, 提交一个问题或拉取请求 - 澄清总是受欢迎 :)

至于代码更改,通常适用:我预见到的唯一变化将是实现更多特定类型类的变化,这些变化是美妙且受鼓励的!