php-fp / php-fp-io
PHP中IO单子的实现。
Requires (Dev)
- phpunit/phpunit: ^5.3
This package is not auto-updated.
Last update: 2023-06-02 12:59:27 UTC
README
这个实现看起来比我想要的更面向对象,但它仍然满足单子法则。
简介
IO单子是函数式编程中的一个概念,由Haskell使之“闻名”。它是一种纯化IO操作的方式:本质上,这个单子提供了一种构建不纯程序描述的方法,而不实际运行它。
通常,在编写函数式程序时,你会有一个主入口点文件(例如 src/Main.php
),它生成一个IO实例。类似于以下这样
<?php use PhpFp\IO\IO; // There are no true first-class functions in PHP. return IO::of('Hello, world!')->map('var_dump');
这个程序不做任何IO,也没有任何副作用 - 它只是构建了这个IO实例。然后,另一个文件(例如 public/index.php
)会看起来像这样
<?php (include '../src/Main.php') ->unsafePerform();
因此,程序可以保持纯(以及完全可单元测试等),而你的真正入口点只需运行主文件描述的程序。
单子的工作方式就像多米诺骨牌连锁:在调用 unsafePerform
之前,这个单子本质上只是构建一个延迟函数的管道。一旦它 已经 被调用,所有的操作都会按照指定的顺序发生。
API
在以下类型签名中,构造函数和静态函数被写成在Haskell等纯语言中看到的样子。其他包含一个管道,管道前面的类型代表当前IO实例的类型,管道后面的类型代表函数。
of :: a -> IO a
这是IO单子的应用构造函数。提供给此函数的值将是运行 unsafePerform
的结果 - 已经为你完成了适当的包装,这与标准构造函数不同。
<?php use PhpFp\IO\IO; assert(IO::of(2)->unsafePerform() == 2);
__construct :: (-> a) -> IO a
类型签名有点奇怪,但可能是我在PHP中能想到的最佳匹配。构造函数的参数必须是一个零参数函数,它返回一个值。这个值是IO实例的“内部值”。参见以下两个示例
<?php use PhpFp\IO\IO; // With the applicative constructor. $x = IO::of(2); // With the OOP constructor. $y = new IO( function () { return 2; } ); // The result of both is the same - the former is neater. assert($x->unsafePerform() == $y->unsafePerform());
构造函数看起来可能没有用,但当产生不纯的IO操作时,它就有用了:参见下面的 chain
示例,其中构造函数用于将 fgets
的执行延迟到不纯操作执行时。
chain :: IO a | (a -> IO b) -> IO b
这是Haskell的 >>= (bind) 操作在此IO实现中的等效操作。有时也称为“平坦映射”,这将把内部值转换为IO类型,然后将两个IO包装器“扁平化”成一个。与 map
类型进行比较:如果我们用链式函数映射IO会得到什么?
<?php use PhpFp\IO\IO; //+ $getLine :: IO String $getLine = new IO( function () { return fgets(STDIN); } ); //+ $putStrLn :: String -> IO () $putStrLn = function ($str) { return new IO( function () use ($str) { echo $str; // Newline picked up by fgets } ); }; // This IO reads a line from STDIN, and prints it to STDOUT. return $getLine->chain($putStrLn); // ->unsafePerform() to run.
map :: IO a | (a -> b) -> IO b
这是通常的函子映射,尽管其实现是从chain
派生出来的。它所做的只是根据某个函数转换monad的内部值。请注意,该函数实际上只有在调用unsafePerform
时才会执行。
<?php use PhpFp\IO\IO; $mapper = function ($x) { return $x + 2; }; assert(IO::of(2)->map($mapper)->unsafePerform() == 4);
ap :: IO (a -> b) | IO a -> IO b
将IO包装的参数应用到这个IO包装的函数上。这个实现也是从chain
派生出来的——这是一个相当重要的函数。
<?php $add = function ($x) { return function ($y) use ($x) { return $x + $y; }; }; $a = IO::of(2); $b = IO::of(4); // Look up the `liftA2` function in a functional library. assert (IO::of($add)->ap($a)->ap($b)->unsafePerform() == 6);
unsafePerform :: IO a | a
此函数将运行IO操作并返回结果。如简介中所述,在正常程序流程中运行此函数不符合函数式编程的精神;理想情况下,这应该在程序末尾运行一次。
贡献
鉴于这个monad在其他语言中的相对稳定性,我不期待大量的代码PR,但如果您看到任何想改变的内容,请随时提交!我认为文档更改的可能性更大,而且也非常欢迎:PhpFp项目的目标不仅是生产一系列功能工具,还为希望了解更多关于函数式编程的程序员提供学习资源。