ZuriHac:练习函数式编程

今年6月,在瑞士小镇拉珀斯维尔(Rapperswil )举行了第十次名为ZuriHac的活动。 这次,有500多个Haskell爱好者从初学者聚集到该语言的创建者。 尽管组织者将此活动称为黑客马拉松,但从传统意义上来说,它仍然不是会议或黑客松。 它的格式与传统编程不同。 我们很幸运地了解了ZuriHac,并参与其中,现在,我们有责任告诉我们一个不寻常的发现!




关于我们


本文是由圣彼得堡高等经济学院应用数学和计算机科学计划的两名3年级学生编写的:Vasily Alferov和Elizaveta Vasilenko。 对我们俩的函数式编程的热爱始于D.N. Moskvin在大学二年级的一系列讲座。 目前,瓦西里(Vasily)参与了Google Summer of Code计划,在Alga项目团队的指导下,他参与了Haskell语言的代数图的实现。 伊丽莎白在课程工作中运用了所获得的函数式编程技能,该课程致力于实现反统一算法并随后在类型理论中使用。

活动格式


目标受众是开源项目的所有者,想要参与其开发的程序员,函数式编程的研究人员以及对Haskell充满热情的人员。 今年,拉珀斯维尔大学高铁技术学院将来自世界各地五十多个开源Haskell项目的开发人员聚集在一起,讨论他们的产品并吸引他们的开发兴趣。



Twitter的照片ZuriHac

该方案非常简单:您需要事先编写一些有关项目的建议,然后将其发送给组织者,组织者将在活动页面上发布有关您项目的信息。 此外,项目的第一天,项目的作者有30秒的时间从舞台上非常简短地讲述他们正在做什么以及需要做什么。 然后,感兴趣的人搜索作者,并详细询问任务。

我们还没有自己的开放项目,但是我们确实想为现有项目做出贡献,因此我们注册为常规参与者。 在三天内,我们与两个开发团队合作。 事实证明,对代码和实时交流的共同研究使项目作者和贡献者之间的互动非常有效-在ZuriHac,我们能够为我们找到新的领域,并能够通过关闭每个项目中的任务来帮助两个完全不同的团队。

除了有价值的实践,ZuriHac还举办了几次讲座和大师班。 我们尤其记得两次演讲。 首先,来自纽卡斯尔大学的安德烈·莫霍夫(Andrei Mokhov)谈到了选择性应用函子-这种类型的类应该成为应用函子和单子之间的中介。 在另一场演讲中,Haskell的创始人之一西蒙·佩顿·琼斯(Simon Peyton Jones)谈到了类型推断在GHC编译器中如何工作。



西蒙·佩顿·琼斯(Simon Peyton Jones)的演讲。 Twitter的照片ZuriHac

黑客马拉松期间举办的大师班根据参与者的培训水平分为三类。 提供给参加项目开发的参与者的任务也具有难度级别的注释。 小型但友好的函数式程序员社区很高兴欢迎新来者加入他们的行列。 但是,要了解Andrei Mokhov和Simon Peyton Jones的讲座,大学通过的函数式编程课程对我们非常有用。

对于普通参与者和项目作者而言,该活动都是免费的。 我们于6月初申请参加,之后我们迅速从等待名单转移到已确认参加者名单。

现在,我们将讨论我们参与的开发项目。

潘多克


实际上, Pandoc是文本文档的通用转换器-从任何格式到任何格式。 例如,从docx到pdf,或从Markdown到MediaWiki。 其作者约翰·麦克法兰(John MacFarlane)是加州大学伯克利分校的哲学教授。 通常,Pandoc非常有名,当我们的一些朋友得知Pandoc是用Haskell编写时,他们感到很惊讶。



Pandoc支持的文档格式列表。 该站点还具有完整的图形,但是此图片不适合本文。

当然,Pandoc不会为每对格式实现直接转换。 为了支持如此广泛的转换,使用了标准的体系结构解决方案:首先,将整个文档转换为特殊的内部中间表示形式,然后从该内部表示形式生成不同格式的文档。 开发人员将内部表示称为“ AST”,它表示“抽象语法树”或“ 抽象语法树” 。 您可以非常简单地查看中间表示形式:为此,您只需将“ native”设置为输出格式

$ cat example.html <h1>Hello, World!</h1> $ pandoc -f html -t native example.html [Header 1 ("hello-world",[],[]) [Str "Hello,",Space,Str "World!"]] 

至少与Haskell有过合作的读者已经可以假设Pandoc是用Haskell专门编写的:此命令的输出是一个Pandoc内部结构表示形式,它是一个字符串,类似于在Haskell中通常的做法,例如,在标准库中。

因此,在这里您可以看到内部表示是一个递归结构,在每个内部节点中都有一个列表。 例如,在最顶层有一个元素的列表-具有“ hello-world”,[],[]属性的第一层标头。 该标题内是字符串“ Hello”,空格和字符串“ World!”的列表。

