作为双自回归闭范畴的电路

我的以前的 很少的 帖子关于笛卡尔闭合范畴(CCC)。从Haskell到硬件,通过笛卡尔闭合类别,我给出了一个简短的动机:类型lambda表达式和CCC词汇表同样具有表达能力,但具有不同的优势:

  • 在Haskell中,CCC词汇表是可重载的,因此可以比lambda和application更灵活地解释。
  • Lambda表达式对人类程序员来说更容易写和读。

通过自动将lambda表达式转换为CCC形式(如过载λ),我希望能在这两种选择中取得最佳效果。

我特别喜欢的一种解释是电路,正如这篇文章中所描述的那样,这也是激发了这一系列帖子的灵感。

编辑:

  • 2013年9月17日:“为所有类别的产品定义”⇒“为所有产品类别定义”。感谢汤姆·埃利斯。
  • 2013–09–17:阐明了上面的第一个CCC/lambda对比:“在Haskell中,CCC词汇表是可重载的,因此可以比lambda和应用程序更灵活地解释。”感谢Darryl McAdams。

CCC公司

首先是关于CCC的提醒,摘自从Haskell到硬件,通过笛卡尔闭合类别:

你可能听说过“笛卡尔封闭范畴”(CCC)。CCC是一个抽象概念,包含少量词汇和相关法律:

  • “类别”部分意味着我们有一个“形态”(或“箭头”)的概念,每个都有一个域和余域“对象”。与结合复合算子存在同一态射。如果对形态和对象的描述听起来像函数和类型(或集合),那是因为函数和类型是一个示例身份证件(∘).
  • “笛卡尔”部分意味着我们有乘积,带有投影函数和一个操作符,可以将两个函数组合成一个对生成函数。对于Haskell函数,这些操作是有限状态试验,信噪比(△).(后者称为“(&&&)“在中控制。箭头.)
  • “闭合”部分意味着我们有一种方法将形态表示为对象,称为“指数”。相应的操作是咖喱,未修剪的、和应用由于Haskell是一种高阶语言,这些指数对象只是(一类)函数。

如中所述过载λ,我还需要副产品(对应于Haskell中的sum类型),将CCC扩展为双承压的相近类别或“biCCC”。

通常,我会用一小部分类型类(例如,在Edward Kmett的categories包). 由于相关约束的技术问题(将在未来的帖子中探讨),我到目前为止还无法找到令人满意的这样的公式。相反,我将转换帖子中给出的biCCC术语表示过载λ.

电路

我们如何看待电路?一个听起来简单的想法是,电路是组件(逻辑门、加法器、触发器等)的有向图,其中的图边表示导线。每个组件都有一些输入引脚和一些输出引脚,每条导线将某个组件的输出引脚连接到另一个组件的输入引脚。

仔细检查后,出现了一些问题:

  • 如何识别预期的输入和输出?
  • 如何确保图形完全连接,而不是预期的外部输入和输出?
  • 如何确保输入引脚最多由一个输出引脚驱动,同时允许输出引脚驱动任意数量的输入引脚?
  • 如何按顺序组合图形,匹配并使用免费的输出和输入?

注意,在编程语言的设计中也出现了类似的问题。在函数式语言(甚至是半函数式语言,如Fortran、ML和哈斯克尔+IO),我们通过嵌套函数应用程序来回答连接/组合问题。整体输入在语法上被识别为函数参数,而输出对应于函数的主体。

我们可以将此技术应用于以下电路的构造。与其直接构建图形片段,然后添加边/线来连接这些片段,不如构建消费输出管脚,构建一个图形片段,并指示该片段的输出管脚。因此,电路(生成器)是一个函数,它接受一些输出引脚作为参数,实例化一组组件,并生成一些输出引脚。传入输出引脚来自其他组件实例化,是电路的顶级外部输入。消耗和产生的管脚的数量和排列不同,因此将显示为类型参数。由于不同的管脚是根据需要生成的,因此电路也将消耗给定管脚供应的一部分,并将剩余的管脚传递给后续组件构造。

类型 电路Gb条= 引脚电源 (引脚电源, [Comp公司],b)--第一次尝试

