[英]Is it ok to use Tagless Final (Object Algebras) on coalgebras?
Haskell 和 Scala 社区最近非常迷恋他们所谓的无标签最终编程“模式”。 这些被称为对初始自由代数的对偶,所以我想知道 Tagless Final 的最终结果是什么。 在 ncatlab 上只能找到关于最终代数的讨论,而不是最终代数。
在 CS-Theory Stack Exchange 上提问What Category are Tagless Final Algebras Final我得到了一个非常好的答案,指向这篇博文Final Algebra Semantics is Observational Equivalence 。 所以这些确实是最终代数,但与初始代数不在同一类别中......
然而,当我们查看final tagless是如何使用的时,我们发现它经常被用于看起来像 colgebras 的东西。 例如,请参阅UserRepository
中使用无标签决赛管理效果的错误希望的第 1 部分中的Console
或用户存储库的两个示例。
因此,与其使用在范畴论中用内函子F
表示为F(X) ⟹ X
形式的映射的代数,看起来许多人使用带有映射X ⟹ F(X)
的 Coalgebras 的final tagless
无标签,并表示过程。 这些真的是一回事吗? 还是这里发生了其他事情?
让我们从 Olivier Blanvillain 的Scala 对 Haskell 课程作业示例的翻译中给出的最终无标签的解释开始。 有人注意到,这从代数数据类型开始,它确实是代数结构的原型:组。
在类别中,可以从多项式函子F[X] = X×X + X + 1
构建一个组,该函数将任何类型转换为该类型对或该类型或 1 的类型。然后选择一个特定的X 的类型,比如 A 代数是 function F[A] ⟹ A
。 最广为人知的群是正负自然数的集合,0 表示ℤ,所以我们的代数是:
ℤ×ℤ + ℤ + 1 ⟹ ℤ
代数可以分解为 3 function +: ℤ×ℤ ⟹ ℤ
, -: ℤ ⟹ ℤ
和常数zero: 1 ⟹ ℤ
。 如果我们改变 X 类型,我们会得到不同的代数,这些代数形成一个范畴,它们之间有态射,其中最重要的是初始代数。
初始代数是自由代数,它允许构建所有结构而不会丢失任何信息。 在Scala 3中,我们可以为这样的组构建初始代数:
enum IExp {
case Lit(i: Int)
case Neg(e: IExp)
case Add(r: IExp, l: IExp)
}
我们可以使用它构建一个简单的结构:
import IExp._
val fe: IExp = Add(Lit(8), Neg(Add(Lit(1), Lit(2))))
然后可以通过创建函数IExp => Int
或IExp => String
来解释fe
结构,它们是代数类别中的态射,通过解构 ADT 并将它们递归地映射到具有特定 X (例如String
或Int
)。 这种态射被称为折叠。 (参见Richard Bird 和 Oege de Moor 于 1997 年出版的《编程代数》一书)
在无标签决赛中,这被转化为特征
trait Exp[T] {
def lit(i: Int): T
def neg(t: T): T
def add(l: T, r: T): T
}
一组三个函数都返回相同的类型。 表达式为 function 应用程序
def tf0[T] given (e: Exp[T]): T =
import e._
add(lit(8), neg(add(lit(1), lit(2))))
这些可以用给定的实例来解释
given as Exp[Int] {
def lit(i: Int): Int = i
def neg(t: Int): Int = -t
def add(l: Int, r: Int): Int = l + r
}
tf0[Int] // 5
本质上,解释是在上下文中given
的接口Exp
的实现(或 Scala 2 implicit
)。
所以在这里,我们正在从最初的 ADT 表示的代数结构转移到最终的无标签版本。 (参见关于ctheory的讨论)。
现在,如果我们从UserRepository
中使用无标签最终管理效果的错误希望中获取 UserRepository 示例,我们有
trait UserRepository {
def getUserById(id: UserID): User
def getUserProfile(user: User): UserProfile
def updateUserProfile(user: User, profile: UserProfile): Unit
}
这显然是(对于任何阅读过 Bart Jacobs 的一些从Objects and Classes Coalgebraically开始的作品的人)在 UserRepository 的UserRepository
S 上的一个代数。 代数是代数的对偶:箭头是相反的。 从函子 F 和特定类型 S 开始,余代数是 function S ⟹ F[S]
在用户存储库的情况下,我们可以看到这是
S ⟹ (Uid → User) × (User → Profile) × (User × Profile → S)
这里函子F(X)
将任何类型X
带入一个 3 元组的函数。 余代数就是这样一个函子 F、一组状态 S 和一个跃迁态射S ⟹ F(S)
。 我们有 2 种观察方法getUserById
和getUserProfile
以及一种 state 改变一种updateUserProfile
也称为 setter。 通过改变状态的类型,我们改变了余代数。 如果我们查看这样一个函子 F 上的所有余代数,以及它们之间的态射,我们会得到一个余代数的范畴。 其中最重要的是最后一个,它给出了该函子的余代数上所有观测的结构。
有关代数及其与 OO 的关系的更多信息,请参阅 Bart Jacobs 的工作,例如他的Tutorial on (co)Algebras and (co)Induction或此 Twitter thread 。
本质上,我们有一个进程,例如 UserRepository 或控制台,它们具有 state 并且可以更改 state,而考虑更改 state 是没有意义的。
现在确实,在 UserRepository 的 Tagless Final 示例中,它由 Functor F[_]
泛化,如下所示:
trait UserRepository[F[_]] {
def getUserById(id: UserID): F[User]
def getUserProfile(user: User): F[UserProfile]
def updateUserProfile(user: User, profile: UserProfile): F[Unit]
}
这足以将 UserRepository 更改为代数吗? 它确实在某种程度上看起来函数都具有相同的 F[_] 类型范围,但这真的有效吗? 如果 F 是恒等函子怎么办? 然后我们有前面的案例。
换一种方式,从 Final Tagless 到 ADT,人们应该问为UserRepository
拥有 ADT 会是什么? (我自己在 model 命令中写了类似的东西来更改远程 RDF 数据库,发现这真的很有帮助,但我不确定这在数学上是否正确。)
Tagless Final 声称的两个优点是
以下看起来可以提供线索:
最近的文章Codata in Action声称 codata(一个代数概念)是函数式编程和 OO 编程之间的桥梁,并且实际上在数据和 codata 之间使用了描述的访问者模式(也在Extensibility for the Masses中使用)到 map。 见插图。 codata 的声明是程序抽象的语言不可知表示(上面称为模块化),可测试性来自 Jacobs 用余代数类别描述的接口的多种实现。
两种代数之间的区别是有效代数和无效代数之间的区别。 实际上,也可以像这样在 Dotty (Scala3) 中使用 GADT 编写 UserRepo:
enum UserRepo[A]{
case GetUserById(id: UserID) extends UserRepo[User]
case GetUserProfile(user: User) extends UserRepo[UserProfile]
case UpdateUserProfile(user: User, profile: UserProfile) extends UserRepo[Unit]
}
如果我们抛开final tagless 如何与代数相关的问题并接受它们与 GADT 同构,那么我们可以用代数来重新表述这个问题。 看起来 Andrej Bauer 在 2019 年 3 月的讲义中详细回答了这个问题。 What is Algebraic about Effects and Handlers 。
Andrej Bauer 清楚地解释了代数是什么,从签名开始,然后继续解释什么是理论的解释和模型。 然后他继续在“§2 计算效应作为代数运算”中通过代数参数化进行 model 有效计算。 完成后,我们得到的代数与我想知道的代数非常相似。
在“§4 关于代数效应和处理程序的代数是什么?” 他展示了这种参数化代数的对偶如何为我们提供了对很明显是余代数的共同解释、共同模型和合作。
有人告诉我,这些处理效果的方式与使用 monad 不同,我需要弄清楚区别是什么,以及这是否会影响问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.