哲学博士学位论文

胡说八道--

纯功能过程
应用于
图形用户界面

马格纳斯
托马斯·哈尔格伦

 


计算机科学系
查尔默斯工业大学
哥德堡大学
S-412 96哥德堡,瑞典
哥德堡1998


ISBN 91-7197-611-6
ISSN 0346-718X

http://www.cs.chalmers.se/~hallgren/论文/

计算机科学系

哥德堡1998


摘要

本文的主要成果是一种程序编写方法纯功能语言的图形用户界面。这个方法基于一个称为胡说八道.该方法充分利用了函数语言,如高阶函数和参数化多态性。

Fudget概念是用更简单的概念来定义的流处理器,这可以看作是一个简单的,但实际上过程概念的有用体现。基于fudgets的程序流处理器是通信过程的网络使用组合器分层构建。沟通是类型安全。基本组合器提供串行组合、并行组合组成和循环。与之前的工作的主要区别流处理函数和我们的方法是远离溪流。我们得到了一个可以完全在一个纯功能的语言,但这也使得利用并行评估和不确定性(如有)在功能语言中。纯功能方法使流程成为一流的价值观,并使其易于表达进程克隆和进程迁移。

该方法的实际可行性通过Fudget库,这是一个图形用户的实现纯函数语言Haskell的接口工具包,再加上一些小到大的应用程序在库的顶部实现。

除了GUI编程之外,fudget还适用于其他形式并发I/O编程。我们演示了客户机/服务器我们可以编写基于类型安全通信的应用程序在客户端和服务器之间。我们以一个web浏览器为例GUI编程和网络通信结合在一起。

我们认为fudgets是一个更一般的基于组合子的例子一种将功能语言结合在一起的方法使用组合库是减少使用的一个很好的选择由特定于应用程序的工具支持的表达语言。我们描述一组组合词,让人想起解析组合词,用于构建语法导向编辑器。


前言

这本专著是两位作者的论文。大部分工作后面一直在进行密切的合作作者,但有些章节呈现的是更具个人特色的作品性质:
托马斯·哈尔格伦:
第19章,27岁和39岁。他还发展了中的应用程序第五部分(部分稿件在第36章和不过,37岁)。
马格努斯·卡尔森:
第24、25章,28和29。

致谢

我们要感谢托马斯·约翰森、伦纳特和杰西卡奥古斯特松,约翰·诺德兰德,本格特·诺德斯特伦,尼克拉斯·罗杰莫,大卫·桑兹,科林·伦奇曼和西蒙·佩顿·琼斯对草稿的校对和许多改进建议论文的一部分。特别感谢Ola Freijd插图和封面。

工作思路和实施受到了一批人的好评增加了福吉特图书馆的实用性。简·斯巴鲁的早期版本中的空间泄漏修复突然使运行fudget程序直到有人拔掉插头。简也实现了名称布局的初始版本机制。Lennart-Augustsson对Xlib接口的集成使用HBC的运行时系统,fudget程序更易于使用和效率更高。johnhughes发明了默认参数模拟,这使得fudget编程更加令人愉快。

计算机科学系不仅提供了丰富的学术刺激,还有技术和行政支持表现出了不同寻常的灵活性质量。特别是,我们要感谢克里斯特·卡尔森,戈尔根奥洛夫松、玛丽·拉尔森、佩尔·隆格伦和哈斯·赫尔斯特伦。

我们的部分工作得到了NUTEK的支持。

历史反思

曾经在1991年一个灰蒙蒙的秋日里,三个功能正常程序员们在咖啡休息时间聊天。他们很高兴LML编译器[AJ93型]允许他们在功能语言。其中一个已经实现了游戏俄罗斯方块在LML。另一个实施了蠕虫病毒;交互式、多用户、实时游戏[哈尔90]. 他们没有这些程序的效率问题,即使那时的电脑比我们用的电脑慢20-40倍今天。

但那时,三个函数式程序员开始使用图形工作站而不是简单的文本终端。他们他们对自己没有办法写作感到不快程序图形用户界面在功能上语言。

三个函数式程序员中年轻的两个决定开始致力于解决这个问题。三个人中年龄较大的是有点怀疑,说这可能是不可能得到的一个足够高效的解决方案,可以用它编写“真正的”程序。

两个年轻人实现了一个允许LML程序的接口与xwindows系统对话。他们还设计了一个抽象概念在施工时用作基本构件图形用户界面。这种抽象后来被命名为这个胡说八道.

第一个xwindows接口是作为一个单独的程序实现的LML程序可以通过普通文本I/O进行对话。这个最老的程序员后来将接口与运行时集成系统的LML编译器,使得接口更加丰富高效。

大约一年后,两个年轻的函数程序员感觉他们有一个相当有效的系统和一个相当好的抽象。他们写了一篇关于它的论文,并在函数编程会议[CH93b型]. 年轻人之一函数式程序员写了更多关于它的文章,并把它变成了学位论文[CH93a型].

工作继续进行。一些改进使它更容易编写程序,并将库转换为哈斯克尔。布局系统的改进允许布局和管道应单独指定。很多额外的干扰函数参数在传递参数后可能被删除引入了缺省值机制。结果版本福吉特图书馆在春季高级学校发表了演讲1995年Båstad函数程序设计[HC95型].

目录


一、引言

1组合编程

这篇论文在很大程度上是围绕组合编程我们指的是一种程序设计方法,通过组合子程序。子程序的内部细节可以是从中抽象出来,这使得人脑创建并理解非常复杂的程序。这种方法有:,当然,在各种编程中已经练习了几十年语言。然而,这是一种有时被遗忘的方法,而且通常只用于程序的一部分;例如,当与操作系统相关的任务。

为了使组合编程变得普及,这一点很重要我们不仅可以访问一个好的子程序库,而且编程活动还必须处理形成新的适合组合的子程序。否则我们能写的程序变得有限,因为它们也一样复杂。因此,组合编程也是形成新层次的子程序。

当然,编程的一个重要方面是,哪种编程一个人使用的语言。不同的编程语言差别很大当我们想编程时,他们给我们的支持组合。尤其是在形成新的子程序。作者发现编程语言基于声明的款式适合这个尊重。声明式编程语言允许我们编写程序以数学的方式。例如,考虑表达式

f((a+b)/2,(a+b)/2)
在声明式编程语言中,我们可以标识我们可以命名的子程序平均的.

平均值=(a+b)/2在里面f(平均值,平均值)
重要的是,形成子程序的活动应该对程序员来说尽可能简单。如果程序员是需要写入比中显示的更多的字符上一个示例中,另一个编程方法可能会有吸引力,即复制和粘贴.这将很快就会产生复杂的程序但不幸的是,这是一个被广泛采用的方法。

上一个例子实际上并没有引入子程序。可以简单地看作是声明一个局部变量。但是,在声明式编程语言,我们可以在我们要形成子程序。表达式

f(a,a+b)+f(a,a+c)+f(a,a+d)
对函数使用循环调用模式f.这个模式很容易被我们调用的子程序捕获二楼.

f2(x)=f(a,a+x)在里面f2(b)+f2(c)+f2(d)
在流行程序设计中形成相应的子程序例如,C语言会涉及更多。我们会先需要声明一个新的顶级函数,然后确保name没有与同一个中的其他顶级函数发生冲突最后,函数需要一个额外的变量的参数。所有参数都需要某种类型声明。

作为中组合编程的一个更高级的示例声明式风格,我们可以考虑解析组合词[伯克75][瓦德85](从现在开始,我们会经常谈论连接符什么时候我们指的是为通用而设计的子程序组合)。大量的文本解析器可以由四个组合:

从这些组合器中,程序员可以构建更有用的组合学家。这是一个可以用来分析的组合词括号内的内容:

withinParentheses(p)=令牌('(')>>p>>>令牌(')')
或者我们可以声明组合子很多(p),哪个形成一个解析器来接受任何东西p接受,零或在一个序列中有更多的次数(这是经常写的p*):

多(p)=(p>>>多(p))| | |ε
请注意,在大多数组合中,我们都使用了有可能子程序参数化,即声明式编程语言的另一个重要特性自然支持。

2个组合库替换工具

为什么我们要在可能的情况下使用解析组合词而是使用解析器生成器工具?像Yacc这样的解析器生成器[约翰75]带有一种特殊的编程语言,适用于指定解析器的任务。虽然这是一个相当强大的工具,它是有代价的,因为我们必须学习这个新的程序设计语言。其他工具(例如图形用户界面),也有自己的,领域特定编程语言。虽然他们经常在很多情况下,他们都有一个共同点,那就是程序员必须学习一个相当新的语法,而且通常形成新抽象的程序员很差。在下列情况下Yacc,不可能表达抽象与不相干的例如。也很难或不可能在不同的工具语言和程序员的通用程序设计语言,它增加了软件系统的总体复杂性。

回到我们的例子,解析组合词可以让我们顺利地在我们的软件中集成解析器,无需任何附加工具,语言或编译器。我们只需要一个用于解析的库组合学家。更一般地说,组合库可以看作是在通用编程中定义一种嵌入式语言语言。这样,程序员必须学习急剧减少,因为一般编程语言的习语直接适用。

但是,需要注意的是,组合器库经常会漏掉专用工具具有的功能(如效率)。它是一个组合库创作者面临的挑战这个。

3声明性编程和输入/输出

在后面的章节中,我们将描述如何使用组合词用于编程输入/输出。但在此之前,我们将讨论如何输出可以用声明性编程语言完成。输出从一个程序也可以看作是影响那个节目在外面的世界里。当组合效果时,它们的顺序是通常非常重要(读者可能想尝试不同的“开门”和“穿行”效果的组合例如门“”)。这是我们必须做的一个重要方面在考虑定义效果的子程序时要记住。

有两种广泛使用的样式来处理声明性编程语言。我们要么允许所有子程序直接影响外部世界,或者我们只允许子程序返回代表影响。

考虑使用direct的编程语言中的子程序影响。如果子程序是返回某个值的函数,人们常说函数可以有一个副作用在计算它的价值时。这些副作用的顺序通过定义表达。最容易做到的是子程序应该从左到右计算,然后调用子程序。另外,一个使用局部定义应该在表达式。这样的编程语言被称为严格的.

副作用会影响程序员的活动形成新的子程序,或命名子表达式。例如,它已经不清楚我们能写了

平均值=(+b)/2个在里面f(平均值,平均值)
而不是

f((+b)/2个(+b)/2页)
因为子程序的潜在副作用会是在第一种情况下执行了一次,但在第二种情况下执行了两次。

严格规划中组合规划的另一个问题语言就是我们在定义组合学家就其自身而言。如果我们用这个定义

多(p)=(p>>>多(p))| | |ε
对于许多的,我们会在一个无限循环中结束,如果参数严格计算。

