首页 >后端开发 >php教程 >PHP:单子

PHP:单子

DDD
DDD原创
2024-11-02 21:09:30818浏览

这篇文章的灵感很大程度上来自 Gina Banyard 在“FORUM PHP 2024”上的技术演讲:

用 3 个简单的同义词揭开 Monad 的神秘面纱

让我们从一些同义词开始:

  • 容器
  • 包装
  • 设计模式

如果你用谷歌搜索“PHP monads”,其他概念会很快出现,比如函数式编程、绑定、堆栈,甚至深奥的数学(例如函子、幺半群)。

别害怕。

从本质上讲,Monad 是一种可以通过多种方式实现的模式。

设计模式

当您要运行一些操作时,您可以像往常一样简单地定义自定义对象和助手。

那么,为什么要费心考虑替代概念呢?

恕我直言,这仍然是一个好问题,因为您需要保持效率,但经典方法存在常见的局限性:

  • 操作顺序很重要
  • 湿代码
  • 例外

Monad 可以更一致地处理可选(或尚不可用)值。

Monad 与经典错误处理/异常

现代项目包含静态分析工具,但 PHP 异常没有类型化。

也就是说,工具无法检测函数签名中的异常,因此无法判断代码是否正确处理异常。

为了测试这一点,开发团队通常会编写功能测试,但通过静态分析进行早期检测会更可靠。

来源:“Les Exception:le trou dans la raquette du typepage”(fr)

使用 Monads,您在所有情况下都会获得一个类型化对象,例如自定义枚举情况(例如 FileErrors::AccessDenied),因此错误会在系统中键入。

实现 Logger Monad

构建强大的日志系统可能具有挑战性。复制字符串和调用很容易。

您可能会定义一个名为 log() 的自定义帮助程序,并在项目中的任何地方使用它,而不是对所有内容进行硬编码。

这旨在保持代码干燥,但可能不允许在特定情况下组合更复杂的函数。

函数式方法不包括使用这样的全局帮助器。相反,它宁愿实现一个 Monad 来包装其他函数:

final class LoggerMonad {
    public function __construct(
        public mixed $data,
        public array $logs = [],
    ) {}

    public function bind(callable $fn) {
         $resultLoggerMonad = $fn($this->data);
         return new LoggerMonad(
             $resultLoggerMonad->data,
             [...$this->logs, ...$resultLoggerMonad->logs],
         );
   }
}

function loggify(callable $fn): Closure {
    return function ($value) use ($fn) {
        $name = (new ReflectionFunction($fn))->name;
        $log = [
            'Running '. $name .'('. var_export($value, true) .')'
        ];
        return new LoggerMonad($fn($value), $log);
    };
}

然后,您可以像这样使用 loggify 包装器:

function add2(int $v): int {
    return $v + 2;
}

function square(int $v): int {
    return $v * $v;
}

function multi3(int $v): int {
    return $v * 3;
}

function logIt($value, callable ...$fns) {
    $logging_fns = array_map(loggify(...), $fns);
    $monad = new LoggerMonad($value);
    foreach ($logging_fns as $fn) {
        $monad = $monad->bind($fn);
    }
    return $monad;
}

print_r(logIt(
   3,
   add2(...),
   square(...),
   multi3(...)
));

来源:“Monades simplement”,作者:Gina Banyard (fr)

什么是绑定?

??宝贝别伤害我

Monad 旨在包装值,可以是任何类型,包括对象和函数。

与任何其他包装系统一样,您会发现一个将此值作为输入的构造函数(〜类)以及一些根据您尝试实现的模式有其自己用途的方法。

但是,所有 Monad 都包含绑定函数。顾名思义,这是传递值(或回调)的地方。

无论这些回调中发生什么,monad 都会包装它,这似乎是装饰值和重构代码的强大方法。

代码可读性更好吗?

这显然取决于实现,一开始很容易迷失。

但是,这种替代方法可以显着减少 if 块的数量,并使返回值更加一致:

final class LoggerMonad {
    public function __construct(
        public mixed $data,
        public array $logs = [],
    ) {}

    public function bind(callable $fn) {
         $resultLoggerMonad = $fn($this->data);
         return new LoggerMonad(
             $resultLoggerMonad->data,
             [...$this->logs, ...$resultLoggerMonad->logs],
         );
   }
}

function loggify(callable $fn): Closure {
    return function ($value) use ($fn) {
        $name = (new ReflectionFunction($fn))->name;
        $log = [
            'Running '. $name .'('. var_export($value, true) .')'
        ];
        return new LoggerMonad($fn($value), $log);
    };
}

来源:fp4php - monad

优点和缺点

优点

  • ✅ 有不同用途的各种 monad : Maybe、Either、Logger、List、Reader 等
  • ✅ monad 允许使用更好的类型包装代码,这可能会改进静态分析
  • ✅ PHP 不为此提供内置构造,​​因此实现完全取决于开发人员

缺点

  • ❌ PHP 不为此提供内置结构(例如泛型),因此实现完全取决于开发人员
  • ❌它并没有简化代码

更进一步

  • 使用 monad、monad 转换器和 Cats MTL 进行功能性错误处理
  • PHP 中的 Monad 和用法
  • PHP 函数式编程

包起来

希望您现在对 PHP monad 有更多了解。

当然,您不应该仅仅为了项目而添加花哨的设计模式。

此外,虽然这是一个全新的范例,但很容易错过要点并专注于非常具体的方面,例如错误处理。

然而,发现新方法仍然令人耳目一新。我们需要跳出框框思考。

以上是PHP:单子的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn