为什么是Jaop? 为什么要用球拍?

这是文章“为什么要用球拍?”的续篇 为什么使用Lisp?” 我发现Racket大约一年后才写信。 作为一个新手,我无法理解各方对Lisp的赞美。 我不知道该怎么想。 如何理解Lisp最终会引起“深刻的启迪” 。 好吧,兄弟,你说。

我有一个简单的问题:有什么用? 在上一篇文章中,我试图回答它,并总结了为什么有人想要学习Lisp或尤其是Racket的原因。

作为Racket的新手,我已经列出了对我来说最有价值的九种语言功能列表。 例如,功能号5是“创建新的编程语言”。 此方法也称为面向语言的编程JOP

从那时起,IOP成为了我最喜欢的球拍部分,我在网上书Beautiful Racket中分享了我的钦佩之情,其中介绍了ROP技术和Racket工具。

我的作品中的一个例子是花粉 。 我编写此编程语言是为了方便排版我的在线图书。 在花粉中,上一段编程如下:

#lang pollen     ◊link["https://beautifulracket.com/appendix/why-racket-why-lisp.html#so-really-whats-in-it-for-me-now"]{  },        Racket. ,  № 5 — «   ».     ◊em{- },  ◊em{}. 

另一个示例是brag ,它是解析器生成器(采用lex/yacc的样式),该生成器将BNF语法作为源代码。 bf语言的一个简单示例:

 #lang brag bf-program : (bf-op | bf-loop)* bf-op : ">" | "<" | "+" | "-" | "." | "," bf-loop : "[" (bf-op | bf-loop)* "]" 

两种语言都在Racket中实现,并且可以使用常规的Racket解释器或在Racket IDE(称为DrRacket)内部运行。

主要问题


然而……尽管事实上这本书已经迫使成千上万的人开始探索球拍,但在我看来,有时候我的步伐与我曾经批评过的Lisp迷一样。

如果NOP太酷了,那为什么还要花几天时间看书。 对不对 我可以简单地解释所有内容,而无需费力。 需要回答两个简单的问题:

  1. 哪些问题最适合语言编程?
  2. 为什么Racket最适合创建语言?

第二个问题很简单。 第一个不是。 我多次被问到他。 我经常引用波特·斯图尔特法官的一句名言:看到它,您就会理解它。 对于那些真正感兴趣的人来说,答案就足够了。 但是,对于那些愿意听取实质性论点的人而言,却不是这样。

所以,我会尝试。 请记住,我不是计算机科学教授,不能谈论编程语言理论。 相反,我出于实际目的使用了球拍和领域特定的语言(DSL):我的日常工作取决于它们。 因此,我将专注于实践方面。

简短答案


  1. JOP实际上是一种界面设计方法。 对于需要最少符号同时保持最大准确性的任务来说,它是理想的选择。 最小符号表示唯一允许的符号。 没什么 您可以说最大精度,即该符号的含义。 没有歧义或模式。 IOP达到了无与伦比的地步。

    (不耐烦的人可以继续进行特定类别的任务 ,这将从NOP中受益)。
  2. 球拍由于其宏观系统而非常适合OOP。 它们以编译器样式工作,简化了代码转换。 Racket宏系统比其他任何系统都要好。

此时,本文的一半读者将希望发表批评我的论文的匿名评论。 但是请记住:无论如何,我赢了。 JOP和Racket极大地提高了我的编程效率。 我很高兴分享这些知识,以便您也可以利用这些好处。 但是,如果这些工具仍然是我的秘密武器,我也将感到高兴。 在这种情况下,我将留在0.01%的最有效率的程序员中,与其余99.9%的人相比,我得到的结果更加令人印象深刻和更有利

所以选择是您的。

长答案


如果您考虑最重要的问题,这些问题可以归结为一个元问题:为什么难以解释核武器的好处?

也许当我们谈论语言时 ,这个术语充满了对语言的含义和作用的期望。 虽然我们处于这种范式之内,但很难理解编程语言的价值。

但是,如果您减小规模并将语言视为更广泛的人机界面类别的一部分,那么就很容易看到OOP的特定优势。 因此,让我们开始吧。