编程语言的一个非常理想的特性是子程序没有副作用。此功能用于不严格,纯功能我们的编程语言将用于本文的其余部分。术语``纯粹函数“”表示保证函数始终如果参数具有相同的值,则返回相同的值,并且它没有任何副作用。更一般地说,如果相同的话表达式出现在很多地方(如例如,可以保证所有这些事件的计算结果是相同的价值观。只有在纯函数式编程语言中我们可以引入变量平均的在上一个例如,不管什么b是。

在纯函数式语言中,我们使用第二种处理方式其中子程序可能返回表示而不是直接表演。代表然后,可以将效果与效果,产生效果的新表示。最后我们整个程序所代表的效果得到了实现。这个意味着效果和计算的问题是分开的。什么时候?定义和组合效果,我们不必担心程序的哪些部分应该被计算,需要多少次可能会被计算出来,并且按哪个顺序。

有了返回效果表示的组合子打开了大门有可能操纵在它们之前开展。这可以用来适应现有的效果新情况下的组合。

在下面的内容中,我们将经常讨论组合子各种效果,或做各种输入/输出。有时,我们可以方便地认为直接执行这些效果,但重要的是要记住它们只定义了一种效果的表示。

函数语言中的4个I/O?

纯函数语言中的程序是表示当项目对外部世界的影响程序被执行。我们现在要问的问题是:怎么样指定的基本效果以及如何组合效果?

假设外部世界是一个简单的文本终端(请参见图1). 然后,有趣的效果是:输出字符到终端屏幕,并从终端键盘。程序的行为可以是描述了一系列基本的效果,所以很自然使用序贯成分建立程序的效果有着非同寻常的行为。这是典型的命令式语言。

图1.一个程序和一个用户通过文本终端进行交互。

例如,考虑一个读取一些数字的程序以空格分隔,并输出数字之和。在命令式语言中,它看起来像这样:

程序=sumNumbers 0sumNumbers科目=如果输入结束然后putNumber科目其他的 n<-getNumbersumNumbers(acc+n)getNumber=。。。putNumber=。。。
我们可以识别子程序获取编号putNumber编号作为可重用组件。退后一步反思程序是什么,我们也许可以找到不是按顺序组合的程序影响。有了更通用的编写程序的方法可能会给我们更多的机会来构建可重用的子程序。

我们选择将程序视为定义流处理器,也就是说,程序描述了一种使用输入流并生成输出流。这个观点可以追溯到兰丁[兰65]. 我们使用中显示的符号图2表示流处理器。

图2.一个流处理器。

流处理器可以看作是流上的函数。A与文本终端交互的程序可以看作是从字符流到字符流的函数。什么时候?程序运行时,函数应用于从键盘接收的字符,以及由此产生的字符输出到屏幕。

在懒惰函数式语言中,流可以表示为普通名单。与文本终端交互的程序可以因此可以使用普通的列表函数来构建。典型的懒惰数和问题的函数解法看起来有点像这样地:

程序=show.sum.map read.words
输入流被切分为单词,单词被转换为数字,这些数字被求和并转换回字符可以输出到屏幕上。

让我们将这一解决方案与强制性解决方案进行比较。两者兼而有之解决方案、解析和打印数字的子程序是可重用的。函数解决方案数字求和函数可重用为好。虽然我们使用了一个标准函数来求和数字,上面的程序可以在常量空间中执行,因为语言,计算按需执行。同样,输入从终端按需读取,允许计算与的读取和解析交织在一起的和键盘输入。(如果我们试图使用总和函数紧急解决方案,我们必须首先读取所有的数字并将它们存储在一个列表中,然后调用总和功能。因此,程序不会在恒定空间中运行。)

在函数式解决方案中,程序不再表示为基本效果的构成。相反,我们建造了从一条管道中的多个流处理器进行的程序。

我们现在看到了两种描述流处理器的方法:

5什么是软糖?

以前,我们研究了与文本通信的程序终端。我们现在完善了外界的看法,并考虑图形用户界面(图形用户界面)。与典型文本相比终端程序,通过对话与用户交互因此在本质上是顺序的,程序具有图形用户界面通过显示一个可以被视为提供各种控制和指示器的控制面板设备。各种设备都存在于平行窗口及其各自的行为大多独立于其他设备。这表明程序具有图形用户接口应该使用某种平行作文而不是顺序合成。

为了说明我们要找的是什么样的程序结构,看看反例(双关语)。用户接口应该包含两个GUI元素:一个按钮和一个数字显示。每次按下按钮时显示递增。我们希望程序包含一个每个GUI元素的流处理器,取自库(通常称为GUI工具箱或小部件集),以及特定于应用程序对按钮计数的流处理器按下并将数字输出到数字显示器。溪流处理器应按中的方式连接图3.

图3.计数器所需的程序结构例子。

关键思想是来自库的流处理器处理GUI元素的底层细节,以及应用程序程序员编写,与GUI元素通信在更高的抽象层次上。

GUI元素可以看作是程序可以与之通信的一种特殊的I/O设备。这种想法自然延伸到与其他类型的I/O设备,如Internet上的其他计算机。

我们在一个纯功能语言,是基于一种特殊的流处理器胡说八道(参见图4.``Fudget''是的缩写国家widget公司,widget在哪里缩写威斯康星州佐治亚州dget公司).

图4.福吉。

fudget既有低级流也有高层流。这个低级流总是连接到I/O系统,允许fudget通过接收事件来控制GUI元素向窗口系统发送命令。高层流可以携带任意(通常更抽象)的值,并且把那些在特定于应用程序的方式。

我们将把福吉的类型写成

F 你好 
哪里你好中的消息类型是分别是高级输入流和输出流。

fudgets之间的高层流通过使用组合运算的程序员。三种基本的方法来组合fudgets(通常是流处理器)是连续剧,平行构图循环,参见图5.

图5.串行组成,并行合成和循环。

这些组合词的类型有:

>==<::Fa b->Fc a->F断路器--连续剧
>*< ::Fa b->Fa b->F甲b--平行构图
套圈::Fa->Fa a a--循环
这些简单的想法允许程序具有图形用户界面使用声明式样式以分层方式构建。例如,反例可以表示为

显示f>==<count>==<buttonF“增量”
也就是说,一个由三个傻瓜组成的连续剧显示器按钮处理用户与,以及假币只需数一下按钮出版社。

连载作文与普通作文密切相关功能组成。考虑到这一点,我们可以看到程序的结构与Landin stream I/O非常相似数字求和示例如所示第四章.示例像这样的问题将在中进一步解释第九章.

6论文贡献

这篇论文的工作是从用图形用户界面编写程序功能语言。我们还想实现GUI工具包功能语言本身。我们问的问题我们是:我们相信,这篇论文表明,答案是肯定的这些问题。

本文的主要工作成果是福吉特图书馆,实现了上一节。除其他外,它还提供

Fudget库展示了并发编程范例如何在纯函数编程中实现和应用语言。

我们展示了福吉特图书馆的实用性通过展示一些应用程序,其中一些是相当大。许多编程风格和方法是在用Fudgets编程时可以使用。

7路线图

我和福吉特一起编程

fudget概念和fudget图书馆首先被构想出来设计用于帮助在懒惰的函数式语言。尽管福吉图书馆现在支持其他类型的I/O,库的主要部分仍然与图形用户界面编程。

在Fudget库中,每个GUI元素都表示为福吉。这座图书馆为许多普通的基础建筑提供资料块,如按钮、弹出菜单、文本框等。该库还提供了允许构建的组合器块组合成完整的用户界面。

本节通过介绍一个GUI编程示例的数量。它们说明了如何从GUI元素和特定于应用程序的代码。在示例之后,将概述图书馆。我们展示

8 Haskell简介

我们将在论文的其余部分是哈斯克尔[Pet97]. 介绍可以在[HPF97型],还有那里也是两个定义语言及其标准的报告图书馆[PH97b][PH97a].

我们相信程序示例在没有Haskell的详细知识希望函数式语言已经足够了。然而,有些重复出现的模式也许值得解释:

Haskell的一个独特之处是类型分类系统[WB89],这是对超载的系统处理。A类型类声明引入了许多函数将会超载。实例声明给出了特定类型的重载函数。例如,Haskell中的标准类型类是类型的类支持平等:

等式a哪里(==):a->a->布尔
允许布尔值测试是否与==运算符的实例声明,如可以使用以下方法:

实例均衡布尔哪里真==真=真假==假=真_===错误
对于一些标准类型类,实例声明可以是通过添加衍生条款类型定义:

数据布尔=假|真衍生情商
在新函数定义中使用重载函数时,重载可能由新函数继承。例如,考虑一下函数元素检查是否出现值在列表中,定义为

x`elem`[]=假x`elem`(y:ys)=x==y | | x`elem`ys
类型元素是书面的

元素::公式a=>a->[a]->Bool
那部分呢公式a=>被称为上下文它的意思是类型变量限制为的实例情商班级。

在Haskell 1.3中,类系统被泛化为允许类类型构造函数[琼93]而不仅仅是基类型的类。类型变量被扩展到类型构造函数上的范围。这意味着像这样的计划内景是允许的。类型变量可以实例化为,例如,也许 吧以及列表类型构造函数,给出类型可能是内景[内景]分别是。

众所周知的功能地图,

地图::(a->b)->[a]->[b]
它是为许多函数语言中的列表定义的,现在可以通过介绍课程概括函子,

函子f哪里地图::(a->b)->f a->f b
列表和也许 吧类型可以定义为

实例函子[]哪里地图f[]=[]映射f(x:xs)=f x:map f xs实例可能是函子哪里map f Nothing=无映射f(仅x)=仅(f x)
然而,构造函数类的引入是由对单个I/O的更改(请参见第41.1.3条)和一个方便的一元程序设计的语法。班级单子定义为

单子m哪里return::a->m a(>>=):m a->(a->m b)->m b
还有特别的一元表达式的语法,
 1<-1
   2<-2...n
定义为与

1>>= (\1->2>>= (\2->...n))

9你的前8个Fudget程序

下面,我们将展示8个简单的GUI编程示例。在每个例子中,我们展示了一个窗口快照、程序文本和解释这个例子的要点。保持陈述合理,许多不重要的细节都是故意的没有解释。读者是指福吉特图书馆参考手册[CH97]了解全部信息。另外,本论文的WWW版包含超链接到许多组合词和类型的参考手册。WWW版本位于

http://www.cs.chalmers.se/~hallgren/论文/
窗口快照是在运行X Windows系统和窗口管理器的Unix工作站提供类似Windows-95的窗框。

关于实用的细节,比如在哪里可以找到福吉特图书馆,支持哪些平台以及如何编译程序,请参阅附录A.

9.1“你好,世界!”

我们从一个简单的程序开始在窗口中显示消息。这个例子说明了主程序应该看起来像,以及一些其他实用的细节。如窗口转储所示,窗口管理器会添加一个标题信息栏。

以下是源代码:

进口胡说八道main=fudlogue(shellF“你好”(labelF“你好,世界!”)
注:
  • 使用Fudget库,模块胡说八道应该导入。
  • 一个fudget程序由多个fudget组合在一个构成一个主要骗局的等级结构。功能胡说八道,

    胡说八道::Fa b->IO()
    将主fudget连接到Haskell的I/O系统,从而启动他们之间的对话。它建立了与窗口系统,收集来自所有傻瓜的命令编程并发送到窗口系统,然后分发事件从窗口系统发送到相应的福吉特。
  • 带有图形用户界面的fudget程序需要一个或更多shell窗口(顶层窗口)。这些可以用功能贝壳,

    贝壳:字符串->Fa b->F甲b
    它给了一个窗口标题和一个fudget,创建了一个shell窗口包含由参数定义的图形用户界面福吉。GUI元素的谬误,比如拉贝尔夫,不能直接在程序的顶层使用,但必须出现在一个贝壳窗里面。
  • 在这个简单的程序中,shell窗口的内容仅仅是一个简单的字符串标签,它是用函数创建的拉贝尔夫,

    拉贝尔夫:: (图解的a) =>a->F卑诗省
    参数是要显示的标签。标签可以是的实例的任何类型的值图解的班级。Fudget库为许多预定义的类型,包括字符串。这个图解的课堂讨论在里面第27.1条.

    的输入和输出类型拉贝尔夫are类型其他任何地方都不会出现的变量。这表明没有高级流由使用拉贝尔夫.

    拉贝尔夫只有一个参数:标签显示。大多数GUI fudget有两个版本:一个标准版本版本,比如拉贝尔夫,以及一个可定制的版本,用于例子拉贝尔,允许您更改字体和颜色等参数提供默认值。看到了吗第十五章更多细节。

  • GUI元素的大小和位置不需要明确规定。fudget系统自动选择合适的尺寸因为标签和外壳的大小是相适应的。
有用的程序当然包含不止一个GUI元素。这个下一个例子将包含两个GUI元素!

9.2阶乘函数

这个程序说明了数据是如何在不同的一个骗局计划的一部分。它说明了一种简单的组合方式特定于应用程序的代码(在本例中是阶乘函数)使用Fudget库中的GUI元素。

程序在底部显示一个数字输入字段和一个数字显示在顶部。当用户在输入字段并按返回key,这个数的阶乘计算并显示在数字显示中。

以下是源代码:

进口胡说八道主=fudlogue(shellF“Factorial”facF)facF=intDispF>==<mapF fac>==<intInputFfac 0=1fac n=n*fac(n-1)
注:
  • 程序facF公司结构是由三部分,使用运算符>==<注意,作为使用普通函数组合,数据从右到右左。零件包括:
    • 数字输入字段输入输出,
    • mapF公司 fac公司,抽象的胡言乱语(一个没有相应图形用户界面的傻瓜元素)适用fac公司,从输入字段接收的整数的阶乘函数,
    • 数字显示intDispF公司,显示计算的阶乘。
  • 我们用过胡说八道贝壳与前面的示例中相同的顶层(第9.1条).
本例中使用的新库组件的类型为:

>==<::Fa b->Fc a->F断路器输入输出::F内景,内景mapF公司::(a->b)->F甲bintDispF公司::F内景a
虽然这个程序做了一些有用的事情(至少与前面的两个例子),它可以变得更加用户友好,e、 通过在用户界面中添加一些解释性文本。这个下一个例子展示了如何做到这一点。

9.3阶乘函数,改进布局

这个程序展示了如何使用布局组合来提高视觉效果一个骗人程序的出现。

我们从第9.2条通过在输入字段和输出中添加标签,可以实现更多的自文档化显示。我们还改变了两部分的顺序:条目字段现在位于显示上方。

以下是源代码:

进口胡说八道主=fudlogue(shellF“Factorial”facF)facF=placerF(垂直revP)((“x!=”`labLeftOfF`intDispF)>==<mapF fac>==<(“x=”`labeftoff`intInputF))fac 0=1fac n=n*fac(n-1)
注:
  • 我们使用了函数标签F将标签放在输入字段和显示的左侧。(在Haskell中,反引号可用于将任何函数转换为中缀接线员,我们已经做过了标签F这里)。
  • 功能广场可以应用于fudgets的构图来指定零件的放置。(布局系统会自动拾取如果布局未指定,则进行一些放置。)第一个论点广场是一个砂矿,在我们的例子中垂直旋转,其中垂直使零件垂直堆叠,与最左边的软糖在最上面的作文里修订版反转零件的顺序。
  • 其他一切都和前面的例子一样。
本例中使用的新库组件的类型为:

标签F:: (图解的a) =>a->Fb c->F卑诗省广场::砂矿->Fa b->F甲b修订版::砂矿->砂矿
垂直::砂矿

9.4递增计数器

这个程序说明了一种更一般的方法将特定于应用程序的代码与福吉图书馆。它说明了这一点状态信息可以是封装的。状态信息通常被认为是困难的用纯函数语言处理;希望这个柜台例子说明这是多么容易!

这个程序有一个按钮和一个数字显示。按下按钮可增加显示屏上的数字。

本例中特定于应用程序的代码位于按钮之间还有显示器。它维护一个递增的内部计数器以及每当从按钮接收到点击时输出到显示器。

以下是源代码:

进口胡说八道main=fudlogue(shellF“Up Counter”计数器)counter=intDispF>==<mapstateF count 0>==<buttonF“向上”count n Click=(n+1,[n+1])
注:
  • 和阶乘的例子一样(第9.2条),中央项目的一部分(假币)是由三部分组成的连续剧。在输出端,我们看到熟悉的intDispF公司.在管线的输入端有一个按钮按钮.它输出一个点击按下时。中间部分维护一个内部计数器。计数器递增,并且点击收到从按钮。
  • 地图斯塔夫,就像mapF公司,允许发送消息在一个特定的应用程序中处理好吧。地图斯塔夫,任意数量的消息可以作为对输入消息的响应输出。此外输出不仅取决于电流输入,还取决于内部状态。地图斯塔夫有两个参数:a状态转移函数以及初始状态。当应用于当前状态和输入消息,状态转换函数应该产生一个新的内部状态和一个输出列表信息。

    功能计数是状态转移函数在这个节目里。

  • 这个计划有一个小陷阱:intDispF公司自动显示0当程序启动时。这个计数器的初始值正好是0也。如果这个0的定义发生了变化假币,显示屏仍将显示0当程序开始。解决此问题的一种方法是使用intDispF公司指定要显示的初始值。
本例中使用的新库组件的类型为:

按钮:: (图解的a) =>a->F 点击 点击
数据 点击=单击地图斯塔夫::(a->b->(a,[c]))->a->F卑诗省
这个和前面的例子展示了序列的组成建立一个从一个傻瓜到另一个傻瓜的沟通渠道。但是如果一个傻瓜需要来自多个来源的信息呢?下一个示例显示了一种可能的解决方案。

9.5升降计数器

这个例子说明了如何处理来自多个来源(图6).

图6.上升/下降计数器。

这两个按钮影响同一个计数器。

以下是源代码:

进口胡说八道main=fudlogue(shellF“向上/向下计数器”Counter)counter=intDispF>==<mapstateF count 0>==<(按钮填充三角形向上>+<按钮填充三角形向下)计数n(左键单击)=(n+1,[n+1])计数n(右键单击)=(n-1,[n-1])
注:
  • 递增/递减计数器是递增计数器的一个小扩展(第9.4条).我们通过替换添加了一个按钮
    按钮。。。
    具有
    (按钮F…>+<按钮…)
    使用运算符>+<平行构图。
  • 并行合成的输出是来自的合并输出这两个部分。左组件的输出被标记右组件的输出被标记赖特.施工人员赖特数据类型中的构造函数或者.
  • 这个计数函数现在将接收左键单击右键单击,取决于按下的按钮。它有相应调整。(请注意左键单击右键单击与左右鼠标无关按钮!)
  • 只是为了说明按钮可以显示任意图形和不仅仅是文本,我们使用了两个合适的形状由图书馆提供。
  • 其他一切都和前面的例子一样(第9.4条).
本例中使用的新库组件的类型为:

>+<::Fa b->Fc d->F(或者a c)(或者b和d)填充三角形:弹性拉伸填充三角形:弹性拉伸

9.6升/降/复位计数器

这个例子展示了如何创建多个同一类型的傻瓜。

这个程序用另一个例子扩展了反例按钮。计数器现在可以递增、递减和复位。

以下是源代码:

进口胡说八道main=fudlogue(shellF“向上/向下/重置计数器”Counter)counter=intDispF>==<mapstateF count 0>==<buttonsF数据按钮=向上|向下|复位衍生情商buttonsF=listF[(向上,buttonF“向上”),(向下,按钮“向下”),(重置,按钮“重置”)]计数n(向上,单击)=(n+1,[n+1])count n(向下,单击)=(n-1,[n-1])计数n(重置,单击)=(0,[0])
注:
  • 当把两个以上同一类型的软糖放在一起时,使用起来更方便列表F>+<.关于列表F是一个列表地址和谎言对。地址用于中的组件发送和接收消息组成。
  • 在这个程序中有一个用户定义的枚举类型按钮,其元素用作按钮。接收到的消息计数功能是对按钮价值观和点击美国。
  • 其他一切都和前面的例子一样(第9.5条).
本例中使用的新库组件的类型为:

列表F:(公式a)=>[(a,Fb c)]->F(a,b)(a,c)

9.7可装载上/下计数器

这个例子说明了如何使用循环来处理用于输入和输出的用户界面元素(图7).

图7.可加载上/下计数器。

该程序在第9.5条通过允许用户通过在显示字段。

以下是源代码:

进口胡说八道main=fudlogue(shellF“可加载上/下计数器”Counter)counter=loopthroughtf(mapstateF count 0)intInputF>==<(按钮F填充三角形向上>+<按钮填充三角形向下)计数n(左n')=(n',[])count n(右键(左键单击))=(n+1,[左(n+1)])count n(右键单击))=(n-1,[左(n-1)])
注:
  • 而不是intDispF公司我们用过输入输出,它不仅显示数字,还允许用户输入数字。
  • 我们用了组合子环通滤波器允许这个计数函数同时接收来自和发送的输入输出到intDispF公司。在构图中环通滤波器复旦大学1 复旦大学2,复旦大学1处理通信与外部世界(本例中的按钮)同时复旦大学2只能与复旦大学1,在这个意义上封装复旦大学1.英寸复旦大学1,消息发送/发送复旦大学2标记来自外界的信息是标记赖特.
本例中使用的新库组件的类型为:

环通滤波器::F(或者a和b)(或者c d)->Fc a->F天哪

9.8简单计算器

最后一个例子,我们展示了一个稍微大一点的程序简单的计算程序,可以使用前面的例子(图8).

图8.计算器。

为了简单起见,使用后缀表示法,即计算输入的3+43输入4+。源代码可以在中找到图9.

进口胡说八道main=fudlogue(shellF“计算器”计算)计算=intDispF>==<mapstateF calc[0]>==<按钮数据按钮=加|减|次| Div |回车|数字整数衍生情商buttonsF=placerF(矩阵4)(列表[D7,D8,D9,op Div,d4,d5,d6,操作次数,d1,d2,d3,运算减号,孔,d0,ent,op Plus])哪里d n=(数字n,按钮f(显示n))ent=操作输入孔=(Enter,holeF)op o=(o,按钮F(o标签o))哪里opLabel Plus=“+”opLabel减号=“”opLabel Times=“*”opLabel Div=“/”opLabel Enter=“输入”计算(n:s)(数字d,U)=新(n*10+d)s计算s(输入,Β=(0:s,[])计算(y:x:s)(加上,U)=新(x+y)s计算(y:x:s)(减,U)=新(x-y)s计算(y:x:s)(次数,_U)=新(x*y)s计算(y:x:s)(Div,u)=新(x`Div`y)s计算s=(s,【】)新n s=(n:s,[n])

图9.计算器的源代码。

注:

  • 程序结构与up/down/reset基本相同计数器(第9.6条).
  • 指定我们使用的按钮的位置广场(如第9.3条)以及放置器矩阵X以列数作为参数。
  • 应用程序特定代码(的功能计算)是堆栈(表示作为一个列表)数字。功能计算推动并根据需要从堆栈中弹出数字。最后定义中的子句表示如果堆栈上用于操作的值太少。
  • 现在,计算器可以用鼠标控制只有。的可定制版本按钮允许你指定按钮的键盘快捷方式。所以会是这样相对容易使计算器从键盘控制。
本例中使用的新库组件的类型为:

矩阵X:Int->砂矿

10个Fudget库GUI元素

在本章中,我们将介绍一些由福吉特图书馆。有关更多信息,请参阅参考资料手册,可通过WWW[HC97型].

在介绍GUI元素之前,我们将简要讨论如何fudget程序是使用函数形成的胡说八道.

10.1程序顶层使用的功能

正如我们在中的示例中看到的第九章一个蠢货这个程序由许多假惺惺的人组成构成一个类型的主要错误的层次结构F b,对于某些类型b.主要Haskell中的程序应具有类型IO(),所以我们需要一个胶水功能可以插在主软糖上。功能胡说八道为此目的提供:

胡说八道::Fa b->IO()
这个主要的通常是一个骗人的程序只是打电话给胡说八道因为一场争论,福吉。喜欢

主::IO()main=福洛格你在胡说八道
但是,也可以合并胡说八道与其他一元I/O操作。例如,创建一个程序从读取一些配置文件开始,您可以编写

主=配置<-readFile配置文件名胡说八道(梅恩·福吉特配置)
具有图形用户界面的程序至少需要一个shell(顶层)窗口。这些是用函数创建的贝壳:

贝壳:字符串->Fa b->F甲b
典型的GUI程序只有一个shell窗口所以程序看起来像

main=fudlogue(外壳窗口标题 主奎圭福吉)
例如,一个有多个shell窗口的程序可以看起来像喜欢

main=fudlogue(外壳标题1 复旦大学1>==<外壳标题2 复旦大学2)
笨蛋贝壳不局限于顶层。你可以把上面的例子写成

main=fudlogue(外壳标题1(复旦大学1>==<外壳标题2 复旦大学2))
达到同样的效果。

10.2显示值

我们已经看到了拉贝尔夫,显示静态标签,以及intDispF公司,显示可以动态变化。在那里也是显示器,

显示器:: (图解的a) =>F甲b
用于动态更改值的更通用的显示。它可以在中显示任何类型的值图解的班级。实际上也可以显示数字,但是intDispF公司拥有优点是数字显示正确调整。

10.3按钮

我们已经看到了按钮,

按钮:: (图解的a) =>a->F 点击 点击
在上面的例子中。它提供命令按钮,即。,按钮按下时会触发一些动作。福吉特图书馆还提供切换按钮和单选组(图10). 按这些按钮会引起变化这有一个持久的视觉效果(可能还有其他一些持久效果)。切换按钮在两种状态之间切换(打开和关闭)每次你按下它。无线电组允许你激活几个互斥的替代方案中的一个。这个这些蠢货的类型是
切换按钮:: (图解的a) =>a->F布尔布尔布尔放射组:: (图解的b、 公式a)=>[(a,b)]->a->Fa a a
输入消息可用于更改下的设置程序控制。

切换按钮“运行”

射线组F[(1,“P1”),(2,“P2”),(3,“P3”),(0,“关”)]0

图10.切换按钮和单选组。

10.5输入值

从列表中选择一个备选方案通常比键盘。但如果没有预先确定的备选方案,您可以使用fudget,它允许用户从键盘。图书馆提供
字符串输入::F字符串输入输出::F内景,内景
用于输入字符串和整数(请参见图13). 输入其他类型的值,可以使用字符串输入附加适当的打印机和解析器函数。

10.5.1关于用户输入的更详细信息

无稽之谈字符串输入输入输出不要在用户按下进入(或返回)指示输入完成的键。这个通常是一种合理的行为,但是这些谎言提供了更详细的信息:

::F字符串(输入消息字符串)国际贸易组织::F内景(输入消息内景)
这些fudgets输出类型为输入消息,包含输入字段的当前内容和指示值是中间值还是完整值。

有一些流处理器在以下情况下很有用后处理来自输入字段的消息:

条带输入SP::服务提供商(输入消息a) a输入leavedonesp::服务提供商(输入消息a) a输入::服务提供商(输入消息a) a
第一个传递所有的信息,这样你就会知道关于输入字段内容的所有更改。这个当用户指出当输入焦点离开条目时,输入完成字段。最后一个只在输入为表示完成。

笨蛋字符串输入定义为

stringInputF=absF输入donesp>==<stringF
正如我们在上面看到的,福吉选择列表F也会产生类型的输出输入消息。在这种情况下,考虑输入当用户双击另类选择。所以你用条带输入SP如果是单身点击应该足以做出选择,并且输入如果需要双击。

10.6显示和编辑文本

图书馆提供复印机
莫雷夫::F[字符串](输入消息(整数,字符串)莫雷菲夫::F字符串(输入消息(整数,字符串)更多文件hellf::F字符串(输入消息(整数,字符串)
可以显示较长的文本。(脚注:名字来自它们与UNIX程序的作用相同更多.)输入到莫雷夫是文本行的列表显示。另外两个fudget显示文件的内容输入中接收到的名称。此外,更多文件hellf显示在其自己的shell窗口中,标题反映名称显示的文件。

还有一个文本编辑器fudget(图14),

图14.文本编辑器fudget编辑.

它支持使用鼠标进行剪切/粘贴编辑,以及gnuemacs中使用的击键的一小部分。它还有一个撤消/重做机制。

10.7滚动条

可能会变得非常大的GUI元素,比如选择列表F,莫雷夫编辑,有滚动条默认情况下附加。也有组合词显式地添加滚动条:
scrollF,vscrolllf,hscrolllf::F a b->F a b
这个h版本只提供垂直和分别是水平滚动条。福吉的论点是任何GUI元素的组合。

11指定布局

在为GUI元素组合fudget时,有两个注意事项:在开发fudget程序时,没有必要关注GUI fudgets的实际布局。例如,笨蛋
shellF“按钮”(按钮“A按钮”>+<按钮“另一个按钮”)
将获得一些默认布局图15.

图15程序,自动排样系统选择一个。

但迟早,我们会希望控制布局。GUI库允许我们使用两种不同的方法来执行此操作:

在描述这些之前,我们将呈现布局组合子他们俩都用的。

11.1盒、放置器和垫片

布局是按层次进行的。每个GUI fudget将驻留在,当布局完成。一个列表框可以放在一个一个盒子砂矿.placer定义了框应该是什么样的放在相对较大的盒子里。这个封闭盒可进一步放置,但封闭的盒子被放置者隐藏在某种意义上不能再单独操纵了。一些影响砂矿如中所示图16.

图16不同的放置位置。

的参数矩阵X指定列数矩阵应该有。放置器的类型是

水平::砂矿
垂直::砂矿
矩阵X:Int->砂矿
修订版::砂矿->砂矿
放置者修订版反转应用它的框的列表另一个更高阶的定位符是翻转,哪个将放置器转换为镜像对称放置器到线x=y(即,它翻转x和y坐标):
翻转::砂矿->砂矿
因此,我们可以定义垂直作为
verticalP=水平翻转
放置药可以通过广场:
广场::砂矿->Fa b->F甲b
它将placer应用于参数fudget中的所有框。这个框的顺序是从左到右,相对于连接符列表F,>==<>+<等等。

作为一个例子,假设我们要指定两个按钮在里面图15应该有垂直布局。我们然后就可以写了

shellF“按钮”(placerF verticalP(buttonF“A Button”>+<buttonF“另一个按钮”)
结果见图17以同样的方式,第一个按钮可以放在下面、右边或到第二个按钮的左侧,使用放置器修订版垂直,水平水平旋转,分别。

图17.与中相同的GUI元素图15,但程序明确指定垂直布局。

抽象的傻瓜没有布局中的相应框。这意味着地图斯塔夫在定义中假币在里面图18,在布局中留下一个洞垂直护壁.

verticalcount=placerF verticalP countcounter=intDispF>==<mapstateF count 0>==<(按钮“向上”>+<按钮“向下”)

图18.垂直向上/向下计数器布局。抽象的福吉在里面没有对应的框布局。

如果我们想让显示屏显示在两个按钮之间呢?在我们看到的布局中,会出现两个按钮在布局中一起出现,因为它们一起出现在程序中结构。一种解决方案是使用放置操作符允许排列框的顺序:

permuteP::[Int]>砂矿->砂矿
然后我们可以替换垂直具有

垂直坡[2,1,3]
把显示器放在中间。这种解决方案有效,但很快就会变得相当复杂明白。更一般的解决方案是使用名称布局(第11.2条).

放置器用于指定一组盒。相反,垫片用来把一个盒子包在一个盒子。间隔可以用来确定一个盒子应该是怎样的如果给了太多的空间就对齐,或者添加额外的空间围绕着一个盒子。处理对齐的垫片示例可以是图19.

图19对准用垫片。

最上面的盒子水平)必须加满所有可用空间。下面三个盒子已经放好了在一个消耗额外空间的盒子里。使用的垫片是从间隔棒衍生出来的大比目鱼,谁的论点表示框左侧的空间与总可用额外空间:

大比目鱼::对齐->垫片左=hAlignS 0H中心=哈林0.5权利=hAlignS 1
有一个相应的垫片翻转,即翻转它也会翻转是的坐标,以及让我们定义一些有用的垂直间隔:
翻转::垫片->垫片vAlignS a=翻转(hAlignS a)顶部=向左翻转vCenter=翻转hCenter底部=翻转权限
补偿,我们可以组成间隔,并定义一个水平和垂直居中:
补偿::垫片->垫片->垫片centerS=vCenter`compS`hCenterS
为了在框的左右两边增加额外的空间,我们使用H精氨酸左边 正确的,其中
H精氨酸::距离->距离->垫片
类型 距离=内景
距离以像素数表示。(脚注:这很简单实现,但使程序有点依赖于设备。)H精氨酸,我们可以得出边距,这增加了长方体各边的相等空间:
vMarginS above below=翻转(hMarginS above below)页边距s=vMarginS s s`compS`hMarginS s
垫片可以通过垫片F:
垫片F::垫片->Fa b->F甲b
笨蛋垫片Fs f将应用垫片s所有的盒子f不包括在内在其他盒子里。我们也可以通过包裹一个垫片来修改放置器在放置器组装的盒子周围:
垫片::垫片->砂矿->砂矿
例如,垫片左水平提供水平放置器将左调整其框。

11.2名称布局

为了将布局和fudget结构分开,我们使用了唯一的名称在每个框(通常对应于一个简单的GUI fudget)上我们要控制的布局,使用名称:
类型LName=字符串名称F::LName->Fa b->F甲b
以这种方式命名的框的布局是使用类型指定名称布局这是基本的构造函数名称布局价值观:
leafNL::LName->NameLayout地点:砂矿->[名称布局]->名称布局空间NL::垫片->名称布局->名称布局
要将布局应用于命名框,我们使用名称布局:
名称布局F::名称布局->Fa b->F甲b
作为名称布局的一个应用,我们展示了顶住图18可以更改,以便显示出现在向上和向下按钮之间(图20):
nlfark=布局计数器名称布局counter=名称f dispN intDispF>==<mapstateF计数0>==<(名称upN(按钮填充三角形向上)>+<名称down(按钮filledTriangleDown)--仅下方布局布局=placeNL verticalP(地图叶[upN,dispN,downN])upN=“向上”downN=“向下”dispN=“显示”

图20.使用名称布局,图形用户界面的顺序窗口中的元素不必与在程序文本中排序。

现在,我们可以控制两个按钮的布局显示,而不更改程序的其余部分。

用于名称的实际字符串并不重要,例如只要它们在福吉特结构中是独一无二的它们在范围内。所以我们可以写

(upN:downN:dispN:\=地图显示[1..]

11.3不同布局方法的优缺点。

在指定用户界面的布局时,Fudget库提供了至少三种不同于表现力和安全性:
  1. 这个不在乎解决方案:忽略问题。程序员可以使用管道组合器,没有指定布局。这个系统将自动选取一些布局。这是非常安全,但显然不能程序员对布局的任何控制。
  2. 基于组合子的方法:程序员插入广场应用于中某些选定点的某个放置器复古的等级制度。这给了程序员更多的控制权超布局,仍然非常安全,但是有一个福吉特是如何组成的和它们出现在屏幕上。这不一定是坏事,但是它限制了布局选择的自由。
  3. 名称布局:GUI元素的框被标记有独特的名字。在程序的顶层,程序员插入名称适用于布局规范,通过引用名称,可以实现GUI的布局与它们的构成方式完全无关的元素。

    在这个解决方案中,有可能犯错误,然而。为了使布局规范正常工作每个命名框的名称在布局规范。如果你忘了提到一个盒子,或者你说了两次,或者你说出了一个没有提到的盒子存在,布局将无法正常工作。这些错误是在编译时未检测到,但会导致运行时错误或者一个奇怪的布局。

因此,福吉特图书馆提供安全的解决方案自由,以及完全自由的不安全解决方案。尊敬地为了安全性和表现力,在其他方面功能性GUI工具包,例如Haggis[96英尺]以及小工具[95号],相当于名称布局。

名称布局解决方案的问题是它需要两个不同部分之间的某种一致性程序。在项目期间保持这种一致性发展当然是对程序员。

是否可以使用类型系统使名称布局安全?会的也许可以在GUI的类型会在普通Haskell型系统。但是,要求名称在布局规范中只出现一次你需要一个线性类型的类型系统[霍尔88]抓住所有的错误。

12个抽象的谎言

当在程序中使用Fudget库时,fudgets来自库通常与一些特定于应用程序的代码组合,即通常在串行组合中附加到程序中。例如,我们已经看到了mapF公司地图斯塔夫为此:

facF=intDispF>==<mapF fac>==<intInputF
counter=intDispF>==<mapstateF count 0>==<buttonF“向上”
功能mapF公司地图斯塔夫创造摘要胡说八道,也就是说,不执行任何I/O的fudget。他们只通过他们的高级流进行通信。

一种更一般的方法来构造抽象的fudgets是由功能absF公司,

absF公司::服务提供商a b->F甲b
哪里服务提供商是plain的类型构造函数流动处理器。它们有一个输入流和一个输出溪流。功能absF公司通过联系制造一个谎言流处理器的流到fudgets,同时让底层流断开连接,比如显示在图21.

图21.把一个流处理器变成一个抽象的傻瓜。

功能mapF公司地图斯塔夫事实上定义为absF公司:

mapF=absF mapSPmapstateF=absF mapstateSP
哪里mapSP公司地图状态,

mapSP公司::(a->b)->服务提供商甲b地图状态::(a->b->(a,[c]))->a->服务提供商卑诗省
在中讨论第16.2条第16.3条分别是。

虽然高级组合学家喜欢mapF公司地图斯塔夫对于大多数fudget应用程序编程来说是足够的,一些程序员可能更喜欢更基本方法的灵活性创建流处理器。两个例子定义如下absF公司可以在第32.4条。对流处理器的广泛讨论可以被发现在第三部分.

13福吉管道

我们已经看到了如何使用fudget管道的例子组合学家。作文有三种基本形式:连载合成、平行合成和循环。

--系列组成:
>==<●F b c->F a b->F a c--平行构图:
>+<:F i1 o1->F i2 o2->F(i1 i2中的任意一种)(o1 o2中的任意一种)>*<●F i o->F i o->F i o列表F::(等式t)=>[(t,F i o)]->F(t,i)(t,o)--循环:
套圈●法航->法航循环左F:F(任意一个循环输入)(任意一个循环输出)->F输入输出环通滤波器:F(或oldo newi)(或oldi newo)->旧版->新的,新的
不同的fudget组合子处理不同的方法,而在所有的组合子都是一样的。图22说明福吉的串并联组合。

图22.福吉特的串行和并行组成。

除了上面列出的管道组合,福吉特图书馆包含更多捕获共同模式的组合子。一些下面几节将介绍这些组合词。

fudget组合子有对应的平面组合子流处理器,在中有更详细的讨论第十七章。通过替换F后缀带有服务提供商,或替换-...-对于>...<在操作员中。

13.1系列组成

串行合成将一个fudget的输出连接到另一个傻瓜的输入。与函数组合一样,数据流从右到左,这样在构图中复旦大学2>==<复旦大学1,的输出复旦大学1连接到的输入复旦大学2.

中的许多示例第九章包含形式的连续组成部分

mapF公司f>==<复旦大学
复旦大学>==<mapFf
库提供以下组合符来捕获这些案例:

>=^<::Fa b->(c->a)->F断路器fud>=^<f=fud>==<mapF>^=<::(a->b)->Fc a->F断路器f>^=<fud=mapF>==<fud
(图书馆版本>^=<>=^<更多相关定义以提高效率。)

形式构成

absF公司服务提供商>==<复旦大学
复旦大学>==<absF服务提供商
也很常见。库为此提供了两个运算符特殊情况:

>^^=<::SP b c->F a b->F a csp>^=<fud=absF sp>==<fud>=^ ^<::F b c->SP a b->F a cfud>=^^<sp=fud>==<absF sp
一些组合,比如波普梅努夫(参见第10.4条),创造出福吉特的平行作文,但有时是连载作文相反,合成是必需的。这可以通过用一个循环和一个抽象的fudget来做必要的路由,但是这个库包含两个组合子:

服务完成度::F(或者a和b)(或者b c)->Fa cserCompRightToLeftF公司::F(或者a和b)(或者c a)->F卑诗省
以下方程式成立:
serCompRightToLeftF公司(>+<r)=>==<r
服务完成度(>+<r)=r>==<

13.2平行成分

当组合两个或三个以上的fudget时,得到的标签通过使用>+<会变得有点笨拙。可能是这样使用更方便列表F,

列表F:(公式a)=>[(a,Fb c)]->F(a,b)(a,c)
允许任何类型的情商类用作福吉夫妇的地址要合并。限制是混合起来的福吉一定是同一类型的。(参见第40.4条讨论一门语言依赖类型可以消除这种限制。)

还有一个组合子无标记平行成分:

>*<●F i o->F i o->F i o
输入到未标记的并行合成发送到二者都争论含糊其辞。

还有一个列表版的未标记的平行构图,

未标记的:: [Fa b]]>F甲b
可以使用>*<:

untagedlistf=foldr(>*<)nullF
哪里空的,

空的::F甲b
是一个忽略所有输入而从不产生任何输入的错误输出。

未标记的平行构图不如有标记的。原因可能是你通常不想在一篇作文中要广播给所有的福吉。

还有一些更进一步的组合偶尔有用。这些是各种平行的具有福吉特身份的作品:

伊德里特夫::Fa b->F(或者a c)(或者b和c)空转::Fa b->F(或者c a)(或者c b)旁路::Fa->Fa a a通过::Fa b->Fa(或者博雅)idRightF fud=fud>+<idFidLeftF fud=idF>+<fud绕过fud=idF>*<fud通过hf fud=idRightF fud>==<toBothF托博思::Fa(或者一个a)toBothF=concatMapF(\x->[左x,右x])以色列国防军::Fa a aidF=地图id

13.3回路

最简单的循环组合器是套圈,

套圈::Fa->Fa a a
在构图中套圈复旦大学,输出来自复旦大学不仅仅是输出从构图,还发送回输入复旦大学.

最有用的循环组合器可能是环通滤波器。示例用法如中所示第9.7条并在中进一步讨论第18.2条.

一些有用的循环组合器有:

loopCompthroughtF::F(a b或c)(任一c d)a)->F b d循环编译ftf::F(a(或b c))(b(或a d))->F c d
它们把平行的构图变成循环。以下内容等式成立:
循环压缩hrightf(>+<r)=环通滤波器 r
循环编译(>+<r)=环通滤波器r

13.4动态fudget创建

可以使用前面章节中描述的组合词建造静止的胡说八道的网络。福吉特图书馆也是提供可用于添加或删除fudget的组合符动态(例如,动态创建新窗口)。

创造动态变化的福吉特平行组合,图书馆提供

动态::F(内景,DynFMsg公司a b)(内景,b)
哪里
数据DynFMsg i o=动态创建(F i o)| dyndestory | DynMsg i
上面我们看到了列表F创建标记的平行线静态的合成。组合子动态可以被视为列表F用更精细的输入消息类型。当程序启动时,动态空的平行作文。一个新的傻瓜复旦大学带地址通过传递消息
(,动态创建复旦大学)
动态有地址的笨蛋可以是通过传递消息从并行组合中删除
(,戴德斯特罗)
最后,一个人可以发送信息对一个现有的蠢货地址通过传递信息
(,DynMsg公司)
动态.

(地址由动态被限制在类型内景出于效率考虑,但原则上,可以支持更一般的地址类型,例如列表F.)

一个更简单的组合子,它允许fudgets动态变化达因:

达因::Fa b->F(或者(Fa b)a)b)b
笨蛋达因复旦大学开始表现得像复旦大学,除了给复旦大学应标记为赖特.笨蛋复旦大学可以换成另一个傻瓜复旦大学通过传递消息复旦大学.

14个用于非GUI I/O的fudget

14.1标准I/O模糊

读取标准输入(通常是键盘)并写入到标准输出或标准错误流(屏幕),您可以使用fudgets:

stdinF::F字符串stdoutF::F字符串astderrF::F字符串a
的输出标准差字符是从程序的标准输入通道。出于效率考虑,你不会一次只得到一个字符,而是更大的块字符。如果您希望输入作为行流,则可以使用

inputLinesSP::SP字符串
它把块放在一起,然后在新行处把它们分开。

一个简单的例子就是从键盘复制文本的fudget所有字母都转换为大写的屏幕:

stdoutF>==<(映射toUpper>^=<stdinF)
它适用于吹牛者对字符串中的所有字符输出方式标准差然后将结果反馈给标准输出.

这是一个颠倒思路的骗局:

(stdoutF>=^<(((++“\n”).reverse))>=<(inputLinesSP>^=<stdinF)
组合子的先例和结合就是这样这些谎言可以写成:

stdoutF>==<map toUpper>^=<stdinFstdoutF>=^<(++“\n”)。反向>==<inputLinesSP>^^=<stdinF

14.2访问文件系统

下面的fudget允许你读文件,写文件和获取目录内容:

readFileF::F FilePath(FilePath,IOError字符串之一)writeFileF::F(FilePath,String)(FilePath,IOError())readDirF::F FilePath(FilePath,IOError[FilePath]之一)
这些服务器可以看作是一对一通信的服务器在请求和响应之间。为了方便起见与请求中的文件路径配对。回应包含错误消息或请求的结果。结果是文件的内容(读文件),一个目录列表(读目录),或()(书面文件).

14.3计时器

定时器fudget在一定延迟后产生输出和/或以固定的时间间隔。它的类型是

数据Tick=勾号timerF::F(可能(Int,Int))勾选
计时器最初是空闲的。当它收到只是(,d)在输入时,它开始滴答作响。这个第一个勾号将在d毫秒。然后,滴答声会定期出现在毫秒间隔,除非是0,在这种情况下只有一个勾号将被输出。发送没有什么定时器复位到空闲状态。

作为一个简单的例子,这里有一个fudget,它输出第二,从那以后经过的秒数激活:

countSP>^^=<timerF>=^^<putSP(仅(10001000))nullSP哪里countSP=mapAccumlSP inc 0公司inc n Tick=(n+1,n+1)

15个定制参数

在构建软件库时,可能会有一个张力在简单和一般之间。一般性可以通过提供许多参数以使库组件适应不同的需求。但是如果程序员不得不这样做,它会破坏简单性每次库组件都指定大量参数被使用。为了解决这个问题,一些编程语言允许函数调用中要省略的参数,前提是在函数中为它们指定默认值定义。Haskell不允许这样做,但是通过使用函数语言、高阶函数和Haskell类系统,可以实现非常相似的东西。这个Fudget库中使用的解决方案如下所示。设计和中对实现问题进行了更详细的讨论第三十章.

15.1客户

为了让fudgets在它们通常有两种版本:a例如,标准版按钮,和例如,可定制版本按钮F'.公司名称可自定义版本通过附加'标准版本的名称。

可定制的fudget有许多参数允许比如字体、颜色、边框宽度等明确规定。所有这些参数都有默认值在fudget的标准版本中使用。

而不是每个参数都有一个额外的参数,fudgets的可定制版本(或其他功能)有一个额外的参数客户。客户是总是第一个论点。客户是一个功能修改包含所有参数。

类型 客户a=a->a
数据结构的类型是抽象的。它的名字通常是福吉的名字,第一个字母改成大写--例如,按钮在下列情况下按钮F'.
按钮F':(图a)=>(客户机(按钮a))->a->F单击
因此,客户是通过组合一些修改使用普通函数组合的函数。功能标准,
标准::客户
充当身份定制者,不更改任何参数。fudgets的标准版本是应用于的可自定义版本标准,例如:

buttonF=标准按钮

15.2样品客户

大多数福吉都有可定制的版本在本章前面。

许多傻瓜所共有的客户都超载了。中显示了一些自定义类图23.

HASBG颜色规格a哪里setBgColorSpec::ColorSpec->客户机aHasFgColorSpec a系列哪里setFgColorSpec::ColorSpec->客户机a哈斯字体a哪里setFont::FontName->客户机a海斯马利a哪里setMargin::Int->客户机aHasAlign公司哪里setAlign::Alignment->客户机a钥匙a哪里设置键::[(ModState,KeySym)]->客户a...

图23.一些定制类。

那张桌子图24显示客户Fudget库的当前版本。

BgColorSpec公司FgColorSpec公司字体保证金排列钥匙
文本F是的是的是的是的是的n
显示器是的是的是的是的是的n
是的是的是的nnn
按钮是的是的是的nn是的
切换按钮nn是的nn是的
放射组nn是的nnn
贝壳nnn是的nn

图24.一些客户机实例。

一些傻瓜也有不超载的客户,因为例子:

设置初始化显示*a->客户(显示器(一)--更改最初显示的内容

设置允许字符::(字符->布尔值)->客户 
--更改允许的字符

定位仪::砂矿->客户 放射组
--更改按钮的位置
作为使用定制的一个例子,图25显示中所示的收音机组的变体图10.

RadioGroup F'(设置字体“固定”。设置放置器(matrixP 2))[(1,“P1”),(2,“P2”),(3,“P3”),(0,“关”)]0

图25.中无线电组的自定义版本图10.

III流处理器——福吉特的精髓

本文所述工作的出发点是把愚人节看作是一个和其他蠢货交流的过程通过高级流和I/O系统低层流。因此,fudget有两个输入流,而不是预先知道两个流中元素的顺序变得可用。福吉应该能听高层次的投入还是低层次的投入,还要选择反应第一个可用的输入,不管它是什么流我们原以为前一个案子是例外和后一种情况将是规则,所以为不确定的选择提供一些运算符程序员可以在fudgets的定义中使用,我们选择在将高水位和低水位水流输送到这样就把不确定的选择转移到fudget之外。

所以,我们一开始以为福吉是原始概念,但很快把它们看作是从一个更简单的概念流动处理器,这是一个与通过一个输入流和一个输出溪流。

本文的这一部分致力于流处理器。

16个流处理器

我们没有在示例中广泛使用流处理器到目前为止,但是普通流处理器对至少有以下原因:

从更一般的上下文来看,流处理器可以被视为过程概念的简单但实际的体现,过程代数如CCS[密尔80].流处理器的一个优点是它们承认实施在内部纯功能语言。我们可以定义一组用于构建流网络的组合子处理器,流处理器是一级值,可以作为消息传递。

我们使用以下非正式定义:

这些定义允许流处理器有许多输入和输出流,但在下面我们只考虑具有一个输入流和一个输出流(请参见图26).

图26.通用流处理器和具有单个输入流和单个输出流。

这个限制看起来很严格,但是组合器允许合并和分割流,因此流具有多个输入/输出流的处理器可以表示为一个只有一个输入流和一个输出流。这个优点是我们可以采用基于组合的方法建立通信流处理器网络。这个在中进一步讨论了组合子第十七章在我们下面讨论如何写作原子的流处理器,也就是说,不由多个并发的流处理器组成正在运行流处理器。它们的行为是由一个线性的I/O操作的顺序。

16.1流处理器类型

福吉特图书馆提供流处理器的抽象类型,

数据SP输入输出
哪里输入输出是指输入流和输出流中的元素(图27). (流处理器在惰性函数语言在第二十章.)

图27.类型为服务提供商 o.

该库还提供该功能

运行SP::SP io->[i]->[o]
它可以在用流处理器(请参见第十九章). 功能absF公司在中讨论第十二章可以是用于将流处理器与fudgets相结合。

16.2延续式原子流处理器

原子流处理器的行为由顺序程序。一个流有三个基本动作处理器可以:
  • 它可以在输出流中输入一个值,
  • 它可以从输入流中获取值,
  • 它可以终止。
Fudget库提供以下延续样式这些操作的操作:

putSP::输出->SP输入输出->SP输入输出getSP::(输入->SP输入输出)->SP输入输出nullSP::SP输入输出
作为如何在递归定义中使用这些流处理器,考虑身份流处理器

--身份流处理器idSP::SP aidSP=getSP$\x->putSP x idSP
忙流处理器

--永远忙于计算的流处理器。业务支持:SP a bbusySP=忙
和下面的流处理器等价物列表函数:

mapSP::(a->b)->SP a bmapSP f=getSP$\x->putSP(f x)$mapSP f过滤器SP::(a->布尔)->SP afilterSP p=getSP$\x->如果p x然后putSP x$filterSP页其他的过滤器p
流处理器空SP实际上不需要被认为是原始的。它可以定义为

nullSP=getSP$\x->nullSP
i、 它是一个流处理器,它忽略所有的输入,而且从不产生任何输出。一种实用的优势代表空SP它允许流处理器终止为“垃圾回收”。
例子:
实施concatMapSP::(i->[o])->SP i o.
解决方案:
首先我们定义putListSP公司输出元素一次一个列表:

putListSP::[o]->SP IO->SP IOputListSP[]=idputListSP(x:xs)=putSP x.putListSP xs
以及concatMapSP公司本身:

concatMapSP f公司=获取SP$\x->putListSP(f x)$concatMapSP f公司
例子:
实施mapFilterSP::(i->Maybe o)->spi o.
解决方案:
映射过滤器=获取SP$\x->案例f x属于无->mapFilterSP f只是y->putspy$映射过滤器

16.3封装状态的流处理器

流处理器可以保持内部状态。在实践中,这可以通过在递归定义的流处理器。作为一个具体的例子,考虑萨姆普,一个流处理器,用于计算其输入流的总和:

sumSP::Int->SP Int IntsumSP acc=getSP$\n->putSP(acc+n)$sumSP(acc+n)
在这种情况下,内部状态是类型内景,它也是输入的类型和输出流。一般来说,输入和输出的类型流可以不同于内部状态的类型,然后就可以完全隐藏起来了。

Fudget库为构造具有内部状态的流处理器:

mapAccumlSP::(s->i->(s,o))->s->SP i oconcatMapAccumlSP::(s->i->(s,[o]))->s->SP i o
(concatMapAccumlSP也称为地图状态.)这些函数的第一个参数是状态转换提供当前状态和输入消息的函数应该产生一个新的状态和一个输出消息(零或更多在以下情况下的输出concatMapAccumlSP). 使用mapAccumlSP我们可以定义萨姆普不使用显式递归:

sumSP::Int->SP Int IntsumSP=mapAccumlSP(\acc n->(acc+n,acc+n))
将状态信息表示为一个或多个累积的当流处理器的行为是与国家有关的制服。如果流处理器做出反应与输入不同,根据其当前状态,可以使用一组相互递归的流更方便每个流处理器对应于有限状态自动机。作为一个简单的例子,考虑一个流输出其输入流中每个其他元素的处理器:

passOnSP=getSP$\x->putSP x$skipSPskipSP=getSP$\x->passOnSP
它有两种状态:“pass-on”状态,下一个输入是传递到输出端;以及“跳过”状态,下一个跳过输入。

上述两种表示状态的方法课程合并。

例子:
实施mapAccumlSPconcatMapAccumlSP使用putSP公司getSP公司.
解决方案:
concatMapAccumlSP::(s->i->(s,[o]))->s->SP i oconcatMapAccumlSP f s0=获取SP$\x->(s,ys)=f s0 x在里面putListSP ys公司$concatMapAccumlSP f smapAccumlSP::(s->i->(s,o))->s->SP i omapAccumlSP f s0系列=获取SP$\x->(s,y)=f s0 x在里面腐败$mapAccumlSP f s

16.4流处理器的顺序组成

不像CCS风格的过程代数[密尔80]--不平凡的地方顺序行为只能通过在具有I/O操作的现有行为——流处理器可以按顺序组合:

顺序SP::SP a b->SP a b->SP a b
流处理器服务提供商1`序列号`服务提供商2表现得像服务提供商1直到服务提供商1变成空SP,然后表现得像服务提供商2但是,也可以通过过程以调用连续流处理器结束而不是空SP; 所以序列号不添加任何新的权力。

我们还应该注意到,如果要正常工作操作空SP必须显式表示,而不是只是定义为忽略所有输入和从不产生任何输出;第16.2条.

16.5流处理器单体

到目前为止的演示表明,原子流处理器应以连续样式编程。这是经常发生的很自然,但对于复杂的流处理器来说,这是可能的使用一元的风格是有益的[瓦德92,95瓦].两者风格兼容。流处理器的操作monad如所示图28.

--类型:
类型SPm输入输出应答--标准单子运算:单位SPm::a->SPm i o abindSPm::SPm i o a->(a->SPm i o b)->SPm i o b--nullSP、putSP和getSP的一元版本:空SPm::SPm i o()putSPm::o->SPm i o()获取SPm::SPm i o i--胶水功能:运行SPm::SPm i o()->spi o

图28.流处理器monad。

多亏了运行SPM你可以用组合词``普通的“”流处理器来构造流处理器单体。

对于编写复杂的流处理器,这当然是可能的将流处理器monad与其他monad组合,例如国家monad。Fudget库定义了类型SPms公司对于具有状态的流处理器monad。更近距离的演示它的一个使用示例可以作为第三十一章.

17管道:合成流处理器

本节介绍用于组合原子的组合子流处理器进入通信流网络处理器。我们首先描述三个基本的组合子作文:串联作文、平行作文和作文循环。

17.1系列组成

最简单的组合子是序列组合的组合子,

(==-)::SP b c->SP a b->SP a c
它将一个流处理器的输出流连接到另一个的输入流,如中所示图29。溪流从右向左流动,就像函数组合中的值,f.g.

图29.流处理器的串行组成。

流处理器的串行组合非常接近功能组成。例如,它遵循以下法律:

mapSP公司f-==-mapSPg=mapSP公司(f.g)

17.2平行成分

并行组合中的组合子图30确实是流处理器的关键组合器。它允许我们编写由或多或少的独立的,并行的过程。输出流应该是按时间顺序合并。我们将无法实现在函数式语言中确实如此,但对于stream其行为受I/O操作控制的处理器而不是内部计算,我们将足够接近实用目的。

图30.流处理器的并行组合。

然而,并行的定义可能不止一个组成。输入流中的值应该如何分配给两个流处理器?应该怎么做是否合并输出流?我们定义了两个版本:

  • 服务提供商1-*-服务提供商2表示输入的并行合成值将传播到这两个服务提供商1服务提供商2,输出为按时间顺序合并。我们称之为这个版本无标记,或广播平行构图。
  • 服务提供商1-+-服务提供商2表示平行合成,其中输入流和输出流的值是不相交并。已标记的输入流中的值赖特未标记并发送到服务提供商1服务提供商2,分别。同样,输出中值的标记流指示它来自哪个组件。我们会打电话的这个版本标记平行构图。
这两种组合词的类型是:

(-*)::SP i o->SP i o->SP i o(+-)::SP i1 o1->SP i2 o2->SP(i1 i2之一)(o1 o2之一)
请注意,只有其中一个需要被视为原始的。另一个可以定义为原始的一个,借助于连续剧和一些简单的流处理器mapSP公司过滤器.
例子:
定义-*-依据-+-,和恶习反之亦然。
解决方案:
(-*)::SP i o->SP i o->SP i osp1-*-sp2=mapSP条纹-==-(sp1-+-sp2)-==-托博思stripponer::a->a剥离任一(左a)=a剥离任一(右a)=atoBothSP::SP a(a a或a)toBothSP=concatMapSP(\x->[左x,右x])(+-)::SP i1 o1->SP i2 o2->SP(i1 i2之一)(o1 o2之一)sp1-+-sp2=sp1'-*-sp2'哪里sp1'=mapSP左-==-sp1-==-filterLeftSPsp2'=mapSP右-==-sp2-==-filterRightSPfilterletsp=mapFilterSP stripeleftfilterRightSP=映射过滤器sp stripRightstripLeft::要么是b->也许是astripLeft(左x)=仅xstripeleft(Right Uu)=无stripRight::要么是b->也许是bstripRight(Left Uu)=无stripRight(右y)=仅y

17.3圆形连接

串行组合创建单向通信两个流处理器之间的通道。平行构图拆分和合并流,但不允许组合流处理器来交换信息。所以,用这些两个操作员我们无法获得双向通信流处理器之间。因此,我们介绍构造循环的组合子。

最简单的循环组合器连接流处理器对其输入的输出,如中所示图31。与并行合成一样,我们定义两个循环组合器的版本:

图31.一个简单的循环构造函数。

环路服务提供商,
输出来自服务提供商两者都是循环到的输入服务提供商并传播到输出,在循环外。
环左SP服务提供商,
输出来自服务提供商必须在不相交的结合。标记的值循环和值标记赖特是输出。在输入端,来自循环已标记外部的价值观是标记赖特.
这些组合词的类型有:

环路*SP a->SP a环左SP●SP(任一l i)(任一l o)->SP i o
两个循环组合子中的每一个都可以定义为另一个,所以只需要考虑其中一个原始的。

使用一个循环组合子,现在可以获得两个流处理器之间的双向通信显示在图32.

图32.使用循环获得双向通信。

另一个例子说明我们可以使用循环和并行组合以创建完全连接的流网络处理器。用一种像

环路(服务提供商1-*-服务提供商2-*- ... -*-服务提供商n)
我们有一个广播网。通过替换-*-具有-+-加上标记/取消标记,我们得到了一个点对点通信。
例子:
定义环路依据环左SP反之亦然。
解决方案:
定义环路依据环左SP相对容易:

循环SP::SP a->SP a环路sp=环左SP(toBothSP-==-sp-=-mapSP条带之一)
反之则有点棘手:

loopLeftSP::SP(或l i)(或l o)->SP i o环左sp sp=mapFilterSP帖子-==-环路sp'-==-mapSP右哪里后置(左(右x))=仅x发布=无sp'=mapSP Left-==-sp-==-mapFilterSP pre哪里pre(右x)=刚好(右x)pre(Left(Left x))=刚好(Left x)pre=无

管道工程的18个实用方面

看过一组基本的流处理器组合器我们可以把它看作是一个完整的基元集合可以构建更多的组合子——我们现在来看看组合子可以用来实现一些共同的连接并介绍了我们发现的一些进一步的组合子有用的。

福吉特的构图方式与平原溪流相同处理器。因此,流处理器的描述组合子也适用于相应的fudget组合学家。fudget组合子是按名字命名的,再加上一些其他的组合子,在第十三章.

18.1处理多个输入和输出流

虽然流处理器只有一个输入流,但是很容易构造一个流处理器的程序从两个或多个其他流处理器接收输入。(在有多个输出的情况类似。)例如表达

服务提供商1-==- (服务提供商2-+-服务提供商)
允许服务提供商1从两者接收输入服务提供商2服务提供商.为了最实际的目的,服务提供商1可以是被认为有两个输入流,如图33。当你使用getSP公司在里面服务提供商1阅读来自输入流的消息服务提供商2服务提供商将显示标记为赖特,分别。你不能直接从一个有选择地读但Fudget库提供组合子

图33.处理多个输入流。

waitForSP::(i->Maybe i')->(i'->spi o)->SP i o
可用于等待选定的输入。其他输入已排队,并且可以在选定的输入已收到。使用waitForSP公司你可以定义组合子来从两个输入流之一读取:

getLeftSP::(i1->SP(i1 i2)o)->SP(i1 i2中的任意一个)ogetLeftSP=waitForSP stripeleftgetRightSP::(i2->SP(i1 i2)o)->SP(i1 i2中的任意一个)ogetRightSP=waitForSP strippright
例子:
实施启动SP::[i]->SP i o->SP i o在流的输入流中预先添加一些元素处理器。
解决方案:
启动sp xs sp=sp-==-putListSP xs idSP
注意:这个实现留下了一个串行组合idSP系统在留言后X轴曾经喂给服务提供商。一个有效的实现不留任何开销可以通过使用流处理器的实际表示。
例子:
实施waitForSP公司如上所述。
解决方案:
waitForSP::(i->Maybe i')->(i'->spi o)->SP i owaitForSP需要isp=contSP挂起=getSP$\msg->案例需要消息属于只需应答->startupSP(反向挂起)(isp应答)无->contSP(消息:待定)在里面contSP[]

18.2流处理器和软件重用

对于严肃的应用程序编程,它是有用的拥有可重用软件组件库。但是在在许多情况下,当在库中找到有用的组件时,它还需要修改才能使用。

循环组合子的一种变体在重用流处理器时非常有用环通hrightsp,如中所示图34.钥匙区别于环路环左SP是这样吗循环不会直接从输出返回到单流处理器的输入。相反,它去了通过另一个流处理器。

图34.封装。

典型的情况是环通hrightsp是有用的如果你有一个流处理器,服务提供商古老的,确实如此几乎是你想要它做的,但你需要它来处理一些新的信息。一个新的流处理器,服务提供商新的,可以然后定义。这种新的流处理器可以继承旧的直接发送给服务提供商古老的处理新消息适当的方式;或者把它们翻译成消息服务提供商古老的明白。(另请参见中的第3.1.1节[94尼泊尔卢比].)

在构图中环通hrightsp服务提供商新的 服务提供商古老的,全部与外界的沟通由服务提供商新的.服务提供商古老的仅连接到服务提供商新的,在这个意义上封装在内部服务提供商新的.

类型环通hrightsp是:

循环通入hrightsp::SP(oldo newi)(oldi newo)->SP oldi oldo->斯普尼尼诺沃
编程使用环通hrightsp对应于继承在面向对象编程中。封装的流处理器对应于继承的类。重写的方法对应于封装流处理器处理程序本身。
例子:
实施环通hrightsp使用环左SP以及并行和串行组合视情况而定。
解决方案:
环通hrightsp::SP(或oldo i)(或oldi o)->SP oldi oldo->SP i o环通hrightsp spnew spold=环左SP(mapSP post-==-(spold-+-spnew)-==-mapSP预处理)哪里pre(右输入)=右(右输入)pre(Left(Left newToOld))=左newToOldpre(Left(Right oldToNew))=右(Left oldToNew)post(Right(Right output))=右输出后置(右(Left newToOld))=左(Left newToOld)post(Left oldToNew)=左(Right oldToNew)
例子:
使用带标记的并行程序实现串行组合构图和循环。
解决方案:
(==-)::SP b c->SP a b->SP a csp1-==-sp2=环通hrightsp(mapSP路由)(sp1-+-sp2)哪里路线(右a)=左(右a)路线(左(左c))=右c路线(左(右b))=左(左b)
组合子循环BothSP,

循环通过BothSp::SP(l12 i1之一)(l21 o1之一)->SP(l21 i2或l12 o2)->SP(i1或i2)(或o1 o2)
是的对称版本环通hrightsp.一篇作文循环BothSP服务提供商1 服务提供商2两者都允许服务提供商1服务提供商2与外界沟通世界和彼此(见图35).

图35.电路图循环BothSP.

有趣的性质循环BothSP更基本的组合子的电路图,-==-,-+-环路,可以是从电路图中获得循环BothSP只需拆下电线。其他的组合子因此很容易定义循环BothSP.

18.3动态过程创建

我们隐式地区分了定义原子流处理器的动态行为(空SP,putSP公司getSP公司)还有运营商用于构建流处理器的静态网络(-==-,-*-,环路等等)。但是事实上,没有理由说网络必须是静态的。通过使用组合词-==--*-在一个动态方式下,可以使流处理器的数量动态增加。流处理器的数量可以也可以减少,例如如果合成模具(自空sp-*-sp相当于服务提供商).

文中讨论了这些思想的实际应用第35.4条.

19使用纯流处理器的应用程序编程

虽然普通流处理器大多是结合使用的和福吉一起,他们可以独立使用。在这个第二章,我们来看看交互式Haskell的一些例子使用流处理器编写的程序。

19.1加法器

第16.3条我们定义了

sumSP::Int->SP Int Int
计算流的累积和整数。让我们编写一个完整的Haskell程序萨姆普实现一个简单的加法机。

Haskell提供函数互动,允许类型函数[字符]->[字符]用作程序(如Landin的流I/O模型概述第四章). 把这个和函数结合起来运行SP,

运行SP::SP io->[i]->[o]
(来自第16.1条)我们可以运行类型为服务提供商字符字符:

main=交互(runSP mainSP)主SP::SP Char Char主SP=。。。
能够使用萨姆普我们只需要加些胶水将字符输入流转换为数字流,反之亦然。这个分两个阶段完成。首先,流处理器标准列表函数的等价物线未连接线用于逐行处理输入和输出,而不是逐个字符:

mainSP=unlinesSP-==-加法器p-==-linesSPadderSP::SP字符串加法器P=。。。
现在标准功能显示阅读用于在字符串和数字之间转换,

adderSP=mapSP show-==-sumSP 0-=-mapSP读取
程序完成了。
例子:
实施unlinesSP::SP String Char.
解决方案:
unlinesSP=concatMapSP(\s->s++“\n”)
例子:
实施linesSP::SP字符字符串
解决方案:
linesSP=lnSP[]哪里lnSP科目=getSP$\msg->案例消息属于'\n'->putSP(反向acc)(lnSP[])c->lnSP(c:acc)

19.2用于输入行编辑的流处理器

在上面的例子中,假设输入是行缓冲的(Unix中的熟终端模式),即系统允许用户输入一行文本并使用backspace键(可能还有其他光标移动键)并发送按回车键进入程序。系统是因此负责回显键盘上输入的字符,到屏幕上(图36).

图36线路缓冲输入。

假设一个更简单的系统,输入键盘直接到程序中,并且只有字符显示在屏幕是程序输出的(原始终端模式Unix系统)(图37),流处理器组合子线路缓冲区现在定义为执行以下操作:

图37.无缓冲输入。

lineBufferSP::SP String Char->SP Char Char
它需要一个流处理器,它需要输入进行缓存,并返回一个流处理器对输入进行必要的处理:缓冲,回音等,这样它就可以在无缓冲区工作环境。

我们实施线路缓冲区使用环通hrightsp:

lineBufferSP progsp=环通hrightsp bufSP progsp哪里bufSP::SP(任意字符字符)(任意字符串字符)bufSP=。。。
我们得到了图中所示的连通性图38,即。,bufSP公司将在其输入上接收程序输出和键盘输入流,并应在其上生成输入行和屏幕输出输出流。

图38.电路图线路缓冲区.

实施bufSP公司如所示图39.

bufSP=输入sp“”inputSP line=getSP$从progsp到keyboard哪里fromProgsp c=putSP(到屏幕c)(inputSP行)从键盘c=案例c属于
        --在进入密钥:'\n'->putSP(到屏幕'\n')$putSP(toProgsp(反向线路))$bufSP公司--在退格密钥:'\b'->如果空行然后输入SP线其他的PUTSP(映射到屏幕“\b\b”)$输入SP(尾线)--可打印字符:_->putSP(到屏幕c)$输入SP(c:线)toScreen=右toProgsp=左

图39。bufSP公司-的核心线路缓冲区.

使用线路缓冲区,上一个中的加法器通过改变,部分可以适应在原始终端模式下运行主SP收件人:

mainSP=lineBufferSP(unlinesSP-==-adderSP)

19.3在拆分屏幕上并行运行两个程序

最后一个例子是分割终端的组合子屏幕进入两个窗口并并行运行两个程序,一个在每个窗口中:

splitViewSP::SP Char->SP Char->SP Char Char
一个简单的实现拆分视图SP结构可以是跟随:

splitViewSP sp1 sp2=mergeSP-==-(sp1-+-sp2)-==-Distsp哪里distrSP::SP Char(任意一个Char Char)Distrisp=。。。mergeSP::SP(任意一个Char Char)CharmergeSP=。。。
分配获取键盘输入并将其发送到两扇窗户。用户可以通过按指定密钥。

合并从windows获取两个输出流并生成一个合并流,其中包含光标控制使文本显示在屏幕。这可以通过不同的方式完成,具体取决于终端特性。如果滚动是不需要,是将处理分为两个步骤:第一步解释两个窗口的输出流单独跟踪当前光标位置使用流处理器

trackCursorSP::SP Char((Int,Int),Char)
它接受一个包含混合可打印的字符流字符和光标控制字符,以及生成一个具有对光标位置和可打印字符。下一步是将两者合并流,并将其输入流处理器,以生成终端的相应光标运动命令:

encodeCursorMotionSP::SP((Int,Int),Char)Char
这样我们就有了

合并=编码器光标运动-==-mapSP条纹-==-(trackCursorSP-+-trackCursorSP)
使用上面概述的实现合并,我们获取中所示的电路图图40对于拆分视图SP服务提供商1 服务提供商2:

图40.电路图拆分视图SP服务提供商1 服务提供商2.

在上面的描述中我们忽略了一些细节,我们得到的实现如所示图41.

splitViewSP::(Int,Int)->SP Char->SP Char->SP Char CharsplitViewSP(宽,高)sp1 sp2=mergeSP-==-(sp1-+-sp2)-==-distrSP左右哪里mergeSP=编码器光标或运动SP-==-mapSP条纹-==-(轨迹光标SP(w,h1)-+-(mapSP movey-==-trackCursorSP(w,h2)))h1=(h-1)`div`2h2=h-1-h1移动((x,y),c)=((x,y+h1+1),c)配电盘dst1 dst2=getSP$\c->案例c属于'\t'->Distsp dst2 dst1_->putSP(dst1 c)$distrSP dst1 dst2trackCursorSP::(Int,Int)->SP Char((Int,Int),Char)trackCursorSP size=mapstateSP winpos(0,0)哪里winpos p c=(下一个PS p c,[(p,c)])encodeCursorMotionSP::SP((Int,Int),Char)CharencodeCursorMotionSP=mapstateSP项(-1,-1)哪里术语cur@(curx,cury)(p@(x,y),c)=(nextpos pc,move++[c])哪里移动=如果p==当前然后""其他的移动到p下一步::(Int,Int)->Char->(Int,Int)下一个p c=。。。--c打印后的光标位置移动到::(Int,Int)->字符串移动到(x,y)=。。。--生成适当的光标控制序列

图41.实现拆分视图SP.

四、设计与实施

在这一部分中,我们将描述Fudget库本身,以及我们的一些扩展完成。第一个组织章节可以用文字概括图书馆,扩展编程方法:
图书馆。
这些章节描述了福吉特图书馆背后的原则。第二十章讨论流处理器的不同实现。这个fudget组合子的实现是基于流的处理器,并允许它们与不同种类的I/O系统(第二十一章).第二十二章描写GUI背后的机制,异步I/O和X窗口的底层接口。

自动排样系统第二十三章可见作为组合子库,它不仅用于放置图形用户界面,还可以组成图形。

过滤器fudgets(修改福吉特的影响)第二十四章.缓存filter使fudget程序使用更少的内存运行得更快,并且焦点过滤器修改GUI的输入模型使用键盘。

扩展。
接下来的章节将描述我们但是,不要认为这对图书馆是绝对必要的它们中的一些位于库中,另一些则位于最少提示修改库以便工作。

流处理器和fudgets的一个显著特点是他们可以脱离他们原来的位置在程序,作为消息传递,并附加到另一个位置。第二十五章描述如何使用它来程序拖放应用程序,在这里GUI实际上拖动时在程序内部移动。

第二十六章展示了如何使用fudget概念用于编写客户机/服务器应用程序。服务器程序可以通常没有任何图形界面,但它是可以的有利于以并发方式编程服务器,以便他们可以同时为许多客户服务。

库包含的类型类具有图形化外观,它可以由用户。第二十七章呈现图解的类和它的实施。

编程方法。
这些章节描述了我们编程方法实验福吉特。第二十八章描述用于创建类似于解析的面向语法的编辑器组合子,和第二十九章展示了Haskell的课堂系统可以用来自动生成简单的gui。作为我们已经在前面的部分看到了,类系统也被用于编程使用具有默认值的参数。实现是描述第三十章.
最后,第三十一章描述Fudget库上的功能工具包小工具。这个包括流程概念的功能实现小工具,并允许将Gadget程序合并到福吉特。作为奖励,添加了一个分析工具,它提供消息队列的图形监视器。

20实现流处理器

在本章中,我们将介绍流的不同实现处理器,包括需要语言扩展为并行求值,两个纯功能性的:第一种是基于流的惰性列表,以及另一个使用一个数据类型,其构造函数与原子流处理器的操作我们也讨论如何适用于并行和顺序的不同表示实现。我们从讨论一些设计目标开始我已经想到了。

20.1流处理器的设计目标

流处理器的设计从一开始就已经开始了受作为构造块的预期应用程序的影响图形用户界面库。在这种情况下,我们发现了以下属性重要提示:
层次结构。
合成的结果流处理器也应该是流处理器,因此允许在层次结构。原子流处理器和由几个较小的流处理器组成。
封装状态。
我们应该允许每条小溪处理器的内部状态从在外面,而这并不妨碍其他流处理器。
I/O连接。
应该可以连接以抽象的方式将处理器流到I/O系统,这样I/O效果可以被抽象类型隐藏与相关的组合子进行组合。
反应性行为。
流处理器的预期用途是在实施交互式(反应式)时程序。这意味着程序由通信而不是计算:一个程序无所事事地等待对于要到达的某些输入,计算并输出对的响应输入,然后返回空闲状态。
需求驱动评估。
目的是使用stream函数式语言中的处理器,其中表达式按需评估。流处理器也应该表现得懒惰——他们不应该做任何工作,除非有价值从它们的输出流中被要求,而它们不应该从其输入流中请求任何内容,除非输入可以用于产生所需的输出。
打字的,更高的顺序。
不应限制流的元素类型。应该可以转移任何东西,从数字和布尔值到函数和流处理器。通信应该是类型安全的。
并行和顺序实现。
流处理器应该可以用顺序语言实现,但是我们仍然想保持定义的通用性利用不确定性的结构选择和平行评估。
尤其是考虑到最后一处房产,似乎希望有一个抽象的,形式化的语义用于解释流的不同实现处理器,以及使用它们的程序。到目前为止,我们还没有阐述了这样一个语义,但是我们却集中在通过开发Fudget图书馆和应用程序。而且,实施的基本原则流处理器组合器非常简单,因此可以被看作是一个语义本身——尽管不是最简洁、最抽象的。尽管如此,我们概述了一个简单的流处理器演算后续工作章节中的伴随操作语义(参见第43.1条).

20.2直觉想法——问题是什么?

在懒惰的函数式语言中,一个自然的选择是表示流作为列表。多亏了懒惰可按需计算,一次计算一个元素。元素因此可以在时间上形成一个序列而不是在空间,严格来说就是这样。

所以一个包含类型元素的流可以表示为包含类型元素的列表。流处理器可以是表示为从输入流到输出的函数溪流:

类型a流=[a]类型SP i o=流i->流o
我们称之为基于列表的代表性。显而易见的这种方法的优点是列表类型是一种标准类型类型和为列表提供的所有操作在定义流处理器。

这种表示法的另一个优点是它清楚地显示函数与流之间的密切关系处理器。例如,串行组合就是简单的函数成分:

(==-)::SP m o->SP i m->SP i osp1-==-sp2=sp1。sp2
流处理器的基本操作也非常简单定义:

nullSP=\xs->[]x`putSP`sp=\xs->x:sp xsgetSP isp=\xs->案例X轴属于[]     -> []x: xs'->isp x xs'
然而,这种表示法的一个问题是这种平行性合成是不可能实现的。合理的定义必须看起来像这样:

sp1-*-sp2=\xs->合并(sp1 xs)(sp2 xs)哪里合并ys zs=???
但该怎么办???被替换为构图的输出是可从其中一个组件获得?例如,假设

服务提供商1_|_ = _|_服务提供商2_|_=1:_|_
也就是说,服务提供商1需要一些输入才能产生一些输出,但是服务提供商2can输出1马上。然后,构图应该立即输出1,

(服务提供商1-*-服务提供商2)||=1:_|_
但是(服务提供商2-*-服务提供商1) _|_也应该是1号文件:_|_,所以???必须是选择下列之一之一的表达式ys公司zs公司刚好不是底部。这个罐子显然不能用普通的纯功能语言来完成。

作为一个更具体的例子,考虑一下如果应用流处理器

map(*100)-*-过滤偶数
至[1,2,3,4,…]。如果输入元素以较慢的速度出现它们的速率地图滤波器,的期望的输出流将类似于[100,200,2300,4400,…],也就是说,在这种特殊情况下,应该有两个来自左流处理器的元素正确的流处理器。

两个输出流中的元素应合并到随着输入元素的增加,它们变得可计算流可用。但是,没有办法告诉你两个流处理器中哪一个是顺序语言第一个能够产生输出的。似乎是两个流需要并行计算,然后是元素必须按可用的顺序进行选择。

解决这个问题最自然和最普遍的方法是使用并行评估,接下来我们将看一看这个。但是通过改变流处理器的表示可以得到在普通序列中工作的解语言。我们将在第20.4条.

20.3并行实现

如前一节所示,当流处理器作为列表函数,并行求值是需要,不是为了提高速度,而是因为没有顺序评估命令可以给出期望的结果。我们需要一个接线员开始并行计算两个子表达式,然后说明哪个评估首先完成。结果就是这样不是由表达式的值决定的,而是由他们的操作行为。因此,这样的操作员不能被添加到一个纯功能语言中,没有问题。

上面建议的运算符是amb公司,麦卡锡的矛盾算子[63立方厘米]. 但是有这样一个运算符的编程语言并不是纯粹的函数化,从而使普通的等式推理不健全。尽管这样的语言可能仍然有用[摩根士丹利94],有一些解决方案可以让您纯功能性的不确定选择。

在下一节中,我们将介绍amb公司完全是功能性的。

20.3.1神谕

要纯函数化,运算符的结果必须完全取决于参数的值,同样参数应该总是给出相同的结果。一种方法一个纯函数的不确定选择的算子是引入一个额外的论点,假装结果是完全由这个论点决定,尽管在操作上,发生了其他事情。这种额外的论点被称为神谕[伯克88].

我们叫我们的接线员做不确定的选择选择:

选择::Oracle->a->b->布尔
在操作上选择o b通过启动b并行然后返回是的如果先达到头部正常形态,然后如果b做。意指,选择o b退货是的仅取决于论甲骨文的价值o(奇迹般地发生在具有“right”值)。甲骨文只能使用一次,因为它必须总是给出相同的答案。因此,我们将一棵无限的神谕树分发给所有的溪流处理器,作为附加参数:

数据OracleTree=OracleNode OracleTree Oracle OracleTree类型SP IO=OracleTree->流i->流o
使用oracle树,我们现在可以轻松地实现并行流处理器的组成(另请参见图42):

sp1-*-sp2=\(OracleNode(OracleNode ot_U1)ot2)xs->合并ot(sp1 ot1 xs)(sp2 ot2 xs)哪里合并(OracleNode ot o\uUY)ys zs=如果选择o ys zs然后合并ys zs其他的合并'ot zs ys合并ot(y:ys)zs=y:合并ot ys zs合并'ot[]zs=zs
在这个实现中,oracle树被分成三个部分:两个子树被送入合成流处理器,其中一个是赋予功能合并,以及输出来自组合流处理器的流。功能合并从树上提取新鲜的神谕并用于选择看哪个溪流最先到达源头正常形式,然后调用合并',第二个参数是已计算的流。

图42.使用oracle的流处理器的并行组合。

20.4顺序实施

正如我们在上面看到的,最自然的代表流,即列表,需要并行评估和不确定的选择。但也有一些解决方案你要保持在一种纯功能语言中,比如哈斯克尔。虽然它们不能提供相同程度的在实际应用中,它们已经被证明是足够的使用。以下解决方案已用于福吉系统。

20.4.1合成神谕

如所见第20.2条,最自然的问题流处理器的表示流作为延迟列表,流处理器作为函数懒惰列表——是并行组合的实现。不可能知道输出流应该按哪个顺序合并。

如果我们施加限制服务提供商1服务提供商2必须生产以同样的速率输出,那么服务提供商1-*-服务提供商2可以定义为:

(sp1-*-sp2)xs=合并(sp1 xs)(sp2 xs)哪里合并(y:ys)(z:zs)=y:z:合并ys zs
然而,在输出之间施加这样的约束是很尴尬的两个不同流处理器的流。还有,这个解决方案不适用于标记的并行合成。A更有用的约束将单流处理器。
  • 我们施加了一种约束,即必须有一对一的关系输出和输入中元素之间的对应关系流,即流处理器必须在从输入消耗的每个元素的输出流溪流。
功能地图是一个满足这一点的例子约束,鉴于滤波器不是的函数。

有了这个限制,带标记的并行组合可以很容易地被实现:输出流中的下一个元素应该从上次接收到来自输入流的元素。以下是标记的并行组合通过合并使用流的输出流合成神谕计算的从输入流(另请参阅图43):

(+-)::SP a1 b1->SP a2 b2->SP(a1 a2之一)(b1 b2之一)(sp1-+-sp2)xs=合并操作系统(sp1 xs1)(sp2 xs2)哪里xs1=[x |左x<-xs]xs2=[y |右y<-xs]--os:一个合成的oracle流os=map isLeft xs合并(真:操作系统)(y:ys)zs=左y:合并os ys zs合并(False:os)ys(z:zs)=右z:合并os ys zsisLeft(左=True)isLeft(右=False)

图43.使用合成预言器的流处理器的并行组合。

然而,这个解决方案有一些实际问题。就这样站在上面,可能有严重的空间泄漏问题。考虑对表达式的求值,例如

(服务提供商1-+-服务提供商2)[左n | n<-[1..]]
在这里,服务提供商2不会接收任何输入。这意味着合并永远不需要评估论点(服务提供商2xs2),它包含对输入流通过xs2型。这将导致所有输入被要保留在堆。但是,前提是实现了模式绑定适当地[Spa93],这个问题可以通过计算来解决xs1型xs2型只有一个递归定义返回一对列表:

拆分::[a b]->([a],[b])拆分[]=([],[])拆分(x:xs)=案例属于左x1->(x1:xs1,xs2)右x2->(xs1,x2:xs2)哪里(xs1,xs2)=拆分xs
另一个问题是1-1限制相当严重。如果流处理器不想将使用输入后输出流中的值(如滤波器)? 如果它想输出多个值呢?显然,如果给定了具有此限制的实现对于一个程序员来说,他会发明各种各样的方法它。最好从开始。

解除限制的一个方法是改变流处理器的表示

类型SP a b=[a]->[[b]]
从而允许流处理器将值列表输出到为输入中的每个元素放入输出流溪流。不幸的是,用这种表示法列出函数,例如地图滤波器,不能以这种直接的方式使用更长时间。例如,而不是地图f必须使用地图(\x->[f x]).序列号组合不再只是函数组合,而是函数组合是更复杂,效率更低的东西。还有,是的仍然可以编写不遵守1-1限制,导致无法检测到的错误编译器。因此,揭示这一点并不是一个好主意向应用程序程序员表示,但是提供流处理器类型作为抽象类型。以及当我们使用抽象类型时,我们不妨使用更好的代表性。

20.4.2基于延续的表示法

Fudget库没有使用列表,而是使用了一个数据类型,构造函数与流中的操作相对应处理器可以采取(如第3.2节所述):

数据SP i o=空SP|PutSP o(SP i o)|GetSP(i->spi o)
我们称之为基于连续性代表流处理器。类型对每个类型都有一个构造函数流处理器可以执行的操作。施工人员具有作为操作一部分的参数(值输出输入PutSP公司),以及确定如何流处理器在操作被执行。

基于连续性的表示避免了我们在使用基于列表的表示,因为它使可观察的输入流。使用列表函数,流处理器应用于整个输入流一次全部。此列表中元素的消耗速率为从外面看不到的。以延续为基础表示,流处理器必须计算为GetSP公司服务提供商每次它想从输入流中读取值时。这个我们需要的是能够将输出流合并到平行构图定义中的正确顺序。

广播并行合成的一个实现是显示在图44标记并行的实现成分是类似的。请注意,我们任意选择检查左参数服务提供商1第一。这意味着即使服务提供商2计算和输出值的速度比服务提供商1,它将没有机会这样做。

空sp-*-sp2=sp2sp1-*-NullSP=sp1PutSP o sp1'-*-sp2=PutSP o(sp1'-*-sp2)sp1-*-PutSP o sp2'=PutSP o(sp1-*-sp2')GetSP xsp1-*-GetSP xsp2=GetSP(\i->xsp1 i-*-xsp2 i)

图44.并行组合的实现基于延续的表示法。

使用基于延续的表示法,串行组合可以如中所示实现图45.

NullSP-===-sp2=NullSPPutSP o sp1'-==-sp2=PutSP o(sp1'-==-sp2)GetSP xsp1-==-NullSP=NullSPGetSP xsp1-==-PutSP m sp2'=xsp1 m-==-sp2'GetSP xsp1-==-GetSP xsp2=GetSP(\i->GetSP xsp1-==-xsp2 i)

图45.使用基于延续的表示法。

循环组合子的一个定义环路如所示图46.

loopSP sp=loopSP'空sp哪里loopSP'q NullSP=NullSPloopSP'q(PutSP o sp')=PutSP o(loopSP'(输入q o)sp')loopSP'q(GetSP xsp)=案例q删除q属于只需(i,q')->loopSP'q'(xsp i)没有->GetSP(loopSP.xsp)--Fifo队列
数据排队空::队列aenter::队列a->a->队列aqremove::队列a->可能(a,队列a)

图46.实施环路基于延续的表示。

例子:
实施运行SP::SP a b->[a]->[b].
解决方案:
运行sp sp xs=案例服务提供商属于PutSP y sp'->y:runSP sp'xs获取SP xsp->案例X轴属于x:xs'->runSP(xsp x)xs'[]       -> []空SP->[]

20.5延续与列表功能

我们已经看到了流处理器的两种表示:一种基于列表函数和一个基于continuations。哪个一个更好?

使用列表函数很适合并行实现。需求从输出传播到输入流的正常评估机制功能语言。由于流是用列表表示的,因此标准列表函数可以直接用作流处理器。

对于顺序实现,我们看到了表示流的处理器作为函数从一个流到另一个流阻止了我们实现并行合成。这里,那个基于延续的表示法似乎更具吸引力,而且我们现在在福吉特图书馆使用的那个。这个基于延续的表示也允许流处理器被分离,移动,插入程序——用于第二十五章.

因为我们的实现是基于顺序编程的语言,我们不能得到真正的并发。只要所有的溪流处理器对输入做出快速反应,以避免阻塞其他流处理器在程序中,这在实践中是可以接受的。这个但是,编译器不强制执行反应性属性。

如果有一个能很好地为并行和顺序实现。也许是基于延续的表示法也适用于并行实现?考虑一下组成

(服务提供商1-*-idSP)-==-服务提供商2
组合的第一个输出应该是第一个输出来自服务提供商1或者第一个输出服务提供商2,以两者为准正好先准备好了。但平行构图必须评估为PutSP。。。,或获取SP。。。.英寸我们过早地承诺要接手的第一个案子的输出服务提供商1第一。在第二种情况下,我们不会能够在之后交付第一个输出服务提供商2已交付它的第一个输出。我们不清楚行为应该做到。

21个Fudgets作为流处理器

有了流处理器的实现,我们可以构建fudget组合子和一些简单的fudget,基于流处理器组合与运算。图4记住,我们可以从两个方面来看待福吉特:
  1. 作为可以产生I/O效果的普通流处理器。这个是呈现给应用程序的抽象视图程序员。有了这个观点,福吉组合家就这么做了与相应的流处理器相同组合学家。
  2. 作为流处理器,具有显式的高级别和低级别溪流。在本章中,我们将采取这种观点,以便实施谎言。
应当指出的是,还有其他实施方法福吉特。我们给出了两个适用于一元I/O系统的例子在里面第21.5条(参考其他工作实施的谎言第四十二章).

在图书馆中使用的fudget实现是非常高的同步流I/O系统用于Haskell 1.2版[HPJWe92型].

21.1同步流I/O

同步流I/O可以看作是Landin stream输入/输出输入图1,其中字符输出和输入流被替换为请求响应施工人员。程序和I/O系统是同步的,在那对于每一个请求程序产生,I/O系统产生一个响应。因此,程序和I/O系统可以看作是特殊类型的对话。同步流的类型程序主要的

类型对话=[请求]->[响应]主要内容:对话
每个请求构造函数表示一个特定的效果,并且在数据类型中定义请求在Haskell 1.2中:

数据请求=ReadFile字符串|WriteFile字符串|陈丽倩|附录Chan Chan字符串...
建造师读文件f是对I/O的请求系统读取名为的文件的内容f.写入文件f s是一个写作请求s名为的文件f.标准输入和输出是所谓通道的实例:读取字符流从标准输入请求ReadChan标准,和附录标准s是一个写的请求s在标准输出上。

请求为时生成的响应的类型执行,取决于请求构造函数。全部响应类型放在联合类型中回应:

数据响应=成功|故障IOError|字符串|IntResp Int公司...
如果I/O请求成功,则请求输出只是生成一个成功响应,而输入请求生成标记为的值结构,IntResp公司,或其他构造函数,具体取决于其类型。如果请求失败,错误值被标记失败生成。

使用同步流I/O模型的程序可以是作为一个原子序列流处理器显式地使用列表来表示流。

21.2标记的低级流

当同步流模型适应Fudgets时,我们两个修改。首先,我们丢弃显式将流表示为延迟列表,其次,我们标签请求和响应,以允许多个流在我们的程序中执行I/O的处理器。

我们定义了一种类型的谎言你好,你好成为一条小溪可以输入类型的消息的处理器你好或加上标签响应和输出消息或标记的请求:

类型F hi ho=SP(消息响应高)(消息请求ho)数据消息低-高=低-低-高-高
我们本可以用标准型的或者但我们更喜欢使用等效类型消息对于清晰。

低级流携带I/O请求和响应。福吉特组合学家>+<>==<合并来自两个参数fudget的请求流。但是当一个fudget输出一个请求时,我们必须能够发送相应的响应返回到相同的福吉。为了这个原因是,低级流中的消息被标记为指示它们来自哪个fudget或应该是哪个fudget的指针发送到。因为一个fudget程序可以看作是fudgets,节点是fudget组合子和叶子我们选择使用原子弹路径那个指向树结构中的节点:

类型响应=(路径,响应)类型TRequest=(路径,请求)类型路径=[转弯]数据转弯=L | R--左边还是右边
原子fudgets输出的消息包含一个空路径,[].二进制fudget组合子在或者R在输出消息的路径上指示信息是从左边传来的还是从右边传来的子预算。组合词组合两个以上的福吉(例如列表F)使用子预算的二进制编码位置。在输入端,检查路径以找到消息应传播到哪个子预算。

举个例子,考虑一下fudget

f=f1>==< (f2>+<f)
什么时候?f2希望执行I/O请求r,它([],r)在它的低级输出流中。这个>+<combinator将在通往小路,自从f2是左边的子预算。所以([L],r)出现在的低级输出中f2>+<f.类似地>==<combinator将预告R,所以低电平输出f包含([右,左],r)。当稍后出现响应时的输入流f,它将使用相同的路径,[右,左],这将导致组合子传播到f2.

显然,路径的长度是确定的直接由fudget组合子在程序。对于结构大致相同的程序绿树成荫,道路的长度也因此而增长与原子数成对数关系程序。因此,构建和分析路径的开销也随着假惺惺的数量呈对数增长。实践中,我们观察到的最大路径长度从4琐碎的程序(“你好,世界!”程序输入第9.1条),16用于小程序(中的计算器第9.8条)大型项目30人(校对助理阿尔法在第三十三章).

构造和分析消息的路径不是低层消息传递中唯一的开销来源。一些fudget组合,最著名的过滤器(见第二十四章)特别对待一些命令或事件。他们因此需要检查通过它们的所有消息。什么时候?必须发送大量的消息,开销可能变得太高。第27.5.3条我们提出一个在这种情况下我们遇到了这个问题,并给出了一个解决方案。

fudgets标签并行组合的一个实现是显示在图47。我们重新使用了标记为parallel的通过添加适当的标签来组合流处理器调整前置和后置处理器。另一个笨蛋组合子可以用类似的技术实现。

>+<::F i1 o1->F i2 o2->F(i1 i2中的任意一个)(o1 o2中的任意一个)f1>+<f2=mapSP后-==-(f1-+-f2)-==-mapSP预处理哪里发布消息=案例消息属于左(高ho1)->高(左ho1)右(高ho2)->高(右ho2)左(低(路径,请求))->低(L:路径,请求)右(低(path,req))->低(R:path,req)预消息=案例消息属于高(左hi1)->左(hi1高)高(右hi2)->右(hi2高)低(L:路径,resp)->左(Low(path,resp))低(R:路径,resp)->右(Low(path,resp))

图47.被标记的福吉特平行组合。

当一个请求到达fudget程序的顶层时在将请求输出到之前,应分离路径然后连接到响应之前被送回福吉特等级。这是在胡说八道.一个简单的版本胡说八道显示在里面图48.

fudlogue::F a b->对话fudlogue mainF=runSP(循环hrightsp routeSP(lowSP mainF))路线=getLeftSP$\(路径,请求)->putSP(请求权)$getRightSP$\response->putSP(左(路径,响应))$路线lowSP::SP(消息li hi)(消息lo ho)->SP li lolowSP fud=过滤器lowSP-==-fud-==-mapSP低filterLowSP=mapFilterSP条带低条带低(低-低)=刚好低条带低(高)=无

图48.一个简单的版本胡说八道对于同步流I/O。它不能处理异步输入。

这个版本将满足个人fudget不会阻塞他们的I/O请求。如果我们想的话对来自许多未知来源的输入做出反应顺序(例如插座、标准输入、车窗系统、,超时事件),这个实现是不够的,所以我们该怎么办?我们将在第22.2条简而言之,我们可以当主要的谎言变成虚度(也就是说已评估为getSP公司,而不是等待同步响应)。此时,我们执行一个系统调用(即选择)等待输入发生在我们的事件来源。

21.3编写同步原子模糊

在福吉特代表处第21.2条,一个atomic fudget反复接受Haskell I/O请求,执行它并输出响应,可以实现为跟随。组合子getHighSP公司getLowSP公司等待高级和低级消息,分别。它们的定义是waitForSP公司(第18.1条).

请求F::F请求响应请求F=getHighSP$\req->putSP(低([],请求))$getLowSP$\(\u,响应)->putSP(高响应)$请求
有些请求应该避免,因为当我们评估响应,程序可能会阻止。例如,我们不应该使用ReadChan标准,因为它的响应是一个延迟列表表示来自标准输入的字符流。

文件通常是可以阅读的,像一个傻瓜读文件F(第14.2条)可以实现为跟随:

readFileF::F String(IOError字符串之一)readFileF=post>^=<requestF>=^<ReadFile哪里后(Str s)=右s后置(故障f)=左f
在输入时,它等待打开文件名。输出是错误值或文件内容。

21.4福吉果仁

笨蛋请求在上一节中提供了Haskell流I/O系统的接口。编程一个具有特定顺序I/O行为的傻瓜,一个类组合子

--初步版本streamIoF::SP(任一响应i)(任一请求o)->F i ostreamIoF sp=环通hrightf(absF sp)请求
可以使用。参数流处理器服务提供商可以和某人交谈请求使用标记的邮件通过标记的信息发送给其他傻瓜赖特但是,用类型标记消息似乎更合适消息上面介绍的,让

--最终版本流:K i o->F i o
哪里

类型K i o=SP(消息响应i)(消息请求o)
我们称之为基奥 胡说八道果仁.Fudget内核因此在定义新的原子Fudget时使用具有特定的I/O行为。我们定义了一些组合子对于以连续方式描述I/O行为:

putchik::o->K i o->K i ogetHighK::(i->K i o)->K i o空:K i odostreamik::请求->(响应->K i o)->K i o
前三个操作直接对应于流处理器组合器putSP公司,getSP公司空SP,所以福吉果仁可以看作是普通的流可访问I/O系统的处理器。

本文介绍的组合子的实现截面如所示图49.

类型K i o=SP(消息响应i)(消息请求o)流:K i o->F i ostreamIoF kernel=mapSP post-==-内核-==-mapSP pre哪里pre(高i)=高ipre(低(u,resp))=低响应后(高o)=高opost(低请求)=低([],请求)putchik::o->K i o->K i oputHighK=高位getHighK::(i->K i o)->K i ogetHighK=waitForSP高哪里高(高i)=只有我高=没什么空:K i onullK=空SPdostreamik::请求->(响应->K i o)->K i oDostreamik请求联系人=putSP(低请求)$waitForSP低接触哪里低(低响应)=仅响应低-没什么

图49.Fudget核组合子。

21.5使用单体I/O的替代实现

今天,Haskell使用单体I/O模型在中解释第41.1.3条.一个单体版本胡说八道定义如下:
fudIO1::fab->IO()fudIO1 f=案例fNullSP->return()GetSP返回()PutSP(高)f'->fudIO1 f'PutSP(低(路径,req))f'->do resp<-doRequest请求fudIO1(startupSP[低(路径,响应)]f')
此版本仍然使用流I/O构造函数内部表现效果。它依赖于一个辅助工具功能

