php-fp/php-fp-io

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

PHP中IO单子的实现。

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

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