请注意,没有输入类型,因为它可以显示为函数类型的一部分:a→电路G b这种因子分解在一元配方中是典型的。

电路单子

当然,我们以前在writer和state monad中见过这种模式。此外,作者希望附加组件列表,因此为了提高效率,我们将替换【a】具有附加友好的数据类型,即表示为手指树的序列(顺序数据。顺序).

类型 电路M = WriterT(写入器T)(顺序 Comp公司) ( 引脚电源)

一个非常简单的操作是生成单个引脚(不生成任何组件)。

新Pin 电路M 引脚新Pin= {(第页:ps')获取;放置ps’;返回p}

事实上,这个定义有一个更通用的类型,因为它没有使用WriterT(写入器T)的方面电路M. The得到操作来自莫纳德州在中mtl公司包,所以我们可以使用任何莫纳德州实例,具有引脚电源作为国家。为方便起见,请定义约束缩写:

类型 MonadPins品牌 = 莫纳德州 引脚电源

并使用更通用的类型:

新Pin MonadPins品牌引脚

稍后我们需要这个额外的通用性。

我知道我答应过你一个类别,而不是一个单子。我们会到达那里。

每个管脚代表一个信道,用于传输一位信息,但随时间变化,即信号。在硬件实现电路并运行之前,这些导线上传输的值将不可用。在构建图形/电路时,我们只需要一种区分管脚并生成新管脚的方法。考虑到这些简单的要求,我们将pin简单地表示为整数,但是新型-包装以确保类型安全:

新类型 引脚 = 引脚 国际 衍生(等式,订单,显示,枚举)类型 引脚电源 =[引脚]

每个电路组件都是底层基元的一个实例,并具有三个特性:

  • 底层的“原语”,它决定了功能和接口(信息输入和输出的类型),
  • 携带信息到实例中的管脚(来自其他组件的输出),以及
  • 从实例中携带信息的pin。

组件可以有不同的接口类型,但我们必须将它们全部组合到一个集合中,因此我们将使用存在类型:

数据 Comp公司 = a b类. 源代码2a b类 Comp公司(Prim公司a b)a b衍生 实例 显示 Comp公司

目前,原语只需通过名称进行标识:

新类型 Prim公司a b类= Prim公司 字符串

实例 显示(Prim公司a b)哪里 显示(Prim公司字符串)=字符串

这个源代码2constraint是IsSource(IsSource)域和范围类型的约束:

类型 源代码2a b类=(IsSource(IsSource)a、,IsSource(IsSource)b)

源将是管脚的结构。我们需要将它们扁平化为序列,为新实例的输出生成它们,并根据类型查询管脚的数量(即,不进行评估):

 显示 IsSource(IsSource)哪里至Pins 顺序 引脚genSource(发电机源) MonadPins品牌百万numPins数 国际

的实例IsSource(IsSource)定义起来很简单。例如,

实例 IsSource(IsSource)()哪里至引脚()=genSource(发电机源)= 返回()numPins数_= 0

实例 IsSource(IsSource) 引脚 哪里至引脚p=单例p发电机电源=新PinnumPins数_= 1

实例 源代码2a b类 IsSource(IsSource)(a×b)哪里toPins(sa,sb)=toPins sa to Pins某人genSource(发电机源)=提升M2(,)genSource genSourcenumPins数~(a、b)=numPins锁定+numPins编号b

请注意,我们注意不要将论点评估为numPins数,将是在实践中。

A电路类别

我答应给你一个电路类,但给了你一个单子。有一个标准的结构可以将单子转换为类别,即克莱斯利控制。类别,所以您可能认为我们可以简单地定义

类型a⇴b类= 克莱斯利 电路M  --第一次尝试

我不喜欢这个定义的地方是它需要像这样的参数类型引脚引脚×引脚,它公开了实现的各个方面。我想用布尔布尔×布尔相反,要反映信息的概念类型流经电路。此外,我想生成在底层类别上参数化的计算(实际上,从Haskell源自动生成这些类别的通用计算)。明确提及表示概念,如引脚会阻碍这种通用性,限制在电路上。

要获取类型参数,如布尔布尔×布尔,我们必须将值类型转换为管脚类型。类型族为我们提供了这种能力:

类型家庭引脚

现在我们可以说电路可以通过布尔单个管脚的值:

类型 实例 引脚 布尔 = 引脚

我们可以在没有插脚的情况下通过该单元:

类型 实例 引脚()=()

用于a×b包括销和针用于b条:

类型 实例 引脚(a×b)= 引脚引脚b条

总和类型更为复杂。我们一会儿就到了。

现在,我们可以定义改进的电路类别:

新类型a⇴b类= C类(克莱斯利 电路M(引脚a)(引脚b) )
总和类型

如上所述引脚类型族分布在()和配对。对于每个固定形状类型,也就是所有值都具有相同表示形状的每个类型,包括n个-元组、长度型向量和深度型完美叶树。

元素形状可变的类型的典型示例是sum,在Haskell中表示为要么例如,代数数据类型任一Bool(Bool,Bool),我将改为布尔+布尔×布尔.可以引脚分配+也就是说,我们可以定义

类型 实例 引脚(a)+b)= 引脚+ 引脚b条--??

我们不能使用这个定义,因为它意味着我们必须选择一个形状静态地即,在构建电路时。然而,数据可能会改变形状动态地,所以没有一个静态选择就足够了。

我会给出一个解决方案,看起来效果不错。然而,它缺乏我一直追求的优雅和必然性,所以如果你有其他想法,请在这篇文章的评论中留下建议。

我们的想法是,我们将为两个表示中较大的一个使用足够的管脚。自从那两个引脚表示(别针a销b)可以任意不同,将它们展平成一个共同的形状,即序列。要区分这两个和,请添加一个额外的位/引脚:

数据:++b条= 向上{sumPin 顺序 引脚,sumFlag(摘要标志) 引脚}类型 实例 引脚(a)+b)= 引脚:++ 引脚b条

