marcosh / lamphpda
函数式编程数据结构集合
Requires
- php: >= 8.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.30
- kahlan/kahlan: ^5.2
- maglnet/composer-require-checker: ^4.7
- vimeo/psalm: ^5.15
This package is auto-updated.
Last update: 2024-08-29 08:30:09 UTC
README
类型安全的函数式数据结构集合
目标
该库的目标是在PHP生态系统内以最类型安全的方式提供一系列函数式数据结构,同时提供通用和一致的API。
主要思想
区别于PHP中其他函数式库的两个主要思想是
- 遵循 http://marcosh.github.io/post/2017/10/27/maybe-in-php-2.html 使用安全的求和类型
- 使用高阶类型来提高重用性和类型安全性(见 http://marcosh.github.io/post/2020/04/15/higher-kinded-types-php-issue.html 和 http://marcosh.github.io/post/2020/04/15/higher-kinded-types-php-solution.html)
安装
composer require marcosh/lamphpda
工具
我们使用 Psalm 作为类型检查器。它基本上作为一个编译步骤,确保所有类型都一致。
要利用这个库,您的代码必须通过Psalm检查。
开发
该库包含一个 flake.nix
文件,使您能够访问一个配备PHP 8.1和Composer的开发环境。要在目录中利用它,只需执行 nix develop
即可。对于使用 direnv
(带有 nix-direnv
)的人来说,还提供了一个预配置的 .envrc
文件,它在进入库目录时会自动加载环境。
决策记录
关于项目的相关决策收集在 adr
文件夹中,遵循 架构决策记录 格式。
内容
该库提供了一些不可变的数据结构,这些结构对于用函数式风格编写应用程序非常有用。
目前实现的数据结构包括
- Maybe,它允许对可能缺失的数据建模;
- Either,它对备选方案进行建模;
- Identity,它只是一个简单的包装器;
- LinkedList,它对可能包含多个值的可能性进行建模;
- Pair,它对同时存在两个事物进行建模;
- Reader,它对依赖于上下文的价值进行建模;
- State,它对可以与全局状态交互的价值进行建模;
- IO,它对惰性值进行建模。
您可以在 docs/data-structures 文件夹 中找到有关每个数据结构的实现和背后的想法的更多详细信息。
如何与数据结构交互
该库被构建得非常抽象和通用,以允许极度的可组合性和可重用性。
您可以使用多种方式与提供的数据结构进行交互。
类型类
您可以将类型类视为可以附加到数据结构上的行为。由于数据结构原则上可以以多种方式实现特定的行为(例如,使用两个整数计算新整数的方法不止一种),我们不能直接使用接口来实现我们的数据结构。因此,类型类实例作为实现接口的独立对象实现,该接口描述了类型类本身。
例如,Semigroup
类型类,它描述了将同类型两个元素组合成相同类型的元素的行为,可以像下面这样实现:
/** * @template A */ interface Semigroup { /** * @param A $a * @param A $b * @return A */ public function append($a, $b); }
现在我们可以为任何我们想要的类型实现 Semigroup
实例,甚至包括原生类型。例如,我们可以为整数之间的加法实现一个半群
/** * @implements Semigruop<int> */ final class IntAddition implements Semigroup { /** * @param int $a * @param int $b * @return int */ public function append($a, $b): int { return $a + $b; } }
然后我们可以使用它来计算两个整数的和
(new IntAddition())->append(1, 2); // returns 3
这个特定的实例并不那么有趣,但您能够编写依赖于通用 Semigroup
的代码这一事实绝对值得注意!
我们目前公开的类型类有
- Functor,允许将一元函数提升到给定上下文中;
- Apply,允许将任何元数的函数提升到给定上下文中;
- Applicative,允许将值提升到上下文中;
- Alternative,模拟组合封装在上下文中的值的能力;
- Monad,允许按顺序执行在上下文中返回值的函数;
- MonadThrow,允许以纯方式管理异常;
- Foldable,允许将数据结构缩减为单个值;
- Traversable,允许在应用上下文中通过函数转换数据结构;
- Semigroup,允许组合相同类型的两个值;
- Monoid,允许创建一个单位元素;
- Bifunctor,模拟依赖于两个协变类型变量的上下文;
- Profunctor,模拟依赖于一个逆变和一个协变类型变量的上下文。
有关每个类型类的更多详细信息,请参阅docs/typeclasses 文件夹。
类型类和数据结构
作为此库的设计原则,我们试图在我们的数据结构上仅公开来自类型类的方法。这意味着提供的结构具有标准通用 API,并使用类型类实例。
例如,Either
有两个 Apply
实例。要选择您想使用的实例,Either
提供了 iapply
方法,该方法将 Either
的 Apply
类型类实例作为第一个参数。
/** * @template A * @template B */ final class Either { /** * @template C * @param Apply<EitherBrand<A>> $apply * @param HK1<EitherBrand<A>, callable(B): C> $f * @return Either<A, C> */ public function iapply(Apply $apply, HK1 $f): self }
我们可以指定一个类型类实例指向特定的数据结构,使用所谓的Brands
,这其实是在类型级别上的标签,它使我们能够模拟高阶类型。
默认类型类实例
通常情况下,一个数据结构只接受类型类的一个实例,或者存在一个在文献中被认为是标准的实例。在这种情况下,持续传递类型类实例的负担是非常不便的;为了减轻痛苦,我们还公开了已经提供了默认类型类实例的方法。
继续前一个示例,Either
还公开了一个名为apply
的方法,其中硬编码了EitherApply
实例。
/** * @template A * @template B */ final class Either { /** * @template C * @param HK1<EitherBrand<A>, callable(B): C> $f * @return Either<A, C> */ public function apply(HK1 $f): self { return $this->iapply(new EitherApply(), $f); } }
贡献
如果您想为该项目做出贡献,请阅读贡献指南。