通用语言和面向主题的语言


首先,一些术语。 面向语言的编程(也称为OOP)是通过创建新语言然后在其上编写程序来解决编程问题的想法。 通常,这种“小语言”称为领域特定语言(DSL)。

顾名思义,面向主题的语言适用于特定领域的任务。 例如,PostScript,SQL, make ,正则表达式, .htaccess和HTML被认为是面向主题的语言。 他们没有尽一切努力。 相反,他们专注于做好一件事情。

在另一端是通用语言 。 在这里,我们看到C,Pascal,Perl,Java,Python,Ruby,Racket等。为什么不将它们视为面向主题的? 因为它们将自己定位于各种计算任务。

实际上,通用语言通常专门研究特定领域。 例如,对于系统编程,C优于其他语言。 Perl-用于系统管理中的脚本。 Python是一种适合初学者的语言。 面向语言编程的球拍。 在每种情况下,这都是该语言最初设计的目的。

DSL和通用语言之间有一条很好的界限。 例如,Ruby是作为通用语言创建的,但通过与Ruby on Rails的关联,主要在Web应用程序中流行。 另一方面,JavaScript最初是Web浏览器脚本的面向主题的语言。 但是他像病毒一样变异,从那以后,它的发展远远超出了最初的任务。

什么是语言?


如果将整个范围称为语言,那么语言的定义特征是什么?

我知道您在想什么:“这是您弄错的地方。 HTML不是一种语言。 这只是标记。 他无法描述算法。” 或者:“正则表达式不是一种语言。 他们不能自己工作。 这只是另一种语言的语法。”

我也曾经这样认为。 但是我看得越近,这些分歧显得越模糊。 因此,我的第一个主要声明(三分之二)是:编程语言本质上是一种交换媒介- 人们和计算机都可以理解的符号系统

符号系统(notation)表示该语言具有语法。 “清除”是指该语言以其语法传达含义 (或语义 ,如果使用的是更高级的单词)。 该定义涵盖所有通用编程语言。 和所有DSL。 (但不是每个数据流,这将在后面详细讨论)。