如您所见,内部表示形式与HTML并没有太大区别。 它是一棵树,其中每个内部节点报告有关其后代格式的一些信息,并且叶子包含文档的实际内容。

如果深入到特定实现的级别,则整个文档的数据类型将如下定义:

 data Pandoc = Pandoc Meta [Block] 

在这里,Block恰好是上面提到的内部高峰,而Meta是关于文档的元信息,例如标题,创建日期,作者-对于不同的格式来说是不同的,并且Pandoc试图在从一种格式转换到另一种格式时尽可能地保存此类信息。

几乎所有Block类型的构造函数-例如Header或Para(paragraph)-都将属性和较低级别的顶点列表-内联作为规则,作为参数。 例如,Space或Str是Inline类型的设计器,HTML标记也被转换成其特殊的Inline。 我们没有任何理由对这些类型给出完整的定义,但是,我们注意到可以在此处看到。

有趣的是,Pandoc类型是一个monoid。 这意味着存在某种空文档,并且文档可以相互堆叠。 编写Reader时使用起来很方便-您可以使用任意逻辑将文档分解成多个部分,分别进行解析,然后将所有内容放到一个文档中。 在这种情况下,将立即从文档的所有部分收集元信息。

例如,从LaTeX转换为HTML时,首先是一个称为LaTeXReader的特殊模块,将输入文档转换为AST,然后是另一个名为HTMLWriter的模块,将AST转换为HTML。 由于采用了这种体系结构,因此不必编写二次转换-为每种新格式编写Reader和Writer就足够了,并且将自动支持所有可能的转换对。

显然,这种架构也有其缺点,软件架构领域的专家早就预言到了这一缺点。 最重要的是更改语法树的成本。 如果更改足够严重,则必须在所有读取器和写入器中更改代码。 例如,Pandoc开发人员面临的挑战之一是支持复杂的表格格式。 现在Pandoc只能在最简单的表中,每个单元格中都有标题,列和值。 假设HTML中的colspan属性将被忽略。 出现这种现象的原因之一是,缺少所有或至少许多格式的单个表表示方案-因此,不清楚应以哪种形式将表存储在内部表示中。 但是即使选择了特定的视图,也必须绝对更改所有支持使用表的Reader和Writer。

Haskell不仅从对函数式编程的作家的热爱中脱颖而出。 Haskell以其强大的文字处理能力而闻名。 一个例子是parsec库,该库积极使用函数式编程的概念( monoid ,monads,应用函数和替代函子)来编写任意解析器。 在HaskellWiki 示例中可以看到Parsec的全部功能,该示例解析了一种简单的命令式编程语言的完整解析器。 当然,Parsec也被Pandoc积极使用。

简而言之,当一个先出现然后另一个出现时,monad用于顺序解析。 例如,在此示例中:

 whileParser :: Parser Stmt whileParser = whiteSpace >> statement 

首先,您需要考虑一个空间,然后考虑-也具有Parser Stmt类型的语句。

如果解析失败,则使用备用函子进行回滚。 举个例子

 statement :: Parser Stmt statement = parens statement <|> sequenceOfStmt 

意味着您需要尝试读取括号中的语句,或者依次尝试读取多个语句。

适用函子主要用作monad的快捷方式。 例如,让tok函数读取某种令牌(这是LaTeXReader的真实函数)。 让我们来看看这样的组合

 const <$> tok <*> tok 

她将连续读取两个令牌并返回第一个。

Haskell对所有这些类都有漂亮的符号运算符,这使编程阅读器看起来像ASCII艺术。 只需欣赏这段精彩的代码即可。

我们的任务与LaTeXReader有关。 Vasily的任务是支持\ mbox和\ hbox命令,这在用LaTeX编写软件包时非常有用。 伊丽莎白负责\ egraph团队的支持,该团队允许在LaTeX文档中执行egraph。

痕量


在类似UNIX的操作系统上,通常执行ptrace系统调用。 它在调试和模拟程序环境中很有用,可让您跟踪程序进行的系统调用。 例如,非常有用的strace实用程序在其内部使用ptrace。

Hatrace是一个为Haskell中的ptrace提供接口的库。 事实是,ptrace本身非常复杂,直接使用它非常困难,尤其是从功能语言中使用时。

Hatrace在启动时像strace一样运行,并接受类似的参数。 它与strace的不同之处在于,它还是一个提供比ptrace更简单的接口的库。

Hatrace已经在Haskell GHC编译器中捕获了一个令人不快的错误-在错误的时间被杀死时,它会生成错误的目标文件,并且在重新启动时不会重新编译它们。 通过系统调用编写脚本,可以确保一次运行即可重现该错误,而随机杀死事件会在大约两个小时内重现该错误。

我们向库添加了系统调用接口-Elizabeth添加了brk,Vasily添加了mmap。 根据我们的工作结果,在使用库时,我们可以更轻松,更准确地使用这些系统调用的参数。

Source: https://habr.com/ru/post/zh-CN460537/


All Articles