没有合适的资源?快使用搜索试试~ 我知道了~
理论计算机科学电子笔记238(2009)59-70www.elsevier.com/locate/entcs高效和类型安全的通用数据存储Sjaak Smetsers1 Arjen van Weelden1 Rinus Plasmeijer1荷兰奈梅亨大学计算与信息科学研究所摘要在本文中,我们提出了一个优雅的方法,顺序化任意数据使用通用语言扩展的函数式编程语言清洁。我们展示了如何拟议的操作可以 用于在多种IO容器(如文件或数组)以及如何有效地操作存储的数据。此外,通过使用编码的类型信息扩展存储的数据,数据操作将是类型安全的。 一般来说,这些操作的定义优点是用户定义的数据类型的特定实例可以完全自动生成。与传统的序列化方法(或使用关系数据的常见数据操作)相比,基地),我们的业务是一个数量级更快。关键词:多型/泛型编程,函数式编程,数据存储/序列化,类型推理1引言数据库通常用于数据的高效存储和检索。实际上,它们经过优化,以非常高效的方式存储(大型)表。对于大量的应用程序,这种类型的存储足够了。然而,传统的数据库通常不适合存储递归数据结构,如链表或二叉树,甚至更复杂的数据类型,如抽象语法树。在函数式语言中,这些代数数据结构非常常见。人们也希望能够在某些数据库系统中有效地存储这些代数数据类型。高效存储不仅需要快速的输入/输出操作,而且还需要有选择地更新存储数据的能力,即不需要读入和写回完整的数据结构。例如,人们希望单独检查存储的树的元素并破坏性地更新它们,或者重新平衡树的主干而不检索/复制元素。此外,存储数据的签名应编码在1电子邮件:S. cs.ru.nl,A. cs.ru.nl,R. cs.ru.nl1571-0661/© 2009 Elsevier B. V.根据CC BY-NC-ND许可证开放访问。doi:10.1016/j.entcs.2009.05.00760S. Smetsers等人/理论计算机科学电子笔记238(2009)59数据库,这样在运行时从数据库读入的数据的类型安全性就得到了保证。在本文中,我们将展示如何有效和灵活的数据操作可以实现使用通用或多型编程技术。使用泛型函数,可以指定适用于(几乎)任何类型的操作。泛型函数基本上是根据用户定义类型的结构归纳定义的。Equality和map是可以一般定义的函数的基本例子。其他著名的例子是(反)序列化函数,如打印和解析(导致数据的字符串表示)和(反)压缩(使用数据的有效二进制表示)。要在特定类型上应用泛型函数,需要该类型的泛型函数的实例。Clean编译器将完全自动地生成这个实例,但是程序员必须通过派生语句显式地请求它。本文中提出的所有IO操作都是通用的。所提供的通用函数是用Clean编写的(参见[2])。由于某些语言规范的限制,所提供的函数不能直接转换为通用Haskell。Haskell和Clean之间存在一些语法上的差异。主要区别将在后面解释;关于Clean和Haskell之间的完整比较,我们参考[1]。2泛型编程泛型函数类似于定义类型类及其实例。主要的区别是,泛型编译器可以自动派生大多数实例,给出三个(泛型)类型构造函数的最小固定实例集。 实例的推导是基于这样一个事实,即任何代数数据类型都可以用eithers(用于区分构造函数)、pairs(用于表示每个构造函数的参数类型)和units(用于表示0元构造函数)来表示。这个泛型类型表示由Hinze [6]开发,被Clean和Generic Haskell使用,并由以下Clean类型编码*2a b =左a|右b;* 配对a b =配对a b;: :UNIT = UNIT为了定义一个泛型函数,程序员指定它的签名,并为泛型类型(EITHER,PAIR和UNIT)提供实例。作为一个例子,我们定义了一个通用的包装程序,取自[9]。这个程序的基本思想很简单:给定一个值,我们构造该值的紧凑表示,其中数据构造函数由尽可能少的位表示。 例如,对于简单的二进制树这意味着我们使用单个比特来区分叶子和内部节点。为了从用于存储打包值的具体容器中抽象出来,我们首先引入classGWriteiowhere writeC:: Char *io→ *io2在Clean中定义一个新的数据类型,Haskell使用data关键字。S. Smetsers等人/理论计算机科学电子笔记238(2009)5961∗Clean使用唯一性类型来表示对象的共享属性。A表示对应的对象将是唯一的或单线程的,这意味着不存在对它的进一步引用这允许对象被破坏性地更新因此,在Clean中,可以使用显式环境传递(示例中的io参数)而不是monad来合并副作用。我们假设不直接支持写入单独的位因此,我们跟踪一些额外的信息来收集比 特 ,直 到 我 们 有 一 个 完 整 的 字 节 可 以 写 入 。 这 一 切 都 隐 藏 在 函 数compressBit::Bool(CSt *io)→(CSt *io)中使用的抽象数据类型CSt中|GWrite io 3打包函数本身很简单:genericgCompress a::a →(CSt *io)→(CSt*io)|GWrite iogCompress{|单元|}x = idgCompress {|对|} cx cy(PAIR x y)= cy y o 4cx xgCompress{|要么|} cl cr(LEFT l)= cl l o compressBitFalse gCompress {|要么|} cl cr(RIGHT r)= cr r ocompressBit TruegCompress{|对象|}co(OBx)= co xgCompress {|缺点|} cc(CONS x)=cc x与普通类的实例不同,我们使用特殊的括号{||}中。此外,每个实例的实际参数数量取决于所使用的类型构造函数的arity(实际上是类型)。 例如,PAIR和EITHER的实例提供了两个额外的参数,可用于压缩这些类型的参数。 最后,(辅助)类型OBSTANTS和CONS被添加到Clean中的泛型类型集合中,以访问有关原始类型定义(通过OBSTANTS)和数据构造器(通过CONS)的具体信息。 在这个例子中,不使用该信息,但是在第5节中,它发挥了关键作用。程序中实际使用的其他类型的泛型实例是自动派生的。例如,如果需要以下树类型的gCompress实例,* Tree a=Leaf|节点a(树a)(树a)它必须声明派生gCompress树。对于某些类型,如函数类型和抽象类型,泛型函数不能自动派生。然而,程序员可以显式地为这些类型定义专门的实例与泛型Haskell不同,Clean中泛型的实现是基于类型类的。每个泛型函数产生一个所谓的种类索引类型类的集合,即通过种类索引从泛型函数的类型获得签名的类[2]。这些泛型类型类可以用作任何其他类型类。 因此,在类型签名的上下文中允许使用它们或者可以像任何其他重载操作一样在表达式中应用3Clean通过空格而不是→分隔参数类型,并使用|来宣布上下文限制。在Has kell中,这将被写为(GWriteio)· ··。4运算符o是函数复合。62S. Smetsers等人/理论计算机科学电子笔记238(2009)593基本通用IO操作在本节中,我们将介绍两个通用函数,写和读,作为定义通用数据存储操作集的基础。本质上,write函数是gCompress的一个除了用于写入的类GWrite之外,我们还定义了用于从IO容器中读取字符的类GReadclasssGReadswhereereadC::*io→(MaybeChar,*io)GWrite和GRead的两个实例都是预先定义的:实例GWrite GString, GFile;实例GRead GString, GFileGString和GFile类型基本上是字符串(字符数组)和文件。可以使用以下函数创建(打开)和释放(关闭)这些对象openString::*String → *GString;closeString::*GString →*String openFile::!绳子!* World→(可能是*GFile,*World);closeFile::!* GFile!*世界→* 世界在Clean中,(唯一的)World类型是特殊的:它只有一个居民,本质上,它封装了机器的完整状态。这个世界对象对应于Haskell中IOmonad的隐藏状态,例如,用于执行I/0。我们的通用序列化操作的基础是两个通用函数:泛型写a::Int a *io → *io|GWrite io泛型读取a::Int *io →(可能是a,*io)|GRead iointeger参数是一个辅助参数。如果一个构造函数是写/读的,这个参数包含该构造函数的编号(即构造函数在相应代数类型定义中的位置)。然而,对于顶级调用,这个参数的值被忽略了:我们通常会提供0作为默认参数。假设我们想使用这些操作来序列化一棵树。存储在树中的值是元素,每个元素由(整数)键和包含人名和电子邮件地址的记录组成。::Elema5={key::Int,rec::a}::Person={name::[Char],email::[Char]}我们现在可以对树对象使用上一节中定义的操作首先声明必要的泛型实例:derivewriteTree,Elem,Person,[];derive readTree,Elem,Person,[]要将一个由4条记录组成的树(由fixTree创建)存储到文件fname中,我们只需这样写:我们要为世界创造一个美好的未来(Justfile,world)6=openFilefnameworld5 Clean 中 的记录类型被{和}包围。6这不是一个循环的let定义:#在LHS上为id引入了一个新的作用域S. Smetsers等人/理论计算机科学电子笔记238(2009)5963/{||}{||}{||}{|--|}→→{||}=closeFile(write*70(树4)文件)世界哪里=foldl(λlk→Node{key=k,rec=recordk}lLeaf)Leaf[1. 尺寸记录k8kl= fromString( toString k)={name=[john.do读和写的泛型实现(用于为具体类型生成实例)相当简单。与gCompress一样,每个构造函数都由一个数字表示,该数字对应于构造函数在类型定义中的位置。因为write和gCompress几乎是一样的,所以我们将自己限制在read的定义上。OBJECTIVE的实例负责读取构造函数readUNIT_io=(JustUNIT,io)readEitherread_aread_biio| i bitand 1 =0= read_b (i>> 1) io>>= RIGHT|otherwise=read_a(i>>1)io>>=LEFTread PAIRread_a read_b _io=caseread_a0ioof(Justx,io)read_b0io>>=PAIRx(Nothing,io)(Nothing,io)读取gtd_num_conses的对象read_a_io=casereadCgtd_num_consesioof(Justcn,io)→read_a(swapcngtd_num_conses)io>>=objective(_,io)→(Nothing,io)阅读全文|缺点|} read_a_io=read_a0io>>=CONS(>>=)(mb,io)f=Justx的情况mb→(Just(fx),io);Nothing→(Nothing,io)4可更新数据存储所提出的IO操作的主要缺点之一是,它们不允许在不读取和写回所有原始信息的情况下更新现有数据。特别地,当涉及大量数据时,这可能是非常不方便的或者甚至是不可接受的。在本节中,我们将展示一个扩展库,即所谓的块。块可以被看作是对仍然驻留在IO容器中的数据的引用。 通过将数据包装在块中,用户指示: 当包含该包装数据的数据结构被读入应用程序时,该信息保留在容器中。 一旦真正需要这些数据,就可以通过一个特殊的chunkValue操作显式读取。此外,这可能是最重要的改进,它可以使用updateChunk操作选择性地更新。因此,该块机制提供对数据的选择性读/写访问,而不需要读/写整个数据结构。块的实现要求IO流提供随机访问。这是通过一个额外的类型类来实现的7write {|*|}表示类型 * 的重载写操作。8清洁支持let-before(#)以增加可读性。64S. Smetsers等人/理论计算机科学电子笔记238(2009)59GIO IO类|GWrite io &GRead io whereposition:: *io→( Int, *io); seek:: Int *io→ *io为了方便起见,添加了上下文GWrite和GRead。块和对块的操作的签名定义如下。*CHSio9openChunks::*io→(Bool,Chunka,CHS*io)|GIOiocloseChunks::(CHS *io)→*ionewChunk::a(CHS*io)→(Chunka,CHS*io)|写e{|*|}a &GIOiOchunkValuee::(Chunka)(CHS*iO)→(a,CHS*iO)|read{|*|}a &GIOiOupdateChunk::(Chunka)a(CHS*iO)→CHS*s |写e{|*|}a &GIOiO用于表示块对象的类型Chunk是抽象的。另一个抽象类型称为CHS,用于分配新的块并顺序化其他块操作。通过使这种类型唯一,保证了单一的嵌入性,因此可以安全地执行就地更新。openChunks操作首先检查容器,以确定它是否为空。在空容器的情况下,创建一个新的空块(所谓的根块)。否则,读取容器的根块。布尔结果值指示发生了这些可能性中的哪一个。为了说明这些操作的使用,我们返回到我们的树示例。其思想是将存储的记录包装在块中,以便在读入树数据结构时将它们留在容器中。首先,我们用块注入由CockTree构建的树。通过使用通用的状态映射,这可以在几行代码中轻松指定addChunks::(a(Elemb))(CHS*c)→(a(Elem(Chunkb)),CHS*c)|写e{|*|O&c &gMapLSt{|* → *|} aaddChunkstr io =gMapLSt {|*→*|}addChunktriowhereaddChunk{ key, rec}10io=let( ch, nio)=newChunkrec ioin({ key= key,rec= ch},nio)来自标准Clean库的通用函数gMapLSt将状态转换函数(在本例中为addChunk)映射到树tr上。应用addChunks的结果是原始记录存储在IO容器中,并且树使用对这些记录的引用请注意,addChunks在gMapLSt {|* → *|},这意味着它实际上可以应用于任何类型的数据类型 *→*。树结构本身存储在根块中。完整的程序如下所示为这个世界写一个大块头(Justfile,world)=openFilefnameworld(ok,chunk,chunks)=openChunks文件(ch_tree,chunks)=addChunks(chunks树100000)chunks=closeFile(closeChunks(updateChunkchunkch_treechunks))world上面的树是完全不平衡的:所有元素都存储在正确的分支中。 使用组块使我们能够重新平衡树,而不触及存储的记录。 这可以通过以下方式实现(我们省略了与上一个示例相同的打开和关闭操作9Clean中的抽象数据类型。CHS前的*表示任何CHS-对象必须始终是唯一的。因此,* 可以在代 码的其余部分省 略。10{f,g}表示从记录参数中(惰性)选择场f和g。S. Smetsers等人/理论计算机科学电子笔记238(2009)5965下面,balance会取任意一棵树,并将其转换为平衡树。(unbal_tree,chunks)=chunkValuechunkschunks= updateChunk chunk( balanceunbal_tree)chunks显然,在平衡树之后,我们现在可以快速找到特定的记录,并通过updateChunk直接更新其内容。5类型安全IO操作上一节中的序列化方法的另一个严重缺点,实际上几乎所有的压缩/解压缩、打包/解包或读/显示操作,都是写的信息是非类型化的。 结构写成树 可以作为列表读回,有时会导致崩溃,或者可能不会被注意到。 在本节中,我们将展示如何扩展IO操作, 它们是类型安全的。 请注意,这个问题不能静态解决,底层IO容器中的数据(字符串,文件等)是无类型的。我们必须将有关实际数据的类型信息添加到存储的数据中。 但我们所有的操作都是以通用的方式定义的。我们如何动态地派生对象的类型,以便这些信息可以存储在容器中,并在重新开放供人阅读解决方法是再次使用泛型。 记住泛型编译器是 能够为任何具体类型构造泛型函数的版本。不幸的是,泛型函数所应用的具体类型是未知的。原因是泛型函数是在类型构造函数上定义的,而不是在具体类型上。 在类型OBSTAND和类型CONS的一般定义中,我们可以检查对应于实际类型构造器和具体数据构造器的类型定义的表示,因此,我们必须定义一个泛型函数类型,它使用这些类型定义来构造实际类型的表示。对于非递归类型,该方法很简单。然而,递归需要一种更微妙的方法。递归的主要问题是,为了得到 实际的类型,必须遍历该类型的泛型表示,一种在不进入无限域的情况下收集所有类型信息的方法, 循环.作为我们描述的起点,我们定义类型的签名如下:泛型类型a::(可能是a,类型)::TypeCons:==String::TypeVar:==Int::Type = TVar TypeVar |TApp TypeCons [类型] |TArr类型|TBas BType |TEmp类型用于将类型表示为值。我们分别使用整数和字符串来标识类型变量和类型构造函数辅助TEmp构造函数旨在用作初始值或默认值。请注意,尽管我们只对类型感兴趣,但我们必须在类型签名中使用泛型类型变量a。Maybe的使用允许我们将Nothing作为一个具体的虚拟值来传递。66S. Smetsers等人/理论计算机科学电子笔记238(2009)59→{||}{||}→→在库本身中,此结果用于构造所请求类型的默认值,例如,可以用于初始化新对象。在解释泛型类型构造的工作之前,我们先说明它的用法。 其目的是定义基本泛型IO操作的类型安全版本。我们首先引入一个包装器类型来将类型信息包含在IO容器的类型中,以及一个用于构造这样一个容器的函数。这个函数还负责类型检查。::*GStreama io={state::io}openStream::*io(Bool,GStream a *io)|GIO io&type * a openStream io(m,t)=类型*io= forceType iom|isEmptyIO IO=(True,{state = write{|*|} 0 t io})(maybe,io)=read{|*|}0io=(is Justmaybefrom Justmaybe==t,{state=io})哪里forceType::(GStream a *io)(Maybe a)GStreama*ioforceTypeioy=ioisEmptyIO检查io是否为空。在这种情况下,类型表示(由泛型类型函数产生)被写入容器。如果io不为空,read会尝试读取先前存储的数据类型,并将其与请求的类型进行比较。forceType只是一个连接流的值类型和m的值类型的辅助函数。如果没有forceType,这两者之间就没有联系,从而导致(静态)内部重载错误。所有其他操作都是简单的包装器。writeValue::a(GStream a *io)→ GStream a *io |GIO io&write{|*|} awriteValue val io=:{state}={io &state =write{|*|} 0 val state}readValue::(GStream a *io)→(Maybe a,GStream a *io)|GIO io &read{|*|} areadValueio=:{state}= let(v,nst)= read {|*|} 0状态in =(v,{io&状态= nst})closeStream::(GStream a*io)→*iocloseStream{state}=state下一个小程序展示了我们的示例树(这次没有块)可以从文件中读取。请注意,额外的Stream层使类型安全检查完全不可见。readTree::String*World(Maybe(Tree(ElemPerson)),*World)readTreetree_fileworld(Justfile,world)=openFiletree_fileworld(ok,stream)=openStream文件| ok(val,stream)=readValue stream=(val,closeFile(closeStreamstream)world)=(Nothing,closeFile(closeStreamstream)world)要调整readTree,使其可以读取可能完全不同的结构,只需更改结果类型readTree是否读取指定类型的树完全由该类型决定,而不是由代码决定。剩下的问题是如何定义类型,使得它将派生适当的类型。像往常一样,我们必须为预定义的泛型类型提供实例S. Smetsers等人/理论计算机科学电子笔记238(2009)5967结构体。然而,这一次,Obbital的替代方案起着至关重要的作用。如前所述,我们主要关注的问题之一是没有进入无限计算。为了说明这种危险,我们从类型的实例开始,它将通过编写derivetype[]为列表生成。这个生成的函数的结构,比如type_List,完全由列表类型的定义决定*名单a =无|缺点a(列表a)type_List type_a=bimap(type_OBLOG(type_EITHER(type_CONStype_UNIT)(type_CONS(type_PAIRtype_a(type_Listtype_a)函数bimap负责列表对象和它的通用表示之间的转换该函数的精确结构与本演示无关如果在type_List的右边,每个实例函数都直接调用它的continuation,这最终会导致对type_List本身的调用,从而导致无限计算。为了防止这种情况,我们为type提供一个辅助的history参数,其中包含到目前为止所做的后续调用的描述其思想是在(有向)调用图中标记一条边,该边从数据构造函数C引导到类型构造函数T,比如通过C的第n个例如,在上面的列表示例中,对type_List的递归调用将标记为List(TVar n),即Cons 类型变量的数字n应该是新鲜的,即以前没有使用过。将这些新的数字转换为新的变量需要一个额外的类型参数来维护变量counter。当输入type_List时,检查相应的调用边是否已经出现在历史中如果是,函数立即返回否则,通过调用continuation来构造基础类型但是,在某些情况下,对类型的单个遍历不足以完全派生类型。例如,考虑以下交替列表的定义* AList a b=ANil| AConsa(AList b a)例如,为了获得AListIntChar的表示,AList类型的实例必须通过type_AList两次。我们的后者也适用于更复杂的类型,*Rosea=Rosea(List(Rose a))甚至对于非统一的递归数据类型,例如下面的相当奇特的类型定义* Exot a=ENil| EConsa(Exot(Exot a))fresh变量不仅用于创建辅助唯一标记,还用于构造请求的类型。这种类型的碎片被一点一点地收集起来,并通过统一而相互连接。 这需要在每次遇到新的类型构造函数时实例化数据构造函数类型。所有这一切都由对象类型的实例来处理。我们实现的另一个细节是,统一不是在后台执行的因此,所有类型的方程68S. Smetsers等人/理论计算机科学电子笔记238(2009)59{||}{\\←}{||联系我们在类型构造过程结束时,收集并解决由遇到的依赖关系产生的问题。所有这些都导致了下面的类型签名。::历史记录:==[类型];::标记:==类型::TypeSt={fresh::Int,equa::[(Type,Type)]}一般类型a::类型历史记录和标记类型t→(可能是类型a,类型t)类型的第一个参数是上下文请求的类型。一旦type的实例产生了一个具体类型,这个类型的等式和请求的等式就会被添加到等式列表中。例如,Int类型的实例定义为:类型e{|在t|}rthistorymarkkts=(Nothingg,{ts&equua=[(rt,TBasBInt):ts. equua]})从上面的解释中,应该清楚的是,Objective的实例比Int的前一个更复杂。 由于空间限制,将其省略,单位、EITHER和PAIR的定义也是如此。泛型类型函数不适合程序员使用。首先,有几个参数只在内部使用。第二,它并没有产生一个类型,而是产生了一组仍然需要解决的等式。我们可以使用下面的包装器隐藏这个实现特定的信息defaultType::(可能是a,Type)|type * a defaultTypefv= TVar0(m,ts)=type*fv[]TEmpfresh= 1,equa=[]subst=unifyts.equaTEmp_[0. . [TS.fresh]=(m,Subst fv subst)我们使用(fresh)变量0作为top类型调用的请求类型。历史和(递归)标记都是空的,分别由[]endTEmp表示。调用类型后,将收集的方程传递给统一。这个标准的统一算法需要一个类型方程列表以及一个替代作为输入,并产生一个新的替代来解决这些方程。 我们用一个类型数组来表示替换,其中类型变量用作索引。我们从一个空的替换开始,然后它将被unify更新(破坏性地)。为了获得最终类型,将该单位器应用于变量0。6相关工作据作者所知,泛型编程技术的使用对于创建类型安全的破坏性数据库系统是新的。序列化和反序列化是任何介绍泛型或类型驱动编程技术的入门文章中的标准示例[8,7,10]。以这种方式序列化的数据可以从持久存储器中存储和检索,但它缺乏破坏性更新子结构的能力。这对于任何现实世界的数据库系统来说都是绝对必要的。当然也可以使用重载机制来代替一般技术[14]。然而,这有一个缺点,程序员必须为显式地读取和写入数据S. Smetsers等人/理论计算机科学电子笔记238(2009)5969在[5]中,给出了Haskell的持久存储。 其基本思想是扩展 应用程序存储器与永久存储器。当数据从一个内存(显式)转移到另一个内存时,应用程序中数据的内部表示转换为存储器中的外部表示,并向后转换。对于转换,需要运行时系统的支持。不提供破坏性更新,但是当结构被修改时,共享在存储器中以与应用程序内存中相同的方式维护。该方法不能用于不同应用程序之间的数据交换在Amber [4],CAML [11]和Clean [12]中,可以通过将它们包装到Dynamic中来存储和检索任何类型的值。 一个Dynamic包含一个表示一个值和它的类型的表示。这种方法确实可以在应用程序之间进行类型安全的数据交换,但是需要编译器和运行时系统的支持,以便能够检查类型一致性并处理转换。在Clean中,甚至可以在应用程序之间交换功能[15]。运行时系统的一部分是动态链接器,它可以通过加载评估新函数所需的相应代码来扩展具有附加功能的运行中的可执行文件。但是,没有一种动态方法支持破坏性更新。7结论在本文中,我们提出了一套通用的IO操作,是灵活的,高效的和类型安全的。它们是可伸缩的,因为通过一个简单的派生语句,任何具体类型的数据都可以被存储和检索。它们是高效的,因为数据结构可以部分读入,存储的数据可以破坏性地更新。 它们是类型安全的,因为数据的具体类型的表示也被存储了。 在读入数据结构之前,将根据读取器请求的数据类型检查此类型。我们已经用相当大的数据集(10万条记录)测试了这些操作,在大多数情况下,效率似乎足够。然而,如果性能是关键的,则可以通过使用[3]中描述的扩展融合技术来完全消除由通用代码生成方案引入的开销。由此产生的代码似乎是非常有效的。 我们的测试表明,我们的系统可以比标准序列化方法快一到两个数量级或数据库实现。我们的系统已集成到iTask工作流程系统中[13]。它允许交互式工作流程的高级别规范,从而完全自动地生成多用户基于Web的工作流程系统。 在工作流系统中,信息的存储和检索起着至关重要的作用。然而,在iTask系统中指定工作流程的程序员不必再担心通常附加到数据库访问的低级细节。任何类型的信息现在都可以使用我们的通用读写功能自动存储和检索。通过这种方式,数据库访问可以完全隐藏的工作流程的程序员。它还可以定义可重用的工作流程方案70S. Smetsers等人/理论计算机科学电子笔记238(2009)59在一个高层次的抽象。通过这种方式,人们可以专注于具体的实际工作流程。如果没有我们的通用数据存储方法,这是不可能的。引用[1] 彼得·阿赫滕。为Haskell98程序员清理。技术报告,计算科学研究所,数学和信息学院,奈梅亨大学,荷兰,2007年。http://www. st.cs.ru.nl/papers/2007/CleanHaskellQuickGuide.pdf。[2] Artem Alimarine和Rinus Plasmijer。 Clean的通用编程扩展。 在托马斯艺术和Markus Mohnen,编辑 , Proceedingsofthe14thInternationalWorkshoponImplementationofFunctionalLanguages,IFL 2001,pages 257-278,Stockholm,Sweden,2001年9月。爱立信计算机科学实验室。[3] 阿特姆·阿里马林和夏克·斯梅瑟斯优化泛型函数。Dexter Kozen,编辑,第7届国际会议,程序构造数学,LNCS第3125期,第1631.斯特灵,苏格兰,英国,施普林格,2004年7月。[4] 卢卡·卡德利安珀在Guy Eschineau,Pierre-Louis Curien和Bernard Robinet,编辑,Combinators andfunctional programming languages: Thirteenth Spring School of the LITP, ValSpringer-Verlag ,1986.[5] A. Davie,K.Hammond和J.昆特拉伟大的执着的哈斯克尔。 在程序草案中第10届函数式语言实现国际研讨会(IFL '98),183-194页,1998年。[6] 拉尔夫·海因兹 通用程序和程序,2000年。 在波恩大学,习惯很好。[7] 拉尔夫·海因兹泛型函数式编程的一种新方法。第27届ACM SIGPLAN-SIGACT Symposium on Principlesof Programming Languages,第119波士顿,马萨诸塞州,2000年1月。[8] P. Jansson和J. Jeuring。PolyP -一种多型编程语言扩展。 在POPLACM Press,1997.[9] 帕特里克·扬松和约翰·朱林。多型数据转换程序。Science of Computer Programming,43(1):35[10] 拉尔夫·莱梅尔和西蒙·佩顿·琼斯。废弃你的样板:泛型编程的实用方法。在Proc ACM SIGPLAN语言设计和实现类型研讨会(TLDI 2003),第26ACM。[11] 泽维尔·勒罗伊Objective Caml系统国家信息和自动化研究所,2004年7[12] M.R.C.皮尔动态类型和类型依赖函数。Kevin Hammond,Tony Davie,and Chris Clack,editors,Implementation of Functional Languages(IFLSpringer Verlag,1999年。[13] Rinus Plasmeijer,Peter Achten,and Pieter Koopman. iTasks:Executable Specifications of InteractiveWork Flow Systems for the Web. 2007年ACM SIGPLAN Intern. Conf. on Functional Programming,pages 141-152. ACM,2007年10月1日至3日[14] 安德烈·桑托斯和布鲁诺·阿布顿·蒙泰罗。haskell的持久化库。InSBLP[15] Martijn Vervoort 和 Rinus Plasmeijer 。 在 惰 性 函 数 式 语 言 Clean 中 的 惰 性 动 态 输 入 / 输 出 。在RicardoPenArts和ThomasArts编辑的第14届函数式语言实现国际研讨会上117. Springer,2003年9月。
下载后可阅读完整内容,剩余1页未读,立即下载
cpongm
- 粉丝: 5
- 资源: 2万+
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 基于Python和Opencv的车牌识别系统实现
- 我的代码小部件库:统计、MySQL操作与树结构功能
- React初学者入门指南:快速构建并部署你的第一个应用
- Oddish:夜潜CSGO皮肤,智能爬虫技术解析
- 利用REST HaProxy实现haproxy.cfg配置的HTTP接口化
- LeetCode用例构造实践:CMake和GoogleTest的应用
- 快速搭建vulhub靶场:简化docker-compose与vulhub-master下载
- 天秤座术语表:glossariolibras项目安装与使用指南
- 从Vercel到Firebase的全栈Amazon克隆项目指南
- ANU PK大楼Studio 1的3D声效和Ambisonic技术体验
- C#实现的鼠标事件功能演示
- 掌握DP-10:LeetCode超级掉蛋与爆破气球
- C与SDL开发的游戏如何编译至WebAssembly平台
- CastorDOC开源应用程序:文档管理功能与Alfresco集成
- LeetCode用例构造与计算机科学基础:数据结构与设计模式
- 通过travis-nightly-builder实现自动化API与Rake任务构建
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功