(顺便说一句,尽管“编程”和“语言”是惯用的单词,但这些语言不仅被人们用来对计算机编程,而且有时被计算机用来与我们交流(例如,S表达式),有时彼此交流(例如XML,JSON,HTML。)当然,排除这些功能似乎是错误的。但是实际上,是的-实际上,我们通常使用编程语言来进行编程。

考虑一下HTML代码:一种告诉计算机(特别是Web浏览器)如何绘制网页的方法。 这是人类和计算机可以理解的符号系统(尖括号,标签,属性等)( charset属性指示字符编码, p标签包含段落等)。

这是一个小的HTML页面:

 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>My web page</title> </head> <body> <p>Hello <strong>world</strong></p> </body> <html> 

假设您不同意HTML是一种编程语言。 好啊 我们将用Python显示页面。 这是一种真正的编程语言,对吗?

 print "<!DOCTYPE html>" print "<html>" print "<head>" print "<meta charset=\"UTF-8\">" print "<title>My web page</title>" print "</head>" print "<body>" print "<p>Hello <strong>world</strong></p>" print "</body>" print "<html>" 

如果Python是一种编程语言,而HTML不是,那么此Python示例是一个程序,但HTML示例不是。

显然,这是一个折磨。 在这里,python化除了增加复杂性和刻板印象外,没有添加任何其他内容。 辛辣的是,从管理Web浏览器的角度来看,Python程序中唯一有趣的语义内容是将其嵌入HTML中(也许可以将DOCTYPEmetastrong等HTML标记视为带有参数的函数)。 逻辑使我们得出结论,HTML尽管更简单,灵活性更低,但仍然是一种编程语言。

嵌入式语言


我们提出了一个有关HTML和Python的示例。 但是将DSL嵌入另一种语言是无处不在的。 以这种方式使用的语言称为Embedded 。 它们代表了语言编程的最常见形式。 作为程序员,即使您不知道JOP的名称,您也已经依靠JOP多年了。

例如,正则表达式(其他示例: printf用于格式化字符串,CLDR用于日期/时间模式,SQL)。 我们不能将正则表达式视为一种独立的语言。 但是每个程序员都知道它是什么:

 ^fo+(bar)*$ 

此外,您可能可以使用自己喜欢的编程语言输入此正则表达式,它将正常工作。 仅因为正则表达式表示法是一种外部语言( POSIX ),所以这种一致的行为才有可能。

与HTML一样,我们可以用宿主语言符号编写等效的表达式。 例如,Racket支持Scheme正则表达式 (SRE):这些是带有S表达式符号的正则表达式。 上面的模板将这样写:

 (seq bos "f" (+ "o") (* (submatch "bar")) eos) 

但是Racket程序员很少使用SRE表达式。 它们太长,难以记住。

嵌入式DSL的另一个无处不在的例子:数学表达式。 每个程序员都知道这意味着什么:

 (1 + 2) * (3 / 4) - 5 

单靠数学表达式并不能创建有趣的程序。 我们需要将它们与其他语言构造结合起来。 但是,与正则表达式一样,这是符合人体工程学和实用的记录。 数学表达式具有自己的符号和含义,无论是人还是计算机都可以理解,因此它们可以作为独立的内置语言使用。

您是在开玩笑HTML正在编程吗?


不,是。 我肯定HTML(正则表达式和数学表达式)都可以用作基本的编程语言。 这意味着编写HTML(或正则表达式或数学表达式)符合基本编程的要求。

请不要惊慌。 当然,在LinkedIn上只有HTML和算术知识的“程序员”是胡说八道(尽管一周之内他可能会得到价值18万美元的工作)。 但这是一个单独的问题,“程序员”在劳动力市场中意味着什么。 我们不是在谈论那个。

图灵全面陷阱


如果编程语言的这种定义仍然困扰您,也许您认为一种真正的编程语言应该表示所有可能的算法-也就是说,它应该是图灵完整的

我理解这样的想法凭直觉表明了自己。 每种通用编程语言都是图灵完整的。

但是问题在于这是一个低门槛。 图灵完整性是一项技术指标,与现实世界中的语言使用情况不符。 例如,正则表达式不是图灵完整的,但是在以最小符号表示许多计算时,它们很有用。 HTML也不是图灵完整的,但是它是控制浏览器的有用方法。 相反, bf语言是图灵完整的,但是即使是最琐碎的任务也需要数千米的无法通过的代码。

语言限制


我对语言的定义有什么要求吗? 不行

  • 二进制数据格式不被视为语言。 例如,一个jpeg文件。 尽管计算机可以理解它们,但一个人却不能。 或PDF:如果您对其进行破解,则其中的某些部分是易于阅读的。 但这是由于PDF的工作原理。 使用PDF结构编写任何构想都是没有意义的。
  • 文本文件不是语言。 假设我们有荷马的伊利亚特(Iliad)文件。 我们人类可以阅读和理解它。 尽管计算机可以通过打印文件内容等方式来处理文件,但是计算机内部的文本是无法理解的。
  • 图形用户界面不是语言。 是的,这些是符号系统(依赖于文本和图像)。 但是它们只有人们才能理解。 计算机绘制一个GUI,但不理解它们。

语言作为界面


上面,我将编程语言描述为人与计算机之间的“交换媒介”。 因此,语言可以归入更广泛的类别,我们称之为interfaces

这引出了第二个基本陈述(三分之二): 语言编程基本上是一种设计接口的方法 。 如果您想考虑接口,那么您会喜欢JOP。 否则,您仍然会喜欢JOP,因为它使可能无法实现的接口成为可能。

我最喜欢的一种语言作为接口示例是brag ,它是用Racket创建的解析器生成器语言。 如果您曾经使用过lex / yacc工具链,那么您就会知道目标通常是根据BNF语法生成解析器。 例如,对于bf,它看起来像这样:

 bf-program : (bf-op | bf-loop)* bf-op : ">" | "<" | "+" | "-" | "." | "," bf-loop : "[" (bf-op | bf-loop)* "]" 

要使用通用语言创建解析器,您需要将此语法转换为一堆本机代码。 这是一项繁琐的工作。 毫无意义-我们还没有写下语法吗? 为什么要再做一次?

然而, brag实现了我们的愿望。 要创建解析器,我们只需在文件中添加#Lang brag行, #Lang brag将BNF语法神奇地转换为brag的源代码:

 #Lang brag bf- : (Bf-op | Bf-loop)* bf-op : ">" | "<" | "+" | "-" | "."| "," Bf-loop : "["(Bf-op | Bf-loop)* "]" 

做完了! 在编译时,此文件导出parse函数,该函数实现此BNF语法。

这是我最喜欢的示例之一,因为它无疑优于其他选项。 而且,对于通用语言而言,这样的接口实际上是不可能的。

但是JOP程序员一直在制作这样的接口。

语言是最佳界面


这使我想到了第三个也是最后一个基本论点,即语言相对于接口具有独特的优势 。 当然,以下类别不是穷举或排他的。 但是我发现IOP在以下情况下可以提供很多帮助:

1.当您要为技术水平较低的程序员,非程序员或懒惰的程序员创建接口时(请不要低估后一类的规模)。

例如,Racket具有完善的Web应用程序库 。 但是,也可以使用web-server/insta语言快速启动简单的Web服务器:

 #lang web-server/insta (define (start request) (response/xexpr '(html (body "Hello LOP World")))) 

马修·弗拉特(Matthew Flatt)在他的文章《在球拍中创造语言》中展示了可产生可玩文字冒险的语言。 像brag一样,它看起来更像是一个规范而不是程序,但是它可以工作:

 #lang txtadv ===VERBS=== north, n "go north" south, s "go south" get _, grab _, take _ "get" ===THINGS=== ---cactus--- get "Ouch!" ===PLACES=== ---desert--- "You're in a desert. There is nothing for miles around." [cactus, key] north meadow south desert 

2.当您想简化表示法时。 正则表达式就是一个例子。 另一个例子是我的面向主题的语言花粉,用于编写在线书籍。 Pollen与Racket相似,只是在这里您以文本模式开始工作,并使用特殊字符来指示嵌入在内容中的Racket命令(Pollen基于称为Scribble的Racket文档语言,它承担了大部分负载)。 因此,本段的开头编程如下:

     .    —  .   —  -  ◊link["https://pollenpub.com/"]{Pollen}   -. 

Pollen关心粘贴所有必要的标记并将它们转换为可靠的HTML。 我仍然拥有手动标记的全部优点(可以完全控制页面),但是没有缺点(例如,我不能不小心留下未封闭的标签)。

简化符号的另一个示例是lindenmayer ,即Lindenmayer系统的分形生成和绘图语言,如下所示:



在典型的球拍中,Lindenmayer程序可能如下所示:

 #lang racket/base (require lindenmayer/simple/compile) (define (finish val) (newline)) (define (A value) (display 'A)) (define (B value) (display 'B)) (lindenmayer-system (void) finish 3 (A) (A -> AB) (B -> A)) 

但是您可以通过简单地更改文件顶部的#lang表示法来使用简化表示法:

 #lang lindenmayer/simple ## axiom ## A ## rules ## A -> AB B -> A ## variables ## n=3 

该语言假定您已经熟悉L系统。 但是,简化的表示法可以很容易地在执行所需功能的程序中写下您的愿望。

3.当您要使用现有符号时。 上面我们看到了brag如何使用BNF语法作为源代码。

 #lang brag bf-program : (bf-op | bf-loop)* bf-op : ">" | "<" | "+" | "-" | "." | "," bf-loop : "[" (bf-op | bf-loop)* "]" 

另一个例子。 尝试过Pollen的人说:“是的,很棒,但我更喜欢Markdown。” 没问题: pollen/markdown是一种花粉方言,它提供花粉语义,但接受通常的pollen/markdown表示法:

     .    —  .   —  -  [Pollen]("https://pollenpub.com/")   -. 

最愉快的事情? 我仅用一个小时就编写了该方言,将Markdown解析器与现有代码结合在一起。

4.如果要为其他语言创建中间目标。 JSON,YAML,S表达式和XML都是面向主题的语言,它们定义了用于机器读写的数据格式。

在Perfect Racket中,一种训练语言称为jsonic 。 它允许您将Racket表达式嵌入JSON,从而使JSON可编程。 源代码如下所示:

 #lang jsonic // a line comment [ @$ 'null $@, @$ (* 6 7) $@, @$ (= 2 (+ 1 1)) $@, @$ (list "array" "of" "strings") $@, @$ (hash 'key-1 'null 'key-2 (even? 3) 'key-3 (hash 'subkey 21)) $@ ] 

编译为常规JSON:

 [ null, 42, true, ["array","of","strings"], {"key-1":null,"key-3":{"subkey":21},"key-2":false} ] 

5.当程序的主要部分是配置时。 例如,点文件可以描述为DSL。 Racket的一个更复杂的示例是Jesse Alama的Riposte,这是一种用于测试基于JSON的HTTP API的语言:

 #lang riposte $productId := 41966 $qty := 5 $campaignId := 1 $payload := { "product_id": $productId, "campaign_id": $campaignId, "qty": $qty } POST $payload cart/{uuid}/items responds with 200 $itemId := /items/0/cart_item_id GET cart responds with 200 

作为一种微型脚本语言,Riposte比普通的点文件要聪明得多。 它隐藏了HTTP事务所需的所有中间代码,并允许用户专注于编写测试。 它仍然是房屋清洁。 但是至少您可以专注于您关心的家庭。

为什么要用球拍?


JOP的批评者通常会问:“为什么要制作面向主题的语言? 编写本机库更容易吗?”

不,如果您拥有合适的工具,这并不容易。 球拍很不寻常:它是专为YOP设计的。因此,在Racket上实施DSL比其他方法更快,更便宜,更容易。例如,在我的书的第一节课中,我展示了如何在一个小时内开发一种新语言-即使您从未使用过Racket。

在Racket中每个DSL的背后,源到源编译器实际上都可以工作,它将DSL符号和语义转换为等效的Racket程序。因此,Racket DSL的运行速度不如手动编写的C代码快,但是每个DSL都可以使用所有的Racket工具和库。您的生产力下降了,但屡屡赢得便利。当创建DSL既方便又容易时,它成为解决更多问题的现实选择。

因此,要回答批评-不,DSL不一定比本地库需要更多的工作。而且,正如我们已经看到的,作为一种接口,一种语言可以完成本机库无法完成的工作。

为什么是宏?


由于所有DSL都已编译到Racket中,因此程序员必须编写一些语法转换器,将DSL表示法转换为本地Racket。这些语法转换器称为实际上,它们可以描述为Racket编译器的扩展。

球拍微距系统宽敞,优雅,无疑是其皇冠上的明珠。我的书中大部分是关于使用Racket 的乐趣我可以列举两个出色的功能:

  1. Racket , . . , , Racket , , , , , . ( . « » ).
  2. 拍子宏很卫生,也就是说,默认情况下,由宏创建的代码会保留从中定义宏的词法上下文。实际上,这消除了DSL通常所需的大量不必要的手势(有关更多详细信息,请参见“卫生”一章)。

可以在Python中实现DSL吗? 当然可以实际上,我是用Python专门编写了我的第一个DSL,并且仍然在我的字体设计工作中使用它好吧 一次就足够了。从那时起,我一直在使用球拍。

结论:YaOP的胜利


目前,您可能会有以下两种反应之一:

  1. « , , » . , . , , . . . , . .
  2. «, , c Racket » . , - Riposte , — ( ):

    [ ] - Racket. , , - … : « API , ?» : « Riposte». , , [DSL], , . «» Racket. DSL , .

在文章“为什么要用球拍?为什么使用Lisp?” 我说过Lisp语言“为您提供了释放作为程序员和思想家的潜力的机会,从而提高了您对实现目标的期望。”

IOP提供了类似的机会:提高我们对编程语言可以为我们做什么的期望。语言不是黑匣子。这些是我们可以设计的接口。同时,我们为程序的帮助开辟了新的可能性。

如果可以找到最佳的编程技术,请使用它。现在我有了OOP和Racket,我再也不会回来了。

进一步阅读


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


All Articles