php-fp / php-fp-state
PHP中State monad的实现。
Requires (Dev)
- phpunit/phpunit: ^5.3
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 的编写顺序,这不应该如此。如果有任何不清楚的地方,请 提交一个问题或拉取请求 - 澄清总是受欢迎 :)
至于代码更改,通常适用:我预见到的唯一变化将是实现更多特定类型类的变化,这些变化是美妙且受鼓励的!