现在我们要定义一个IsSource(IsSource)实例。回忆一下类定义:

 显示 IsSource(IsSource)哪里至Pins 顺序 引脚genSource(发电机源) MonadPins品牌百万numPins数 国际

很容易生成一系列管脚:

实例 源代码2a b类 IsSource公司(a):++b)哪里至Pins(向上磅/平方英尺)=ps?单粒子f

中的管脚数答:++b是中的最大引脚数b条,加上一个用于标志位:

numPins数_=(numPins(☑a)`最大值`numPins(☑b) )+ 1

要生成a:++b,生成这么多管脚,使用一个管脚sumFlag(汇总标志)剩下的是sumPins(sumPin):

genSource(发电机源)=提升M2向上(序列复制M(a):++b) )- 1)newPin)新Pin

哪里序列复制M这里的函数来自数据。顺序:

复制品M 莫纳德 国际 百万米(顺序a)

这个genSource(发电机源)定义是numPins数方法。另一个将在下一节中出现。

分类操作

我正在努力实现一种既简单又能够实现笛卡尔闭范畴的标准运算集合以及副积(即双自流闭范畴,IIUC)。在这里,我将展示如何实现这些操作,我最近的帖子中也提到了这些操作过载λ.

类别操作

一个类别有一个身份和顺序组成。定义见控制。类别,

 类别k个哪里
  身份证件  `k个`(∘)(b)`k个`c)(a)`k个`b)(a)`k个`c)

所需的法律是身份证件左右都相同(∘)还有那个(∘)是关联的。

回想一下我们的电路类别(⇴)几乎与相同Kleisli电路M,其中电路M是monad(通过标准monadic构建块定义)。因此,我们几乎免费拥有(⇴)是一个类别,但我们仍然需要一点工作。

新类型a⇴b类= C类(克莱斯利 电路M(引脚a)(引脚b) )

由于此表示换行Kleisli电路M,这已经是一个类别,我们只需要再做一点展开和包装:

实例 类别(⇴)哪里
  身份证件  = C类 身份证件
  C类g∘C类(f)= C类(g∘f)

的分类法(⇴)容易跟随。例如,

身份证件C类f≡C类 身份证件C类f选C类(身份证件∘f)≡C类(f)

我将把另外两个(右同一性和关联性)作为一个简单的练习。

我喜欢用一个成语来定义类别要自动展开和包装,请执行以下操作:

实例 类别(⇴)哪里
  身份证件  = C类 身份证件(∘)=inC2(∘)