doRequest::请求->IO响应
它将请求转换为相应的单体效果。

我们还可以通过放弃请求来进一步和响应数据类型,并使用IO单子直接表现效果。这可以通过添加基于延续的流处理器类型的构造函数:

数据F'i o=输出(F'i o)|GetF(输入->输入输出)|空的|doof(IO(F'i o))
建造师多伊夫用于表示I/O影响。此构造函数没有任何显式关于继续的争论从I/O计算返回。把福吉特和I/O系统,我们使用福迪奥2:

fudIO2::F'i o->IO()福迪奥2 f=案例f属于NullF->return()返回()PutF'->fudIO2 f'DoIoF io->io>>=fudIO2
我们可以提供手术多伊夫用于插入fudget中的单次I/O操作:

doof::IO a->(a->F'i o)->F'i odoIoF io c=doIoF(映射c io)
fudget组合子被定义为对于流式处理器,对于多伊夫建造师。例如,在平行的情况下组成,包括:

DoIoF io>*<g=DoIoF(映射(>*<g)io)f>*<DoIoF io=DoIoF(映射(f>*<)io)

22 Fudget I/O:血淋淋的细节

在本章中,我们将深入探讨一些血淋淋的问题Fudget库实现的详细信息。我们拭目以待GUI fudgets的设计适合于中的X窗口第22.1条,使用X提供。

异步I/O是处理来自许多源,如X服务器、标准输入和插座。中描述了异步I/O的实现第22.2条.

fudget程序与X服务器之间的通信使用库Xlib[纽约90],它是用C.Xlib编写的定义许多数据类型,并调用维护窗口,在窗口中绘图并接收输入事件。

Haskell没有标准化的外语接口,所以Haskell程序不能直接调用Xlib。来解决这个问题问题是,我们实现了许多到Xlib的接口:一个其中三个独立于编译器,三个是特定的对于HBC、NHC和GHC。这些接口如中所述第22.3条.

22.1图形用户界面

guifudgets的实现使用了创建分层窗口在X窗口中,一个特性其工作原理如下。

在xwindows中,应用程序创建一个或多个窗户。我们已经看过了第九章怎么了胡说八道贝壳用于创建shell窗口。这些窗口显示在用户桌面上,并用窗口管理器的标题栏。窗口管理器允许用户可以以各种方式操作shell窗口,例如,它们可能会被调整大小并在桌面上移动。贝壳因此,窗口对应于用户的窗口概念。

从应用程序程序员的角度来看,一个shell窗口提供了一个可以填充图形的区域,并且它可以对鼠标单击等事件做出“反应”,而X服务器可以向应用程序报告事件.窗户有它自己的坐标系,它的原点在上面左角,不管窗口在桌面。窗口系统还可以确保在shell窗口中绘制,只有可见的区域是更新。这意味着应用程序的简化程序员,因为他不必考虑其他应用程序用户已启动。

到目前为止,这个故事适用于大多数窗口系统。X窗口运行更进一步,允许程序员创建更多窗口在内部贝壳窗。这些反过来又可以包含更多窗户。每个窗口都有自己的坐标系移动并调整大小(但不是由应用程序,如shell windows)。如果窗户是移动,里面所有的窗户都会跟着,保持他们的位置在局部坐标系中。此外,每个窗口事件掩码,它允许程序员控制应用程序对用户输入的“敏感”程度当指针在窗口中时。

外壳窗口的概念给我们带来的简化应用程序程序员可以转到分层窗口。如果每个GUI元素都放在自己的子窗口,即应用程序中不需要知道元素在shell窗口中的位置例如,当你在里面画的时候。也可以有一个一个小窗口里面的大的子窗口。通过移动大的窗口,我们得到滚动一个区域的效果。

因为每个GUI都有自己的窗口(可能包含子窗口),我们还使用了将每个GUI使用它自己的事件掩码,这是我们用来限制的从服务器到应用程序的事件的网络流量。这个最初是福吉的一个重要方面(见第22.3.1条),在跑步时仍然是一个优势低带宽链路上的程序。

每个GUI使用一个窗口fudget还简化了应用程序中的事件,它接收单个来自X服务器的事件。事件处理不集中,相反,GUI fudgets自己处理事件。X服务器报告鼠标单击,事件包含信息关于单击了哪个子窗口,并且该位置使用本地子窗口的坐标系。使用窗口信息在里面胡说八道,它维护来自窗口的映射GUI的标识符可以伪造路径。

22.1.1 X Windows接口的数据类型

GUI fudgets使用四种数据类型与通过Xlib的X服务器。首先,我们有数据类型X请求X响应(可以看作是扩展请求回应),这让我们可以与X服务器通信。
数据X请求=打开显示名称|CreateSimpleWindow路径矩形|CreateRootWindow矩形|CreateGC可绘制GCId GCAttributeList|加载字体字体名称|CreateFontCursor Int...
数据X响应=显示打开显示|窗口创建的窗口|GCCreated GCId|字体加载的字体ID|游标创建的游标ID...
剩下的两种数据类型是X命令,可以看作一组没有响应的请求,以及XEvent公司,哪个对X服务器异步报告给的事件进行编码应用程序。
数据X命令=关闭显示|销毁窗口|地图绘制|下窗|取消窗口|绘制可绘制GCId DrawCommand|洁净区矩形Bool|透明窗口|创建我的窗口矩形...
数据XEvent公司=KeyEvent{time::time,位置,根位置::点,状态::ModState,键入'::按下,键码::键码,keySym::keySym,keyLookup::keyLookup}|按钮事件{时间::时间,位置,根位置::点,状态::ModState,键入'::按下,按钮::按钮}|MotionNotify{time::时间,位置,根位置::点,状态::ModState}|EnterNotify{time::时间,位置,根位置::点,细节::细节,模式::模式}|LeaveNotify{time::时间,位置,根位置::点,细节::细节,模式::模式}|暴露{rect::rect,计数::Int}...
数据类型或多或少与Xlib调用和X事件,有一个重要区别:Xlib调用和事件处理额外的显示(显示器是连接到X服务器)和窗口参数,它们是添加人胡说八道(参见第22.2.2条).

一些辅助数据类型也或多或少地对应Xlib库中的直接定义如所示图50.

--资源标识符
新型 显示=显示整数--对于Window,PixmapId,FontId,GCId,CursorId也是一样,
--ColormapId。。。

--键入可读性同义词:
类型FontName=字符串类型ColorName=字符串类型时间=Int类型深度=内部--GC和窗口属性:
数据 窗口属性=CWEventMask[事件掩码]|CWBackingStore备份存储|CWSaveUnder布尔...类型GCAttributeList=[GCAttributes像素字体ID]数据 gc属性a b=。。。--看到了吗第27.4.3条

--各种枚举类型:
数据 事件掩码=按键按下掩码|按键释放掩码|按键按键按下按键|按键释放掩码|输入WindowMask | LeaveWindowMask | PointerMotionMask|曝光面罩...数据 备份存储=无用处|映射时|始终--几何学
数据 =点{xcoord公司●内景,ycoord公司*Int}数据 矩形=矩形{矩形波::,矩形::大小}--左上角和大小
类型 大小=
数据 线路=线路  --两个端点的坐标

图50Xlib接口。

22.1.2款F组:原始窗口创建fudget

GUI fudget是用一群蠢货:

组F::K a b->F c d->F(a c或c)(任意b d)
类型F组相似的>+<,并表示它将两个流处理器并行。它也会创建一个由第一个流控制的窗口处理器,它是一个内核(请参阅第21.4条). 全部X内核输出将发送给组fudget的命令窗口中的X事件将转到内核。

顾名思义,F组图形用户界面在第二个论点中,从下面的意义上说,这是一个错误。假设我们有小组g:

g=F组k f
所有由内部组创建的窗口f在由创建的窗口中创建g,因此分组。结果是如果内核k决定移动窗口,所有组都在里面f会接踵而至。

原子GUI fudget是沿着模式构建的F组k空的,也就是说,它们没有任何内部只是一个控制窗口的内核。例如,考虑一下这种形式的一个群体的胡说八道

F组k1(F组k2(F组knullF)>+<groupFk4空的)
它将有一个包含两个子窗口的窗口,其中一个将还有另一个子窗口,如中所示图51.

图51.四组傻瓜。每个组都有一个内核控制X窗口的流处理器。

一个组fudget从输出命令开始创建我的窗口r,其中r是一个矩形确定窗口在其父窗口中的大小和位置窗户。这是一个与任何Xlib都不对应的命令打电话。相反,它将被最近的组fudget,它将其视为表单的标记命令(p,创建我的窗口r)。包含组fudget会把这个转换成请求CreateSimpleWindowp r。当此请求到达时胡说八道,它将是形式(,CreateSimpleWindow p r).从这个信息来看,胡说八道能够推断出应该在哪个窗口中创建新窗口,新窗口的路径是通过连接找到的p(另见本节末尾第22.2.2条).

敏锐的读者现在会问“如果没有包容怎么办一群蠢货答案是贝壳也算作一种集体的胡言乱语,我们知道贝壳总是缠着鬼鬼祟祟的。两者之间的主要区别F组贝壳是后者从输出CreateRootWindow而不是创建我的窗口.请求CreateRootWindow习惯于创建外壳窗口。

群fudget概念可用于构建复杂系统福吉特。一个例子是钮扣组F:

按钮组F::F(BMevents a)b->F a b数据BMevents=BMNormal | BMInverted | BMClick
它在Fudget库中用于编程按钮。这个附件中的fudget将收到指示反馈是适当的,当用户实际在窗口中单击。这是一个群体的例子对用户是不可见的——它只处理输入。

作为一个只处理产出的群体的例子,我们可以看看纽扣订单,

buttonBorderF::F a b->F(Bool a或Bool a)b
用来绘制推的三维边界按钮,可以看起来是按下或释放。熟悉的纽扣软糖按钮是这两个群体的结合福吉特和一个拉贝尔夫.

有人会认为纽扣订单总是被使用就在钮扣组F,但这不是必要的。一个很好的反例是切换按钮,其中a钮扣组F包裹着两个软糖:一个纽扣订单它有一个小的onOffDispF公司它表示它的状态,并且拉贝尔夫。用户可以通过单击来控制切换按钮在任何地方钮扣组F,包括标签。注意切换按钮中的组结构与图51.

22.2同步与异步I/O

流处理器在Fudget库中的实现给了我们合作的多任务处理,这意味着处理器应该在反应性风格。这个意味着流处理器的正常状态是空闲,正在等待输入。当这种输入出现时,流处理器通过或多或少地立即输出0或更多来作出反应消息,然后返回到等待状态。

此外,fudgets在执行I/O时必须合作任务。正如我们在第二十一章,I/O程序中所有fudget的请求都在胡说八道。我们必须确保这些请求是瞬变而且或多或少可以立即进行。

基于这些原因,福吉特图书馆对同步异步输入/输出。同步I/O,整个fudget程序必须等待I/O操作完整,仅用于瞬态操作。实现很简单,正如我们在第21.3条。因为同步I/O很容易Fudget图书馆目前在阅读和写入文件,以及写入套接字时,标准输出和X服务器。(在大多数情况下,但不是所有情况下,这些操作都是暂时的,未来对Fudgets的改进是使用异步I/O,即使是这些。)当谈到阅读标准输入或套接字,或等待来自X服务器,使用异步I/O,因为这些都是操作可能会阻塞任意长时间。

22.2.1异步I/O的Fudgets

无稽之谈时间间隔(第14.3条)以及插座传输(第26.1条)是必须使用异步I/O以避免阻塞整个程序。他们都创造了描述符作为第一次步骤。
数据描述符=插座插座|定时器|显示器...
套接字描述符(类型插座)作为对请求的响应开口插座h p哪一个打开到端口的套接字连接p主机上h同样,一个请求创建计时器 d结果生成与间隔相关联的计时器描述符以及延迟d.

简单地创建描述符不会导致任何异步输入/输出。一个笨蛋可以用这个特殊的要求

选择::[描述符]->请求
发信号给胡说八道它感兴趣的来自指定描述符集的异步输入。

22.2.2异步胡说八道

要处理异步I/O,胡说八道维护映射在描述词和胡说八道之间。我们刚刚看到了胡说八道可以接收表单的消息(p,选择ds公司),宣布有一个路径p它等待关联的异步输入描述词在里面ds公司.功能胡说八道收集以这种方式从所有福吉特收到的所有描述符程序。当主fudget评估为getSP公司没有未完成的请求,胡说八道知道是的等待某个异步事件发生的时间。它发出一个选择请求,将所有收集的描述符作为争论。此请求的效果是
  1. 对UNIX函数的调用选择,等待输入到达任何描述符,或暂停,然后
  2. 对相应描述符的一种读操作(除非是暂停)。
生成的响应的类型为异步输入:

类型异步输入=(描述符,AEvent)数据AEvent=SocketAccepted套接字对等|套头串|定时臂|XEvent(WindowId,XEvent)
作为一种类型事件表示的响应选择已准备就绪的描述符,与数据读取。

使用描述符表,胡说八道能够路由接收到异步输入到等待的fudget。

此外,胡说八道执行以下翻译要处理GUI fudgets的事件:

  • 每一组福吉,胡说八道有关联来自窗口的标识符的路径,以及一个显示描述符(到X服务器的套接字连接)。集团傻瓜们不知道他们是哪个窗口或显示器与…有关,所以胡说八道将此信息添加到来自GUI的与X相关的命令和请求。
  • 还有从窗口标识符到路径的反向映射,哪一个胡说八道用于从X服务器路由事件与窗户有关的福吉小组。

22.3与Xlib的接口

我们已经看过了第22.1条我们所拥有的延长请求回应数据类型施工人员分成X请求,X响应,X命令,和XEvent公司,对应于Xlib调用和X事件。(这些数据类型不提供完整的接口Xlib。我们已经实现了那些我们认为有用的调用根据需要扩展接口。另外,一些参数从某些构造函数中省略。)在某个地方,实际的I/O必须执行这些请求和命令,并且这是在我们称之为Xlib的接口中完成的。我们有实现了许多不同的接口,它们是如下所述。

22.3.1与编译器无关的接口

第一次实现Fudgets是1991年在LML完成的,并使用了Landin的流I/O模型(参见第四章). LML中的程序是类型的函数字符串->字符串.Xlib的第一个接口是由输出调用并接收返回值和事件通过标准输出和输入通道以文本形式显示。这个程序通过一个双向管道连接到外部C执行实际Xlib调用的程序。的类型功能胡说八道F i o->字符串->字符串.

这种方法的优点是便于携带。需要对编译器或其关联的运行时系统。同一个C程序可以与另一个程序一起使用编译器或其他编程语言。

这种方法的缺点是效率低下由于命令的解析和打印,返回值和事件。通过以简单的格式打印它们不过,还是可以控制住的。另外,对于大多数用户界面任务,吞吐量不必很高。

22.3.2 HBC接口

为了避免文本通信的开销过程中,Lennart Augustsson将接口集成到Xlib使用LML的运行时系统。LML使用同步流I/O(请参阅第21.1条),所以整合是通过向请求和响应添加新的构造函数来完成类型。扩展如所示图52它们处理命令和请求对应于Xlib调用、套接字I/O请求和中描述的异步I/O第22.2.2条. The函数的类型胡说八道已更改为F i o->对话.

类型对话=[响应]>[请求]数据请求=ReadFile字符串|WriteFile字符串|  ...--扩展|XCommand(XDisplay、XWId、XCommand)|XRequest(XDisplay、XWId、XRequest)|SocketRequest套接字请求|选择[描述符]|  ...数据响应=成功|字符串|故障IOError|  ...--扩展|GotSelect异步输入|插座响应|X响应X响应|  ...

图52.扩展Haskell 1.2对话请求Xlib接口的I/O类型

HBC运行时系统处理对话I/O的部分是用C语言实现的过程请求s被修改为处理X请求X命令通过调用新过程请求道克斯考尔概述图53。如中所示图54,每个支持的Xlib都有几行C代码需要打电话。

PTR doxcall(t,p)门int t;/*请求的标记*/PTR p;/*指向请求参数的指针*/{PTR反应;p=评估(p);开关(t){case XCommand:/*(显示,窗口,XCommand)*/p=评估值(p,3);xdocommand((Display*)输入到f(GET1OF3(p)),INTOF(GET2OF3(p)),得到3/3(p));响应=mkconstr0(RSuccess);休息;案例XRequest:/*(显示,窗口,XRequest)*/{PTR X响应;p=评估值(p,3);xresp=doxrequest((Display*)输入到f(GET1OF3(p)),INTOF(GET2OF3(p)),得到3/3(p));响应=mkconstr1(XResponse,xresp);}休息;违约:fprintf(stderr,“未知X I/O请求…”,…);出口(1);休息;}返回响应;}void xdocommand(显示,wi,p)...

图53.C函数道克斯考尔已添加到HBC的运行时系统来处理额外的请求X命令X请求.

PTRDOX请求(显示、wi、p)显示*显示;窗口wi;PTR p;{PTR rp;窗口父级;开关(getcno(p)){案例XRqOpenDisplay:/*显示名称*/{字符显示名称[BUFSIZE];显示*显示;evalstring(EARG1(p),显示名称,显示名称的大小;display=XOpenDisplay(displayname[0]?NULL:显示名称);返回MkPtrXResp(XRDisplayOpened,display);}休息;案例XRqCreateRootWindow:/*Rect*/......}}

图54.C函数DOX请求分析这个X请求并执行对Xlib的相应调用。

22.3.3 NHC接口

1996年夏天,福吉图书馆被移植到NHC[罗j95b]对于Haskell 1.3[PH96],允许fudget程序利用中提供的新堆分析功能NHC公司[RR96a公司][雷亚尔96B].

福吉特图书馆可以通过相对较小的工作量:

  • 包含Haskell 1.2类型定义的模块请求回应类型被添加,因为这些都没有在Haskell 1.3中定义。
  • 这个胡说八道函数被修改为福迪欧1在里面第21.5条.
  • 对NHC的运行时系统进行了扩展,实现了Xlib呼叫和其他分机。幸运的是HBC和NHC非常相似的运行时系统,所以所有的C代码都是写的因为HBC只需稍作改动就可以重用,尽管I/O系统之间的差异。扩展名是作为一个新的单体I/O操作提供,类似于多勒克斯特在里面第21.5条:

    doXCall::请求->IO响应
    此函数的作用是调用过程道克斯考尔在里面图53.
22.3.3.1支持两遍堆分析
堆分析可以帮助您改进你的程序。例如,您可能会发现使用传记的堆配置文件堆是拖曳,即最后一次使用后堆起来。然后您可以使用传记简介和定金要了解的个人资料程序中的哪一组函数是负责保持阻力。这也许能给你一个线索至于你应该如何改变程序来摆脱拖累。

某些组合外形的实现,如所述在[雷亚尔96B],分两次收集所需信息,也就是说,程序运行两次。为了创造两个如果运行相同,则所有I/O操作的返回值必须在第一次运行时录制,然后在第二轮。

为了让fudget程序利用最新的堆分析技术,Niklas Röjemo添加了记录和回放结果所需的代码的Xlib调用和其他扩展I/O操作福吉图书馆。作为一个典型的例子Xlib程序xOpen显示(我们已经看到了在里面图54)已更改,如中所示图55.

案例XRqOpenDisplay:/*显示名称*/{字符显示名称[1000];显示*显示;evalstring(EARG1(p),显示名称,显示名称的大小;重放“一”和“跳过”(显示)display=XOpenDisplay(displayname[0]?NULL:显示名称);录音一(显示);返回MkPtrXResp(XRDisplayOpened,display);}休息;

图55.对Xlib glue代码的更改通过堆分析。

录制\u 1重放“一”和“跳”展开到在第一次运行期间记录结果的代码并在第二次调用时跳过实际调用快跑。它们的定义如所示图56. The变量重播,记录输入文件由NHC的运行时系统设置。

#定义REPLAY_ONE_和_SKIP(x)if(REPLAY){REPLAY(x);}其他的#定义一个(x)if(RECORD){RECORD(x);}#定义记录(x)fwrite((void*)&(x),sizeof(x),1,inputFILE)#定义重播(x)fread((void*)&(x),sizeof(x),1,inputFILE)

图56.两遍堆分析的宏。

22.3.4 GHC接口

作为NHC实施的结果,福吉图书馆做到了不再依赖Haskell 1.2 I/O系统。这打开了使用I/O monad和GHC中的C接口[杂志{\etalchar{+}}97]. 通过使用这个端口,它可以利用GHC的时间分析工具和生成有效代码的可能性。

GHC中Xlib的接口是以相当特别的风格编写的使用_ccall公司__卡西姆_声明。今天,一个更好的可以使用外语接口创建接口绿卡支持[JNR97型].

23自动布局

布局组合子第十一章用于指定图形对象的位置和大小。今天,这些物体可以有两种类型:GUI fudgets或如中所述的绘图第二十七章。最初的布局系统是为GUI fudgets,它的实现将在本文中描述第节。

自动布局系统的目的是减轻应用程序程序员指定任务的确切位置以及每个图形用户界面的大小。这个任务有几个动态的方面。首先,它取决于未知因素直到程序开始。例如,绘制的文本的大小在标签上,fudget取决于所选的字体和大小,并且可以只有在福吉公司与X服务器。个别的GUI fudgets也可以改变他们的大小在任何时候时间,用户可能会调整shell窗口的大小。这两个都有活动可能意味着许多GUI傻瓜必须改变他们的位置和大小。

布局系统还简化了个人的鬼鬼祟祟,在那不必担心的地方以及其他GUI fudgets的位置。它只能指定一个首字母请求一个尺寸,布局系统将分配一个位置和实际尺寸。

布局系统的实现通过移动和调整与组fudgets对应的矩形单元的大小(第22.1条). 记住一个群体基本上是在胡闹可能由一个控制X窗口的流处理器组成里面有很多人在胡说八道。每组也都在胡闹包含一个布局系统,它负责每个位置和尺寸立即的福吉特小组。这个责任只有一个层次:一个集团的福吉特没有控制其任何组中包含的任何组。例如,对应的组k1在里面图51负责布置k2k4(但不是k).

这种责任划分是很自然的,因为团队很容易通过单个Xlib命令放置和调整大小(配置窗口). 组内的所有子窗口都将跟随和保持他们的相对位置。

布局系统的机制可以通过查看组fudget与其立即数之间的消息通信子组。

福吉集团有一个滤波器(其他过滤器如中所述第二十四章),称为自动布局,用于侦听布局在来自子组的低级流上输出的消息。

数据布局消息=布局请求|LayoutName字符串|布局放置器|间隔垫圈布局...
子组决定他们需要的大小,并输出一个布局请求.
数据布局请求=布局{minsize::Size,fixedh,fixedv::Bool}
田野最小尺寸是要求的尺寸,并且固定(固定电压)为true指定大小不是可在水平(垂直)方向伸展。(一些砂矿使用此信息为可拉伸的盒子分配额外的空间仅限。)

布局过滤器还接收程序员已经包围了子组。因为所有布局消息使用路径标记时,布局过滤器可以关联放置器以及带有包裹子群的间隔器,通过分析路径。这个建造师布局名称以类似的方式用于关联有名称的子组。

放置器和间隔器是决定实际布局。放置器对布局请求列表进行操作,生成一个单独的空间请求,用于放置所有关联的子组。回顾一下关于盒子的讨论第11.1条,我们将认识到将会有一个每个方框对应的布局请求。

与放置器不同,间隔器将单个请求作为参数,并且布局过滤器将其映射到所有请求上对应于与垫片相关的封闭盒,产生相同数量的请求。

类型Placer=[LayoutRequest]->(LayoutRequest,Rect->[Rect])类型间隔符=LayoutRequest->(LayoutRequest,Rect->Rect)
从这些类型中可以看出,放置器和间隔器也返回一个残余物函数,我们将在下面描述。

收集了布局请求并应用了布局对他们来说,福吉特小组必须决定一个单一的要输出的布局请求。因为即使没有,程序也应该起作用布局由程序员指定,默认的放置位置被包装在小组周围。

使用的默认放置位置被调用自动驾驶,选择合适的根据手头的布局请求进行布局。在当前实现,它只是在两者之间进行选择垂直水平,基于两个首选项:

  1. 不因不必要的拉伸而浪费空间的布局比其他人更喜欢,
  2. 方形布局优先于狭长布局。
未来的实现可以考虑更多的参数在选择布局时考虑,并有更广泛的布局选择在两者之间选择。

在生成了一个布局请求之后,组fudget输出它将由封闭组处理,除非是一个空壳集团。在这种情况下最小尺寸请求用于设置外壳窗口的大小。

这个XEvent公司类型包括用于向GUI fudgets报告布局更改:

数据XEvent=。。。|矩形布局|布局尺寸尺寸
这些布局事件的传播从shell组开始福吉。当shell窗口的大小被调整后,X服务器发送配置通知包含外壳的新大小的事件一群蠢货。请注意,无论调整大小操作是否由程序完成(结果或用户(通过窗口管理器)。不管怎样,shell组fudget生成以下形式的事件布局(矩形0s),到布局过滤器。这说明矩形从原点到s可用于子组。现在,布局过滤器应用将剩余的放置器和垫片放置到该矩形中图案。每一个剩余的放置器都会产生一个矩形列表,其中元素对应于请求列表(因此盒子)被喂到最初的地方。同样,残差分隔符映射到矩形上以调整关联的子组。

当这个反向过程完成时,布局过滤器输出布局消息发送给每个子组,它将移动相应地,将消息传递到其布局过滤器,依此类推这个过程以递归方式进行。

当一个组收到布局消息,它还发送一个布局尺寸消息发送给内核流处理器,以便它可以将窗口的图形内容调整为几何学。注意内核只需要知道它的大小窗户,而不是它的位置。这是因为所有窗口操作使用局部坐标。

23.1福吉特布局的历史步骤

  1. 最初,根本不支持布局。这个程序员必须明确指定每个人都在胡闹。
  2. 然后,在限制条件下实现了自动布局每个GUI-fudget必须对应一个盒子。这意味着,当两个GUI在哪里合成时,一个必须指定放置位置。并行与的组合子连载作文有一个额外的论点。因此,布局与GUI在胡说八道。
  3. 目前的系统允许每个fudget有多个盒子。一起使用命名布局,这允许更灵活的布局。

24过滤软糖

事实上,fudget的所有I/O效果都被表示出来了通过数据类型中的构造函数请求,回应和其他人,打开了写我们的东西的可能性会打电话来的过滤器它改变了一个傻瓜的特定方面输入/输出行为。筛选器具有类型F a b->F a b,这表明他们没有篡改高层消息,它们只分析和修改低级消息。

许多问题可以通过使用过滤器来解决——for例如,交换鼠标左键和右键的含义按钮,或者在GUI fudgets中交换黑白色。

在下面几节中,我们将看到两个过滤器示例从福吉特图书馆,它改变了复杂的行为福吉特:

Fudget库中的过滤器是通过相似的组合词环通滤波器,称为环通lowf:
loopThroughLowF::SP(任意一个TRequest响应)(无论是TRequest TRESPOSE)->F i o->F i o
就像环通滤波器经常被应用程序使用程序员封装和修改现有的行为福吉特,环通lowf用于位于胡说八道贝壳,因此可以修改某些应用程序中所有fudget的方面。控制流处理器,它是环通lowf,接收所有已标记请求的流作为输入从封装的fudgets中输出,也都被标记了对同一个谎言的回应。它可以分析和在输出消息之前,以任何方式操纵这些消息,之后,它们将继续进入I/O系统(在在请求的情况下)或fudgets(在回复)。最简单的过滤器是

循环lowf idSP
它只是传递所有请求和响应不受干扰,因此充当身份过滤器。

24.1缓存过滤器

每个guifudget在X服务器,如字体、字体说明、图形上下文还有颜色。例如,一个带有大GUI的fudget程序可能查询大量字体说明。这会导致启动时间慢,特别是如果程序和服务器都很大。通常,大多数GUI傻瓜都会进行查询和程序中的其他资源一样,看起来浪费。如果资源分配能够在GUI-fudgets之间共享。这不仅会导致更快的启动速度和更少的网络负载,但是程序也会减少内存消耗。这与字体由于这些描述可能占用大量堆的数量。

缓存筛选器的角色是支持此资源在福吉特人之间分享。它是胡说八道,哪个意味着项目中所有的傻瓜都能从资源中获益分享。

缓存过滤器对慢速连接的影响最为显著具有很高的往返延迟,例如拨号连接。演示一下,我们已经跑了中情局,其中一个演示来自Fudget发行版的程序,通过拨号连接使用PPP和secure shell(宋承宪,压缩率9)。这个调制解调器速度是14400位平均每秒钟往返延迟250毫秒。消除由于压缩率不同而产生的错误反复启动,直到启动时间收敛。没有缓存筛选器,的最短启动时间中情局记录到133秒。启用缓存时,启动时间缩短到9.6秒,加速因子超过13比较一下,我们也跑了中情局在这个缓慢的连接上不压缩:启动时间为274秒没有缓存,有缓存31秒。压缩是件好事!)

当启用缓存时,堆使用率也会更好,达到峰值从990千字节减少到470千字节。

这些数字应该不会令人惊讶,因为图形用户界面中情局由一个显示器和28个可共享的按钮组成同样的资源。

使用缓存筛选器意味着在程序。除绘图命令外,过滤器将分析每个命令输出的请求。因此,计算器的启动时间当X服务器运行在与计算器。在这种情况下,连接速度很快可忽略的往返延迟。

24.1.1实施

在描述实现之前,我们将展示一个当一个fudget分配一个特定类型的资源,即图形上下文(GC)。首先,fudget输出X请求创建GC d tgc公司 艾尔,其中d在哪一个抽屉里将使用GC,tgc公司是一个模板GC,并且艾尔新GC应该具有的属性的列表。请求是变成了对Xlib函数的调用XCreateGC,哪个返回对新GC的引用。这将作为响应返回gc已创建气相色谱仪给请求的福吉,这带来了它可以使用。当GC不再需要时,fudget可以通过输出X命令显式地释放它免费气相色谱仪.

使用缓存的想法当然是想要创建一个具有相同模板和属性的GC如果尚未释放第一个GC,则可以重用它。所以是GC缓存从模板和属性到图形维护表上下文和引用计数。

事实证明,大多数资源(de)分配遵循相同的原则模式作为我们的场景,如果我们从特定的请求中抽象和响应构造函数。这个抽象在类型请求类型,表示请求是否是分配、分配或其他:

数据RequestType a r=分配|自由r|其他
分配构造函数携带分配缓存筛选器在资源中用作搜索键的数据表。同样免费构造函数承载资源那应该被释放。在图形上下文中分配数据是成对的模板GCs和属性列表,以及资源是图形上下文。

功能gc请求类型确定请求的类型对于图形上下文:

gcRequestType::请求->请求类型(GCId,GCAttributeList)GCIdgcRequestType r型=案例r属于CreateGC d tgc al->分配(tgc,al)FreeGC-gc->Free-gc_->其他
通用缓存筛选器缓存筛选器参数化完毕确定请求类型的函数:

cacheFilter::(Eq a,Eq r)=>(请求->请求类型a r)->F i o->F i ocacheFilter rtf=loopThroughLowF(缓存[])哪里缓存表=。。。
内部状态桌子是类型列表[(a,(r,Int))],其中元素是相关资源和引用计数。

图形上下文缓存的定义现在很简单:

gcCacheFilter::F i o->F i ogcCacheFilter=缓存筛选器gcRequestType
Fudget库定义了请求类型函数,比如gc请求类型对于一些资源缓存筛选器,使用常规缓存筛选器所有这些过滤器组合成所有缓存筛选器:
allcacheFilter::F a b->F a b所有缓存筛选器=fontCacheFilter。fontStructCacheFilter。gcCacheFilter。colorCacheFilter。bitmapFileCacheFilter。fontCursorCacheFilter
此缓存过滤器包装在中的所有fudget程序中胡说八道你应该害怕所有缓存筛选器会强加由于所有命令都必须在通过六个过滤器中的每一个。实际上,管理费用不是一个大问题。

24.2聚焦过滤器

当我在键盘上键入时,哪个GUI元素应该接收键入的字符?等效地,哪个GUI元素具有输入焦点? 最初,Fudget图书馆实现了简单模型指向类型专注,因为它直接X Windows支持。有了点对类型,一个图形用户界面fudget不能有输入焦点,除非指针结束它。一个GUI fudget(例如)表明它对通过将其事件掩码配置为包括按键掩码,输入窗口掩码,和叶窗遮罩这意味着fudget可以接收键盘输入,也可以在指针进入或离开fudget(交叉事件)。这个交叉事件用于提供视觉反馈福吉有重点。

点对类型控制焦点的潜在问题是用户必须在键盘和键盘之间来回移动一只手指针设备(假设指针无法控制如果她想在表格中填写数据由多个输入字段组成。它也很容易触摸到指针装置意外使指针有点跳跃,可能会导致焦点改变。

当使用单击以键入焦点模型。点击输入,指针位置和焦点之间的紧密耦合已删除。相反,用户在输入字段中单击以指示它应该有焦点。焦点一直保持到用户在另一个输入字段中单击。另外,如果键盘可用于在中的输入字段之间循环焦点表格,可以不用定点设备填写。

这种改进的输入模型的一个有限的变体已经被添加到Fudget库作为一个过滤器在shell fudgets中,离开各种图形用户界面未经修改。局限性在于模型只要指针在外壳内,就只能单击键入福吉。当指针离开shell fudget时,焦点转到不管应用程序窗口在它下面,除非窗口管理器使用单击键入。

24.2.1实施

重点实施是以重点观察为基础的需要键盘输入的图形用户界面集中注意力)可以通过它们将窗口配置为报告。所有的焦点都是课程对关键新闻事件感兴趣,但他们也需要交叉事件,当他们有集中。因此,focus fudgets最初会设置他们的窗口事件掩码以便F掩模是一个子集:

ffMask=[KeyPressMask,EnterWindowMask,LeaveWindowMask]
焦点过滤器的简化实现如所示图57.

focusFilter::F a b->F a bfocusFilter f=环通lowf(focusSP[])(simpleGroupF[KeyPressMask]f)focusSP::[Path]->SP(任意一个TRequest响应)(无论是TRequest TRESPOSE)focusSP fpaths=getSP(任一请求-响应)哪里请求(p,r)=案例获取事件掩码r属于只需掩码| ffMask`issubset`mask->putSP(左(p,setEventMask(掩码'r))$focusSP(p:F路径)哪里mask'=[buttonpressTask]`union`mask_->putSP(左(p,r))$focusSP路径响应(p,r)=如果按键r然后(右(头部右侧)$focusSP路径)其他的 如果左键按1 r&&p`elem`F路径然后putSP(右(p,r))$focusSP(aft++bef)其他的putSP(右(p,r))哪里(bef,aft)=中断(=路径)fpaths--辅助功能:simpleGroupF::[EventMask]->F a b->F a bgetEventMask::请求->也许[EventMask]setEventMask::[EventMask]->请求->请求按键::请求->布尔leftButtonPressed::请求->布尔

图57聚焦过滤器。

焦点过滤器就在里面贝壳软糖。无论位置如何,都可以获取键盘事件指针(只要它在shell窗口内),一个群体福吉特是围绕着内部的福吉创造的一个合适的事件掩码。这就结束了简单组,其作用是F组没有内核。

过滤在中完成焦点,谁的论点F路径累积一个指向焦点的路径列表。这是通过查找匹配的窗口配置命令来完成事件掩码。焦点fudgets的事件掩码被修改为面具'所以焦点的窗户会变模糊将生成鼠标按钮事件。

负责人F路径被视为拥有焦点,并且传入的密钥事件被重定向到它。如果用户点击其中一个焦点人物,F路径重新组织以便点击的路径首先出现。

如前所述,图57显示了一个简化的焦点过滤器。这个Fudget库中的过滤器更为发达;它还可以处理交叉事件,并使用键盘进行焦点更改。更复杂问题,如动态地创造和摧毁福吉特,也是处理。不过,它忽略了一些由迁移的傻瓜进来了第二十五章.

还应注意的是,X窗口模型支持特殊焦点改变控制焦点。这更适合窗口管理器实现点击输入。

24.3过滤器的利弊

我们在福吉特图书馆使用过滤器的经验是好的和坏的。好的一面是,过滤器打开修改现有软件的I/O行为的可能性不必修改源代码。在另一边,虽然过滤器是在没有改变这个图形用户界面的源代码我们使用源代码来决定我们的假设是什么可能会影响他们的行为。例如,我们已经看到了焦点过滤器假设所有的GUI错误都应该在焦点控制可以通过分析它们的事件来区分面具。这会使事件掩码的语义复杂化在编程新的GUI时必须考虑到这一点福吉特。同样,由于缓存过滤器意味着对资源的强制操作(例如交换GC)必须避免在图形用户界面中使用fudgets。

过滤器的实现通常涉及到必须与每个GUI fudget关联。这意味着国家在图书馆里,有些理智。有一部分作为地方政府存在于福吉特本身焦点过滤器和胡说八道表,用于路由异步事件。如果福吉州是这样分布的,总有危险它变得不一致,例如当福吉搬家或死吧。

25个移动流处理器

流处理器的一个显著特点是不直接连接到它们的输入流。而是一条小溪处理器一次对一条消息作出反应。一个更好的名字会真的吗信息处理机,因为没有明确的流到任何地方,只有消息。这与函数相反以延迟列表的形式对流进行操作,这是类型[i] ->[o](如果我们只考虑一个输入流到一个输出流)。获取的输出流这样的流函数,必须将其应用于输入溪流。一旦完成,就没有简单的方法来分离流函数。

为什么要这样超脱?一个原因是如果我们希望流处理器在一个环境中运行一段时间,然后将其移动到其他环境并继续运行在那里。请记住,流处理器是一级值,并且可以作为消息发送。再加上流处理器与已经执行了一段时间,一个“新的”程序,允许我们编程一个可以捕获任意流处理器。

extractSP::SP i o->SP(要么()i)(要么(SP i o)o)抽出物=案例s属于PutSP o s->PutSP(右o)$extractSP sNullSP->NullSPGetSP是->GetSP$\m->案例属于右i->extractSP(is i)Left()->PutSP(Left s)$空SP
流处理器提取器s接受消息形式的赖特,供至s.输出os输出为赖特 o。我们可以随时发送消息左()对它来说,并将封装的流处理器输出到当前状态作为消息,标记为。请注意一般来说,以这种方式输出的流处理器不是等于原始流处理器s.

所以当我们要求继续的时候,提取器s输出它并死亡。但它为什么会死呢?可能有用就好像什么都没发生过一样。这提醒了我们属于克隆对象,以及分叉过程。这个通过修改提取器.

cloneSP::SP i o->SP(任意一个()i)(任意一个(SP i o)o)克隆蛋白s=案例s属于PutSP o s->PutSP(右o)$cloneSP sNullSP->NullSPGetSP是->GetSP$\m->案例属于右i->cloneSP(is i)Left()->PutSP(左s)$克隆蛋白s
因为流处理器只是值,所以我们不需要任何值复制国家的机器——这确实是一个例子我们欣赏纯函数式编程。

我们也可以把这些想法推广给福吉实现变得更加复杂。如果你是个笨蛋,必须采取一些行动来确保例如,当它移动时,它会一直向前移动。然后我们可以编程拖放对于任何GUI fudget,如中所示图58。在下面,我们将描述一组在fudget程序中支持拖放的组合器。

即将拖动

拖动时

落下后

图58.我们要拖的福吉特的照片,拖拽的时候,掉下来之后。fudget被删除,用户更改了文本。请注意,移动的fudget的输出现在转到第二投放区。

我们称之为用户可以拖放的fudget拖曳胡说八道以及他们居住的地区下降区域. The在滴下区域之间进行阻力传递一个隐形人拖放福吉。示意图这些傻瓜的照片如图59.

图59.隐形眼镜的示意图拖放fudget(用虚线框表示),其中此案例包含两个矩形的跌落区域,每个区域包含许多可拖拽的谎言。虚线箭头指示如果用户拖动fudget将发生什么f1从第一个区域到第二个区域:将会是从第一个放置区域提取消息并到达拖放fudget,这将使其反弹到第二次下降面积。

这三种类型他们交换特殊信息来控制拖拖拉拉。让拖拽者与程序的其余部分独立于这些控制消息,我们假装福吉有两个中层输入和输出连接。

类型SF-mi-mo-hi-ho=F(任意一个mi-hi)(任意一个mo-ho)
类型旧金山代表分层软糖带着这个类型,我们可以把流处理器的消息类型看作分成三个层次。拖拽是由集装箱形成的德拉格:
绘图:F a b->绘图a b类型DragF a b=SF DragCmd(DragEvt a b)a b
结果类型德拉格f是一个分层的骗局其中高级流连接到f,和中层流用于控制,通过拖曳命令拖动事件。拖动命令从拖动过程中拖动模糊区域的放置区域。最多重要的拖动命令是脱水,并通知它已经被另一个投放区域接受了。这个命令,drag-fudget用一个包含本身:
数据图纸=脱水|  ...
数据阻力a b=抽提物(DragF a b)|  ...
因为阻力事件可以包含阻力模糊,所以我们看到它必须参数化类型牵引确切地说拖动类型必须在拖动类型中可见事件,以及在其他控制消息类型中介绍如下。因此,类型系统确保拖拽的福吉不能被扔在一个区域不同的类型。

液滴区域是动态(参见第13.4条):

dropAreaF::SF(DropCmd a b)(DropEvt a b)(Int,a)(Int,b)
中层消息称为删除命令删除事件,并由拖放fudget使用控制投放区域。请注意,这两种类型都是参数化,因为两者都可以携带信息。如中所示图59,滴滴该区域包含阻力模糊区域,并对其进行标记。这个因此,当它们进入或离开投放区。

有一个drop命令对应用程序很有意思程序员:

dropNew::DragF a b->DropCmd a b
它被用来在下降区域内注入新的阻力。

最后,我们有了拖放式的fudget,它在投放区域之间调停投放的软糖。

dragAndDropF::SF(t,DropCmd a b)(t,DropEvt a b)c d->F c d
争论德拉甘德罗夫是一个分层的骗局其中层消息应唯一标记为drop area信息。其内涵是分层的模糊包含一个投放区域的名单。这样的列表可以方便地创建使用分层变量列表F:
列表F::公式t=>[(t,SF a b c c d)]->SF(t,a)(t,b)(t,c)(t,d)listSF sfl=pullEither>^=<listF sfl>=^<pushonerPUSHEOR(左(t,a))=(t,左a)PUSHEOR(右(t,a))=(t,右a)拉具(t,左a)=(左(t,a))拉具(t,右a)=(右(t,a))
通过德拉格,下降面积,和德拉甘德罗夫,我们可以对示例进行编程(如图58.作为拖拽软糖,我们用标签字符串输入的。
drag::Show i=>DragF字符串阻力i=dragF$labupperif(“拖动我(”+++显示i++“)$((show i++“:”)>^=<stringInputF
drag fudget的字符串输出在其前面加上身份.

我们定义了一个放置区域,其中显示阻力产生的输出。我们初始化降落通过在其中创建一个与投放区。

区域::显示i=>i->SF(DropCmd字符串)(DropEvt String String)(Int,String)一个面积i=vBoxF$idLeftF(displayF>=^<snd)>=<startupF[Left$dropNew$drag i]dropAreaF
最后,我们定义了一个具有两个拖放区域的拖放fudget在贝壳里面的软糖。
dnd::F(Int,(Int,String))(Int,a)dnd=dragandropf$listSF$[(t,shellF(“下降区域”++显示t)(区域t))| t<-[1..2]]主管道=fudlogue dnd

25.1在X窗口中拖动窗口的问题

在X窗口的指针下拖动对象作为窗口系统不是没有问题的:很难确定在哪里当用户释放鼠标时,对象将被删除按钮。此版本生成一个按钮事件,其中包含有关指针下的窗口的信息。但是这个不会是我们放下物体的窗口,而是对象的窗口本身!如果我们满足于壮观的视觉反馈,我们可以选择不移动对象本身,但将指针更改为带有小物体,就像在开着的窗户里做的那样[索尔97].

我们需要的是有拖动对象的窗口透明的尊敬地对某些事件。我们用一种残酷的方式来实现这一点在对象窗口中的指针,如详图所示。

但是,我们现在有时间问题,如果用户快速移动指针并立即放下对象。在那里是指针和对象移动的延迟,因为是执行跟踪的客户端。持续的延迟,跟踪误差与指针速度成正比,这意味着如果速度足够大,指针将别再站在洞上面了。目前,我们不知道是否有在xwindows中存在一个很好的解决方案。

用于客户端/服务器应用程序的26个类型化套接字

在这一部分,我们将看到福吉如何适用于其他人除了图形用户界面之外的各种I/O。我们会写信的客户机/服务器应用程序,其中fudget程序充当服务器在一台电脑上。这些客户也是伪造程序,他们可以如果需要,可以在其他计算机上运行。

服务器是一个fudget程序的例子,它可能没有需要一个图形用户界面。但是,服务器应该能够同时处理多个客户机。一种方法组织服务器需要客户端处理程序对于每个连接的客户。每个客户端处理程序通过连接(一个套接字),但它可能还需要交互服务器的其他部分。在这种情况下派上用场。服务器将动态创建fudgets作为客户端每个连接的新客户端的处理程序。

我们还将了解如何使用Haskell的类型系统来关联服务器的地址(主机名和端口号)服务器可以发送的消息类型和接收。如果客户机也是用Haskell编写的,并且导入与服务器的类型地址的规格相同,我们知道客户端和服务器将在消息类型上达成一致,否则编译器将捕获类型错误。

我们在这里考虑的套接字类型是Internet流插座。它们提供可靠的双向连接,类似于Unix管道,在Internet上任意两台主机之间。他们被利用了在Unix工具中,如telnet、ftp、finger、mail、Usenet和万维网。

26.1客户

为了能够与服务器通信,客户机必须知道服务器所在的位置。位置由主机(网络上的计算机)和端口的名称数字。典型的主机名是www.cs.chalmers.se. The端口号区分运行在同一台服务器上的不同服务器主持人。标准服务有标准端口号。例如,WWW服务器通常位于端口80上。

Fudget库使用以下类型:

类型主机=字符串类型端口=Int
笨蛋
socketTransceiverF::主机->端口->F字符串
允许客户端连接到服务器并与它(脚注:该库还提供了组合词,可以提供更多控制错误处理和打开和关闭连接。)字符块在输出流中显示为一旦从服务器接收到它们(将其与标准差在里面第14.1条).

我们可以编写的最简单的客户机可能是telnet客户:

telnetF主机端口=stdoutF>=<socketTransceiverF主机端口>=<标准差
这个简单的程序不做所需的选项协商根据标准的telnet协议[rfc854855],所以它不能工作当连接到标准telnet服务器时(在端口上23条)。然而,它也可以用于与许多其他标准进行对话服务器,例如邮件和新闻服务器。

26.2服务器

而客户端主动连接到特定的服务器,服务器被动地等待客户端连接。当客户端连接时建立了新的通信通道,但是服务器通常继续接受来自其他客户端的连接好。

创建服务器的简单方法是

SimpleSocketServer::端口->F(Int,String)(Int,String)
服务器允许客户端连接到上的参数端口运行服务器的主机。为客户分配连接到服务器时的唯一号码。SimpleSocketServer字符串是否被这样的客户机标记数字。输入和输出流中的空字符串意味着连接应分别关闭或已关闭。

这个简单的服务器fudget不直接支持一个程序结构,每个客户端有一个处理程序fudget。更好的组合子将在下一节中显示。

26.3类型插座

许多因特网协议使用人类可读的消息文本。当实现这些时,要使用的自然类型消息是字符串。但是,当我们同时编写两个客户端时对于Haskell服务器,我们可能需要使用适当的数据键入在客户端和服务器之间发送的消息,就像我们一样如果客户机和服务器在同一个程序中出错,就这样做。本节我们将展示如何从实际中抽象出来在网络上表示消息。

我们为键入的端口号键入的服务器地址。这些类型将在类型上参数化我们可以在插座。首先,我们有输入的端口号:

数据t端口c s
客户端程序需要知道服务器:
数据t服务器地址c s
在这些类型中,cs代表客户端和服务器分别传输的消息。

要生成类型化端口,我们应用函数t端口在港口编号:

端口::(显示c,读取c,显示s,读取s)=>端口->端口c s
这个显示阅读签名中的上下文说明不是所有类型都可以用作消息类型。价值观将是转换为文本字符串,然后将它们作为套接字上的消息。这显然不是很有效,但是是实现独立于机器的协议的一种简单方法。

给定一个类型化端口,我们可以通过将计算机指定为主机名:

tServerAddress::t端口c s->主机->tServerAddress c s
例如,假设我们要编写一个运行在主人动物,正在侦听8888端口。客户端将整型消息传输到服务器,服务器反过来发送给客户端的字符串。这可以由
端口::t端口Int字符串端口=端口8888theServerAddr=t服务器地址端口“动物”
键入的服务器地址可以在客户端程序中用于通过打开服务器的套接字tSocketTransceiverF:
tSocketTransceiverF::(显示c,读取s)=>t服务器地址c s->F c(可能是s)
再说一次显示阅读上下文出现了,因为这是从和到文本字符串的实际转换发生。笨蛋tSocketTransceiverF将输出传入消息从服务器作为只是,如果连接被另一端关闭,它将输出没有什么.

在服务器中,我们将等待连接,并创建客户端新客户端连接时的处理程序。这是通过t插座服务器F:

tSocketServerF::(读c,显示s)=>t端口c s->(F s(可能c)->F a(可能b))->F(Int,a)(Int,可能是b)
所以t插座服务器F有两个论点,第一个是为新客户机监听的端口号。第二次参数是客户端处理程序函数。只要有新客户连接后,将创建一个套接字收发器fudget并将其提供给客户机处理程序函数,它生成一个客户机处理程序福吉。然后在内部生成客户端处理程序t插座服务器F.从外部t插座服务器F,的不同的客户端处理程序通过唯一的整数进行区分标签。当客户端处理程序发出没有什么,t插座服务器F会把这解释为连接的结束,杀了处理者。

其思想是客户机处理程序函数应该使用与通信的收发器参数客户。复杂的处理程序可以用环通滤波器如果需要的话。在许多方面尽管如此,所提供的插座收发器作为直接处理客户端。因此,一个简单的套接字服务器可以定义人:

simpleTSocketServerF::(读c,显示s)=>t端口c s->F(Int,s)(Int,可能是c)simpleTSocketServerF port=tSocketServerF端口id

26.4避免客户端和服务器之间的类型错误

使用以下样式开发客户机和服务器,我们可以检测到客户端和服务器在消息类型。

首先,我们定义客户端和服务器。我们把这个定义放在its的一个模块中拥有。假设客户机向服务器发送整数反过来可以发送字符串:

模块我的港口哪里myPort::TPort Int字符串我的端口=端口9000
我们选择了任意端口号。现在,如果客户具体如下:
模块哪里 --客户
  进口我的港口...main=fudlogue(…tSocketTransceiverF myPort…)
服务器呢
模块哪里 --服务器
  进口我的港口...main=fudlogue(…tSocketServerF myPort…)
然后编译器可以检查我们是否不尝试发送消息类型不对。当然,这不是万无一失的。总是存在编译版本不一致的问题例如,客户机和服务器。或者你可以用客户端和服务器中的不同端口声明。

现在,如果我们忘记在我的港口? 那么我们不可能得到不一致的信息吗类型,因为客户端和服务器可以实例化我的港口不同类型的人?直接的答案是不,而这个呢是因为Haskell的一个微妙的特性,即单态限制。此限制的后果是那种我的港口不能包含任何类型变量。如果我们忘记了类型签名,这将是如果是这样,编译器就会抱怨。有可能通过在不过,类型签名。如果我们在定义类型化时这样做波茨,我们朝自己的脚开枪:

模块我的港口哪里myPort::(读取a,显示a)=>t导入字符串--错了!我的端口=端口9000
我们说这是最直接的答案。这个真实的回答如果程序员使用HBC,我们可能会变得不一致消息类型,因为可以给编译器标志关闭单态性限制检查一下。这是我们经常使用的功能(另请参见第40.1条).

26.5示例:组日历

在我们部门的午餐室外面,有一块白板一周活动的登记处。我们来看看电子版的日历,人们可以在这里查看就像在他们的工作站上(图60).

图60.日历客户端。

每个人都可以编辑日历中的条目。什么时候所有日历客户端都应立即更新。

日历由维护数据库的服务器组成,以及在工作站上运行的客户端。

26.5.1日历服务器

服务器的工作是维护包含所有条目的数据库在白板上,接收来自客户端和然后更新其他连接的客户端。服务器包括流处理器数据库SP,和t插座服务器F,从流处理器输出的位置t插座服务器F,反之亦然(图61). 程序出现在图62.

图61.结构服务器. The小fudget是在套接字中创建的客户端处理程序服务器。

模块哪里 --服务器

  进口胡说八道进口我的港口(我的港口)main=fudlogue(服务器myPort)数据HandlerMsg a=NewHandler | HandlerMsg a服务器端口=loopF(databaseSP[][]>^=<tSocketServerF端口clienthandler)clienthandler收发器=putSP(Just NewHandler)(mapSP(map HandlerMsg))>^^=<收发器数据库sp cl db=getSP$\(即e)->clbuti=过滤器(/=i)cl在里面 案例e属于仅处理msg->案例手柄消息属于新处理程序->--一个新客户,把数据库发送给它,
                  --并添加到客户列表中。PUTSP[(i,d)| d<-db]$数据库SP(i:cl)数据库HandlerMsg s->--告诉其他客户,PUTSP[(i',s)| i'<-clbuti]$--更新数据库。数据库sp cl(替换s db)没有->--客户端已断开连接,请将其从
               --客户列表。数据库SP clbuti数据库替换:(公式a)=>(a,b)->[(a,b)]->[(a,b)]替换=。。。

图62.日历服务器。

流处理器数据库SP保持两个值:客户名单,它是连接的客户机和简单的数据库分贝,有组织作为(键、值)对的列表。此数据库新发送到连接的客户端。当用户更改客户机中的条目时,它会将该条目发送到服务器,服务器将更新数据库,并使用客户端列表将新条目广播到所有其他连接的客户端。当客户端断开连接时从客户端列表中删除。客户端处理程序(clienthandler公司)最初用新处理程序,然后他们申请手柄消息到输入端信息。数据库中的(键、值)对的类型是相同的作为接收和发送的消息的类型,并且被定义在模块中我的港口:

模块我的港口哪里
  进口胡说八道类型SymTPort a=端口amyPort::SymTPort((String,Int),字符串)--例如((“Torsdag”,13),“Doktorandkurs:”)端口=端口8888

27显示和操作图形对象

到目前为止,我们已经看到fudgets可以显示文本,但是我们没有了解了如何创建和显示其他类型的图形物体。(您可能想知道按钮边框是如何绘制的,因为示例。)在本章的前几节中,我们将介绍用于处理图形的数据类型、类型类和fudget。

各种各样的结构编辑器都是可以很好地发挥作用的程序图形的使用。这类程序的例子是绘图程序,所见即所得字处理器、文件管理器等其特点是允许您操作图形在屏幕上表示某个对象,例如,通过选择对象的一部分,并对其执行一些编辑操作(例如,在字处理程序中使字倾斜,或删除文件在文件管理器中)。用户执行的编辑操作可以导致物体结构的边缘或根本性改变以及它的图形表示。编辑需要一个更新屏幕以反映这些问题的有效机制变化。我们在本章中描述的图形的模糊性支持这一点。

我们目前看到的Fudget库组件允许您构建由多个部分组成的用户界面,但我们还没有看到任何机制允许任意部分被用户选择,或者被其他东西替代,所以我们还没有看到建筑结构的一般机制编辑。一些基本的谎言,比如切换按钮可以看作特定结构的结构编辑器(布尔和字符串,分别)。本章后面几节介绍数据类型在建造更多建筑时,可以把它作为一个起点通用结构编辑器。第二十八章我们继续描述更直接针对建筑结构的组合词编辑器,或语法指导的编辑器。

在Fudget库中支持图形是由语法导向编辑器Alfa的开发(第三十三章),和功能被添加到fudget系统中特殊目的。这项工作也促进了一些发展在中描述的web浏览器上第三十二章.

27.1课程图解的

我们已经遇到了这个班图解的很多次。很多鬼东西呈现于第九章显示图形。例如,回想一下,按钮:

按钮:: (图解的a) =>a->F 点击 点击
有一个论点是确定按钮内显示的内容。在早期版本中福吉特图书馆的按钮

按钮F::String->F Click Click
但后来,班上图解的被介绍了,还有很多假惺惺的从只显示字符串到显示任意图形对象。因为新的类型更通用与旧版本相比,这些更改是向后兼容的(旧的程序继续工作,未经修改)。(脚注:这种更改实际上会导致不明确的重载。)

这个图解的类的作用与显示类:生成其值具有图形表示形式的类型的实例图解的类,就像值为文本表示是显示班级。就像是显示类的方法图解的不经常使用类直接,除非在定义新实例时,我们在后面的章节。库在中提供实例图解的对于许多标准类型。

27.2原始绘图操作

在我们描述作为图解的类,我们来看看底层接口让一个蠢货在窗户里画东西。

Fudgets GUI工具包构建在Xlib之上[纽约90]X Windows系统的库级别[86新加坡元](如上所述在里面第22.1条). 这件事在福吉图书馆里一览无余图形支持:中提供的基本图形操作fudget库与Xlib提供的库直接对应。

Xlib库的接口调用绘制几何图形和字符串是通过数据提供的类型DRAW命令显示在图63.

数据DRAW命令=牵引绳|DrawImageString点字符串|拉绳点绳|DrawRectangle矩形|圆角矩形矩形|填充多边形形状坐标模式[点]|DrawArc Rect Int Int内部|FillArc Rect Int整数|复制区可绘制矩形点|复制平面可绘制矩形点Int|牵引点|CreatePutImage Rect ImageFormat[像素]|拉线坐标模式[点]...

图63.类型DRAW命令提供Xlib的接口图书馆要求绘制几何图形和字符串。

除了描述要绘制的形状的参数外Xlib调用具有一些在中不存在的附加参数的构造函数DRAW命令类型。作为一个典型Xlib调用与建造师,考虑一下X绘图线:

XDrawLine(显示、d、gc、x1、y1、x2、y2)显示*显示;可提取d;气相色谱仪;内景x1,y1,x2,y2;
可拉拔的d(一个窗口或一个pixmap)和图形上下文气相色谱仪输出绘图命令的fudget。类型X命令(参见第22.1.1条)包含以下用于输出绘图命令的构造函数:

数据XCommand=…|绘制可绘制GCId DrawCommand |。。。
显示器参数可以从drawable中确定。(当前的Fudget库只支持一个显示连接,因此没有额外的支持需要这样做。)

27.3简单图形对象的类型

看了一个傻瓜如何输出绘图命令来画画它的窗户,我们现在可以看看图形对象的一些简单类型。这些类型为Xlib提供了最底层的接口绘图命令。

27.3.1款位图文件

除了通过类型支持的绘图命令DRAW命令,Fudget库也支持Xlib图书馆呼叫XReadBitmapFile文件用于读取图像(位图)从文件:

数据XRequest=…| ReadBitmapFile文件路径|。。。数据XResponse=…|位图读取BitmapReturn |。。。数据BitmapReturn=BitmapBad | BitmapReturn大小(可能是点)PixmapId
这意味着我们可以轻松地创建一个允许我们使用存储在文件中的图像作为图形对象。

数据BitmapFile=位图文件路径实例图形位图文件哪里...
如你所见图64,通过使用类型位图文件,从文件加载图像并显示它就像“你好,世界!”程序(参见第9.1条):

进口胡说八道main=fudlogue(shellF“你好”helloF)helloF=labelF(位图文件“hello.xbm”)

图64“你好,世界”的图形化版本程序与中的文本版本一样简单第9.1条.

27.3.2款弹性拉伸

福吉图书馆提供以下类型可创建可拉伸的图形对象:

数据FlexibleDrawing=FlexD Size Bool Bool(矩形->[DrawCommand])实例图形灵活绘图哪里...
的第一个参数弯曲的构造函数指示名义尺寸,但实际尺寸由fudget决定布局系统和取决于上下文。接下来的两个参数表示延展性,即大小应分别水平和垂直固定。

最后一个参数是一个应该生成绘图的函数在给定矩形内绘制的命令。论点是长方形而不仅仅是一个尺寸,使灵活的绘图更加灵活高效地用作结构化图形的一部分物体。虽然绘图功能可以完全绘制对于不同的矩形位置和大小,改变位置除了翻译,也就是说,

f(矩形位置尺寸)=移动绘图命令(f(矩形原点尺寸))位置
哪里MoveDraw命令,

moveDrawCommands::[DrawCommand]->Point->[DrawCommand]
移动(转换)绘图命令。更改大小将使函数调整图形以填充可用空间,通常通过拉伸它。

举个例子,这里有一些填充矩形、水平线和垂直线:

filledRect,hFiller,vFiller::Int->FlexibleDrawingfilledRect=filler False假hFiller=填充假真vFiller=filler真假filler fh fv d=挠曲d(点d d)fh fv(\r->[FillRectangle r])
示例用法见图66.

27.3.3固定尺寸图纸

定义了类型弹性拉伸,我们可以轻松定义用于创建固定尺寸:

fixedD::Size->[DrawCommand]->FlexibleDrawingfixedD SIZED dcmds=弹性尺寸真实图纸哪里drawit(Rect pos∗)=移动DRAW命令dcmds pos
参数是绘制所需图形命令的列表形状和尺寸。命令应该在指定大小的矩形,以原点为上左角(脚注:而不是让用户指示图形的大小,可以通过检查绘图命令计算边界矩形,但在一般情况下准确地做这件事是相当复杂的而且效率会降低。)

请注意,这取决于您如何定义弹性拉伸价值,你可能会得到非常不同的操作行为。使用固定的,您将得到一个包含对将保留的绘图命令列表的引用并转换到适当的位置(通过MoveDraw命令)每次使用图形时。弹性拉伸我们创造了填料上面是图纸每次绘制图形时,命令可能会重新计算并丢弃被使用。所以,虽然屏幕上的结果是一样的,发生了多少重新计算以及使用了多少内存取决于程序是如何编写的以及什么类型的编译器执行的lambda提升(是否支持完全懒惰[卡尔92]).

27.4结构化图形对象的类型

类型对于上述图形对象缺少两个重要的特征:
  • 指定绘图属性(如颜色、线条)的能力宽度和字体。
  • 将简单对象组合成以简单的方式指定布局的较大的。
如前所述在本章的引言中,我们还需要一种方法来识别构建结构时复合图形对象的部分编辑。我们介绍这个类型绘图照顾这些需要。

数据图纸标签页=原子叶|标签(图纸标签页)|属性GCSpec(图纸标签页)|SpacedD垫片(图纸标签页)|放置器(图纸标签页)|复合[图纸标签页]实例Graphic leaf=>图形(图形标签叶)哪里...placedD::Placer->[绘图l a]->绘图l aplacedD p ds=placedD p(复合ds)
所以,合成图就是树。树叶(用建造师原子)可以包含任何类型的值,但是从上面的实例声明中,可以仅当叶类型是图解的班级。内部节点可以包含:
  • 绘图属性(构造器属性)那是有效的在节点的子树中。这些将在下面进一步讨论。
  • 以垫片和放置器的形式显示的布局信息施工人员SpacedD地点)从普通布局系统(第十一章).
  • 可以用来识别的标签,或者只是保存一些额外的有关的信息,图形的一部分(图建造师拉伯德). 这些没有图形效果。
  • 构图(施工人员沉着的).大多数时候也可以指定布局,因此使用构造函数沉着的直接使用函数地点.
自从绘图类型是图解的类,图形可以由创建标签的GUI fudget显示,按钮,菜单,显示器等等。还有一个使属性的使用绘图类型:

超图SF::(Eq lbl,Graphic gfx)=>图纸lbl gfx->F(lbl,图纸lbl gfx)lbl
它显示带有标签的图形。当你点击一个点在绘图中,fudget输出最小部分的标签包含您单击的点。可以通过以下方式替换零件将一对标签和一个新图纸输入到福吉。超图因此可以作为简单图形浏览器和编辑器。

27.4.2在一张图纸中混合不同类型的图形对象

在一个绘图,所有的叶子必须具有相同的类型。尽管你可以只使用类型的叶绘制任何内容弹性拉伸,如果能混在一起会更方便不同类型的叶子。为此,福吉特图书馆提供以下类型,该类型使用存在的量化类型[LO92型]:

数据Gfx=(图a)=>G?实例图形Gfx哪里...--琐碎的g::图形a=>绘图lbl Gfxg=原子数g
在定义中Gfx公司,?是存在的量化类型变量。上下文(图形?a)=>将变量的域限制为图解的班级。结果是构造函数G可以是应用于图解的等级,屈服类型值Gfx公司。以后使用模式匹配时提取论点G,你不会知道是什么类型的它有,但是你会知道类型是在图解的类,因此可以对其应用该类的方法。所以,制作Gfx公司一个图解的类成为琐碎的。(实例声明如所示图71).

字符串和弹性拉伸混合在一个绘图如所示图66.

placedD verticalP[SpacedD centerS(g“1”),g(填充器1),g“x+y”]

图66.一张带有不同的类型。

存在主义类型的使用为我们提供了一种包装方式数据及其操作和抽象的方法数据的具体表示。这让人想起如何实现面向对象的数据抽象编程。(参考读者[第85周]为了更全面地讨论存在类型、数据抽象和面向对象编程。)

27.4.3图纸属性

大多数Xlib绘图命令都有GC类型的参数,图形上下文。这是一个包含影响结果的许多参数的值绘图命令,但这将是令人厌烦的通过每次你画东西的时候都要显式地作为参数。示例这些参数或属性包括:
  • 前景色和背景色,
  • 文本使用哪种字体,
  • 线条宽度、线条样式(例如,实线或虚线)、填充风格。
这些属性大多由数字或元素的枚举类型,但颜色和字体更多麻烦。可使用颜色名称或RGB值,但是在一个颜色可以在GC中使用之前,它必须转换为像素值。取决于视觉类型显示时,一个像素值可以是,例如,一个8位索引到256元素颜色图(用于8位伪彩色显示器)或RGB信息压缩为16位或24位(对于16位和24位真彩色显示器,分别)。

字体可以由字体名称指定,但是它们可以被使用,它们必须被转换成字体标识符s、 另外,如果你想知道文本有多大的空间绘图将占用,您需要获得一个包含字体的度量信息。

提供的数据类型指定绘图属性的Fudget库如下所示。类型颜色规格字体规格将在下一节中进一步描述。

数据GCSpec公司=SoftGC[GCAttributes ColorSpec FontSpec]|硬GC GCtx数据颜色规格--见下文
数据字体规格--见下文

数据GCAttributes颜色字体=GCFunction GCFunction|GC前景颜色|gc背景色|GCLineWidth宽度|GCLineStyle GCLineStyle|GCFont字体|GCCapStyle GCCapStyle|GCFillStyle GCFillStyle|GCTile PixmapId|GCStipple PixmapId...数据GCtx=GC GCId字体结构数据字体结构--字体度量信息的抽象类型
数据GCId--Xlib GC
类型宽度=整数数据 GCFunction函数=GXclear | GXand | GXandReverse | GXcopy |…| GXset数据 GCLineStyle=LineSolid | LineDoubleDash | LineOnOffDash数据 GCCAP样式=CapNotLast | CapButt | CapRound | CapProjecting数据 GCFillStyle=填充实心|填充平铺|填充填充|填充填充
在中包含图形属性的步骤绘图(定义见上文),您可以使用构造函数属性应用于GCSpec公司,通常是构造函数软GC应用包含高级规范的属性列表字体和颜色。但是,在显示图形之前高级规范必须转换为GC。此外,对能够自动确定文本的大小,度量指定字体的信息是必需的。高层因此,图形属性将转换为类型的值GCtx公司由福吉特展示图纸。此转换可能需要调用Xlib库函数XLoadQueryFont,黄色XCreateGC.对于要多次显示的图形,使每次这些调用都会引起显著的性能降级,因此库提供了一种创建GCtx公司中的值前进。然后可以使用GCS规范具有建造师硬GC。然后可以不使用除了必要的绘图命令外,进行任何调用。这个选择名字的原因软GC硬GC这是节点的子图形使用软GC或者,继承gc属性从父图形中列出,而使用这个硬GC另一种选择,全部的属性从给定的GCtx公司.

27.4.4指定字体和颜色

允许字体和颜色以不同的方式方便地指定,我们介绍了以下类型和类别:

色原a哪里...数据颜色规格--抽象类型colorSpec::ColorGen a=>a->colorSpec丰根a哪里...数据字体规格--抽象类型fontSpec::FontGen a=>a->fontSpec
以下类型是的实例着色剂类并可用于指定颜色:

类型ColorName=字符串--Xlib使用的颜色名称

数据RGB=RGB Int Int Int Int--Xlib使用的RGB值

数据像素--先前获得的像素值
RGB类型的值指定红色、绿色和蓝色的原色强度,使用16位整数。RGB 0 0 0是黑色的,而且RGB 6553565535 65535是白色的。

以下类型是丰根类和可用于指定字体:

类型FontName=字符串--Xlib使用的字体名称

数据字体结构--以前获得的字体结构
将字体和颜色规范包含在画画就是这样做的:

蓝地狱=属性(SoftGC[gc前景(colorSpec“蓝色”),GCFont(fontSpec“-*-次-*-r-*-18-*”),(g“你好,世界!”
作为你可以看到,这是相当笨拙,所以福吉图书馆提供以下更方便的功能:

bgD,fgD::ColorGen color=>颜色->绘制lbl叶->绘制lbl叶fontD::FontGen字体=>字体->绘制lbl叶->绘制lbl叶
使用这些,你可以把上面的例子写成这样:

blueHelloMsg=fgD“蓝色”$fontD“-*-次-*-r-*-18-*”$g“你好,世界!”

27.4.5提前分配颜色和字体

如上所述,你可能是为了提高效率原因是要预先分配颜色和字体,包括结果GCtx公司您构造的图形中的值。为了这个Fudget图书馆提供以下内容:

wCreateGCtx::(FontGen b,ColorGen a)=>GCtx->[GCAttributes a b]->(GCtx->F c d)->F c d根GCtx::GCtx
功能创建GCTX允许你创造GCtx公司值,通过修改模板GCtx公司.你可以从根GCTX包含所有属性的默认设置。

27.5实施

怎么应该是一个显示绘图是否实施?图纸是由包含简单图形对象的树叶组成的树,使用普通的fudget布局系统中的放置器和垫片。A因此,自然的解决办法似乎是对显示简单的图形对象,然后显示组合通过创作展示树叶的软糖画。在这个时候第一个似