哪里

股份有限公司=   C类联合国英寸C2=inC↜unC

这个(↜)操作员在此添加后处理和预处理:

(h↜f)克=小时克小时

产品运营

接下来,让我们添加产品类型和一组最小的关联操作:一个简单的公式:

 类别k个 产品类别k个哪里不包括在内(a×b)`k个`外汇兑换率(a×b)`k个`b条(△)(a)`k个`c)(a)`k个`d)(a)`k个`(c×d))

如果你用过控制。箭头,你会认出(△)作为“(&&&)”. 这个不包括在内出口方法泛化有限状态试验信噪比。还有来自的其他操作箭头可以根据这些原语定义的方法,包括第一,第二、和(×)(称为“(***)“在中控制。箭头):

(×) 产品类别k个(a)`k个`c)(b)`k个`d)(a×b`k个`c×d)f×g=f∘exl△g𕓷exr第一 产品类别k个(a)`k个`c)((a×b)`k个`(c×b))第一个f=身份证件第二 产品类别k个(b)`k个`d)((a×b)`k个`(a×d))第二克= 身份证件×克

明显缺失的是箭头班级的阿珥方法,该方法将任意Haskell函数转换为箭头。如果我能实施阿珥,我会有我的Haskell-to-circuit编译器。我记下了名字“不包括在内”, “外汇兑换率“,以及”(△)”(发音为“fork”)摘自杰里米·吉本令人愉快的论文计算功能程序.

同样,很容易定义产品类别的实例(⇴)使用产品类别的基础ProductCat实例Kleisli电路M(存在的原因是电路M是单子体):

实例 产品类别(⇴)哪里不包括在内= C类不包括在内外汇兑换率= C类外汇兑换率(△)=英寸C2(△)

对这个实例定义进行类型检查有一个微妙之处。不包括在内定义,RHS不包括在内以上具有类型

克莱斯利 电路M(引脚引脚b)(引脚b)

但是不包括在内定义需要类型

克莱斯利 电路M(引脚(a×b))(引脚b)

幸运的是,由于引脚上述产品示例:

类型 实例 引脚(a×b)= 引脚引脚b条

同样,阶级法则的证明也很简单。

产品定律见计算功能程序(第155页),而且很容易验证。例如,

exl∘(u△v)≡u

证明:

exl∘(C类f△C类g)C类exl∘C类(f△g)C类(不包括(f△g))C类(f)

副产品操作

副积/和运算正是乘积运算的对偶。因此,方法签名来自产品类别通过反转类别箭头并用副产品替换产品:

 类别k个 副产品Catk个哪里英制`k个`(a)+b)内部b条`k个`(a)+b)(▽)(a)`k个`c)(b)`k`c)((a)+b)`k个`c)

余积定律与乘积定律也完全是对偶的,即运算被对应的运算所取代,组成被颠倒。例如,

exl∘(u△v)≡u

成为

(u▽v)∘inl≡u

就像IsSource(IsSource)上述总和的定义比产品的定义更复杂,类似地副产品Cat我发现的实例比产品类别实例。我真的很想找到更简单的定义,因为额外的复杂性让我担心。如果你想到更简单的角度,请在本文的评论中提出建议。或者,如果你理解从产品到副产品的简单性损失的根本原因,请也加入进来。

对于左射,国际标准化协会(inl)|a⇴a+b,将引脚,根据需要填充到两个表示中较长的一个,并添加一个标志False(错误)(左):

英制= C类克莱斯利 $λa
  x个常量MFalse(错误)=numPins(☑ 引脚a)编号=numPins(☑ 引脚b)衬垫=顺序复制(最大值钠铌-纳)x返回(向上(固定一个垫子)x)

类似于内部.(实施重构以消除冗余。)

然而,这个定义有一个问题。它的类型是

英寸C 源代码P2a b类a⇴a+b条

哪里

类型 IsSourceP公司= IsSource(IsSource)(引脚a)类型 源代码P2a b类=(IsSourceP公司a、,IsSourceP公司b)

相比之下副产品Cat类定义坚持完全通用性(不受约束b条). 我不知道如何解决这个问题。我们可以改变副产品Cat类定义来添加相关的约束,但当我尝试时,派生操作的类型(可通过类方法定义)变得非常复杂。现在,我将满足于侥幸成功,实施类似以下操作副产品Cat但由于额外的限制,我正在寻找的实例定义受到了阻碍。

对于(▽)操作,假设我们有一个条件操作,取两个值和一个布尔值False(错误)/其他的案件优先:

第二个C IsSource(IsSource)(引脚c)(c×c)×布尔)⇴c

现在,给定一个答:++b代表,

  • 提取sumFlag(汇总标志)对于布尔,
  • 拔出销把它们喂给(f),
  • 拔出销b条把它们喂给、和
  • 将这三个结果输入秒C:
f▽g=第二C((f×g)提取△pureC和标志)

这个(×)这里的操作是简单的并行组合,定义为所有类别的产品:

(×) 产品类别k个(a)`k个`c)(b)`k个`d)((a×b)`k个`(c)*d) )f×g=f∘exl△g𕓷exr

这个纯C函数将端对端函数封装为一个电路,由于我们使用了克莱斯利箭头:

纯C(引脚 引脚b)(a⇴b)纯C= C类∘arr

这个提取两者功能摘录二者都金额的解释:

提取两者 信息源P2a b类+b⇴a×b提取两者=pureC((引脚源△引脚源)∘sumPins)

最后,引脚源使用genSource(发电机源)来自的方法IsSource公司.

引脚源 IsSource(IsSource) 顺序 引脚 端号源端号=Mtl.evalState genSource(列出管脚)

我想要的就是这个函数genSource(发电机源)使用除电路M。这里,我们简单地使用状态引脚供应.

所以这里我们把电路作为一个带有副积的范畴。它“似乎奏效了”,但我有几点不满:

  • 我们不太明白副产品Cat实例,因为IsSource(IsSource)所有三个潜在方法定义施加的约束。
  • 定义比产品类别实例,并且不会对这些定义表现出明显的二重性。
  • 使用提取两者我很害怕,因为它意味着任何两种类型之间都存在某种动态转换。(考虑exr∘extract两者都是inl.)

关闭

我的编译方案依赖于将Haskell程序转换为biCCC(bicartesian closed category)形式。我们已经在上面看到了如何将categorys、cartesians和cocartesian(coproduct)方面解释为电路。关了怎么办?我没有一个准确的、切实可行的答案。以下是一些想法。

从调用过载λ,那是(笛卡尔式的)关闭类别是一个具有指数对象的类别b⇨c公司(经常写“c(c)b条“)进行以下操作:

应用(a⇨b)×a↝b咖喱   (a×b↝c)(a↝(b⇨c))未修剪 (a↝(b⇨c))(a×b↝c)

什么是电路指数,即“硬件闭合”?

我们可以对lambda表达式进行操作,删除内部lambda,就像传统的(我认为)非泛化那样。(参见,例如。,工作中的故障多态型失功能.)在这种情况下,咖喱不会出现在生成的CCC术语中,应用程序(由静态已知原语以外的其他原语)将被模式匹配和静态已知函数/电路的调用所取代。

或者,首先转换为CCC形式,进行简化,然后查看以下内容的其余用法咖喱,未修剪的、和应用.我不确定我真的需要处理未修剪的,它不是由lambda到CCC的转换生成的。我想我目前只将其用于解除基本体的关联。无论如何,专注于咖喱应用.

与非功能化一样,对代码进行全局扫描,并提取所有的闭包形式。如果我们已经翻译成CCC形式,那么这些形式只是咖喱应用。组装所有这些咖喱应用到单个GADT中:

数据b⇨c公司哪里

对于每个应用程序咖喱在我们的程序中,其中f|A×B↣C对于某些类型A类,B类、和C类,生成如下GADT构造函数/标记:

  克隆(_yz)  A类 (B类C类)

其中“xyz公司“为清晰起见,自动生成。注意,我们不能使用简单的代数数据类型,因为类型B⇨C类受到限制。此外,如果(f)是多态的,我们可能有一个存在构造函数。由于咖喱,我们可以用有限多个位来表示GADT构造函数,从而推广了上述副积的处理。如果我们进行单态化,那么我们可以对不同类型使用几种不同的闭包数据类型b条c(c),减少所需的位数。

现在考虑一下应用。每次出现都会有某种形式(b⇨c)×b↝c。apply的实现将提取某些类型的闭包构造函数及其参数,使用构造函数识别预期电路f|a×b↣c,并为b条进入之内(f),屈服c(c).

关于未修剪的?

未修剪的 (a↝(b⇨c))(a×b↝c)

构造的电路工作如下:给定(a、b),馈送到参数态射以获得类型的闭包b⇨c公司,具有a’和一个标记,它表示a'×b→c.给a’b条进入电路以获得c(c),返回。

状态

我还没有制定出足够精确的指数总体计划来实施,所以我期待着一些惊喜。也许还有更好的方法。请提供建议!

递归类型需要思考。如果简单地转换为总和和乘积,我们将得到无限的表示。相反,我认为我们必须通过某种内存使用间接寻址,就像在软件实现中通常做的那样。在这种情况下,动态内存管理似乎是不可避免的。间接可能最适合用于求和,无论是否出现在递归类型中,这取决于求和类型的表示大小的差异和大小。

8评论

  1. 沃博:

    求和的两边使用的管脚数量最多,这让我想起了在大学学习ALU设计。我们不会选择将数据发送到哪个电路(操作),而是将其发送到所有电路并选择要保留的输出。

    这种类型的安全恐慌可能来自这样一个事实,即这是迫不及待地评估的,所以只要我们选择有意义的操作的结果,所有的操作都会被执行,即使它们毫无意义。

  2. 澳大利亚奥:

    我想知道你是否尝试过调查Tempeley-Lieb类别(例如。http://www.math.helsinki.fi/logic/sellc-2010/course/final.pdf)?. 有一种简洁的方法来表示和,计算(简单类型的)lambda演算,更不用说将它们表示为电路是非常直接的

  3. 雅克·卡雷特:

    我认为,您不能在没有约束的情况下实现inl/inr的根本原因是,实现从根本上假定a和b是有限[这就是numPins的力量]。这是一个非常好的假设,但它不是Category的“固有”假设。这可能是一种权宜之计,即假设一个有限的范畴,你可以在这里将其定义为一个从所有对象到Nat都有映射的范畴。你可能还需要一个comonoid结构,它可以分解对象(也称为toPins)。

  4. 艾利斯:

    我相信沃博关于选择要保留的输出的观察与A+B->X具有(A->X,B->X).

  5. 艾利斯:

    我在基于箭头的“接线图”API方面取得了很大成功,我正在使用该API编写关系查询并将其编译为SQL。灵感来自David Spivak的接线图的操作:为数据库形式化图形语言….

    我成功地避免了类型实例和“IsSource”约束,这似乎是一个巧妙的技巧。我用过电线而不是引脚,而不是要求电线为了成为表示连接器的每个数据类型前面的类型构造函数,我只存储电线s在任何Haskell数据类型中,因此例如我们有加::QueryArr(线内,线内)(线内)限制::QueryArr(Wire Bool)().

    其定义是数据导线a=导线串,其中字符串字段是数据库表的列名。通过确保只能生成电线在包含在电线.

    这种方法效果很好。我没有考虑过副产品,但可能会通过代表A+B~>X作为(A~>X,B~>X)而不是试图构建A+B类自身。

  6. 瑞恩:

    我认为你可以把uncry推导出uncry。(f*id)。当f:=curry f'时,可以得到curry和uncurry是beta的倒数。

  7. 斯乔德·维舍尔:

    我在数据类别中用于CCC的方法有:

    应用?(a⇨b)×a↝b元组a↝(b⇨(a×b))(^^^)(a↝b)→(d𕀢c)→(c⇨a)

    具有curry f=(id ^^ ^ f)。元组uncry f=适用。(f***id).

    (^^^)类似于(+++)(***).

  8. 卡里:

    Dan Ghica提出对称单体闭范畴作为电路模型(网址:http://www.cs.bham.ac.uk/~drg/papers/popl07x.pdf). 他介绍了具体的类别“握手电路类别”HC和另一个称为SHC的类别。

    我不知道这些与“电路类别”相比如何C(Kleisli电路M(引脚a)(引脚b))。

留下评论