读取25分钟

宣言编程:它是真的吗?

简约软件制造商。设计,写入,测试,部署和维护解决真实的真实系统(如果无聊)问题。
阅读 西班牙语 es. 本文的版本翻译 Marisela Ordaz.

目前,声明性编程是占主导地位范式的广泛和多样化的域,如数据库,模板和配置管理。

简而言之, 宣言 programming 包括指示程序 什么 需要完成,而不是告诉它 如何 去做吧。在实践中,这种方法需要提供用于表达的特定于域的语言(DSL) 什么 用户想要和屏蔽它们从低级构造(循环,条件,分配)来实现所需的最终状态的屏蔽。

虽然这一范例是对替换的必要方法的显着改进,但我认为声明规划具有重大限制,我在本文中探索的局限性。此外,我提出了一种一种双重方法,可以在取代其限制时捕捉声明规划的好处。

警告: 本文出现了与宣言性工具的多年个人斗争的结果。这里的许多索赔不被彻底证明,一些甚至呈现面值。适当的声明编程批评将需要相当长的时间,努力,我必须回去使用许多这些工具;我的心不在这样的事业中。本文的目的是与您分享一些想法,没有拳击,并显示为我的工作。如果您曾经挣扎着陈述性编程工具,您可能会发现喘息和替代方案。如果您享受范例及其工具,请不要让我太认真。

If 陈述方案制作 works well for you, 我没有任何职位来告诉你.

你可以爱或讨厌宣言的编程,但你不能忽视它。

声明规划的优点

在我们探索声明规划的限制之前,有必要了解其优点。

可以说是最成功的声明性编程工具是关系数据库(RDB)。它甚至可能是第一个声明性工具。在任何情况下,RDB都展示了我认为声明编程的原型的两个属性:

  • 域特定语言(DSL):关系数据库的通用接口是一个名为的DSL 结构化查询语言,最常见的称为SQL。
  • DSL隐藏来自用户的较低级别层: 自从 Edgar F. Codd在RDB上的原文,这是简单的,这种模型的力量是将所需的循环,索引和访问路径解散所需的查询。

在RDB之前,通过命令代码访问大多数数据库系统,这些代码大量依赖于低级细节,例如记录,索引和物理路径对数据本身的物理路径。由于这些元素随着时间的推移而变化,因此代码经常停止工作,因为数据结构的一些潜在的变化。由此产生的代码很难写,很难调试,难以阅读和难以维护。我会出去一个肢体,并说大多数这个代码都在,所有可能性,长,充满了众所周知的大鼠的条件,重复和微妙,国家依赖的虫子。

面对此,RDB为系统开发人员提供了巨大的生产力跃入。现在,而不是数千行的命令代码,你有一个明确定义的数据方案,加上数百(甚至只是数十)的查询。因此,应用程序只能处理数据的抽象,有意义和持久的数据,并通过强大而简单的查询语言接口。 RDB可能提高了程序员的生产力,以及雇用它们的公司的生产力。

What are the commonly listed advantages of 陈述方案制作?

声明性编程的优势如下所列,但每个都有代表图标。

声明规划的支持者很快就能指出优势。但是,即使他们承认它带有权衡。
  1. 可读性/可用性:DSL通常更接近自然语言(如英语),而不是伪码,因此更可读,也更容易被非程序员学习。
  2. 简洁:大部分的样板由DSL抽象,留下较少的线路来做同样的工作。
  3. 重复使用:创建可用于不同目的的代码更容易;使用势在必行结构时,这令人惊奇的东西。
  4. 幂等:你可以合作 终点和let the program figure it out for you. For example, through an upsert operation, you can either insert a row if it is not there, or modify it if it is already there, instead of writing code to deal with both cases.
  5. 错误恢复:很容易指定将在第一个错误停止的构造,而不是必须为每种可能的错误添加错误侦听器。 (如果您在Node.js中写了三个嵌套的回调,那么您就知道我的意思。)
  6. 参考透明度:虽然这一优势通常与功能编程有关,但它实际上适用于最小化状态的手动处理并依赖于副作用的方法。
  7. 换行:表达最终状态的可能性,而无需指定它将实现的实际顺序。

虽然以上都是宣言编程的常见优势,但我想将它们融为一体,这将作为指导原则,当我提出另一种方法时。

  1. 对特定域定制的高级层:声明性编程使用它适用的域的信息创建高级层。很明显,如果我们正在处理数据库,我们希望一组用于处理数据的操作。上面的大部分优势是源于创建一个精确定制的高级层,以确定特定的问题域。
  2. Poka-yoke. (fool-proofness):域定制的高级层隐藏了实施的必要细节。这意味着您的错误较少,因为系统的低级细节根本无法访问。这个限制消除了许多 班级 您的代码中的错误。

声明性编程的两个问题

在以下两个部分中,我将展示宣言编程的两个主要问题: 分离缺乏展开。每一个批评都需要其奖金,因此我将使用HTML模板系统作为声明规划缺点的具体示例。

DSLS的问题:分离

想象一下,您需要使用非琐碎次数的视图编写Web应用程序。硬编码这些视图到一组HTML文件不是一个选项,因为这些页面的许多组件更改。

最简单的解决方案是通过连接字符串来生成HTML的解决方案,似乎很可怕,您将快速查找替代方案。标准解决方案是使用模板系统。虽然有不同类型的模板系统,但我们将为这种分析的目的索取他们的差异。我们可以考虑所有这些都相似,因为模板系统的主要使命是提供一种替代的代码,用于使用条件和循环连接HTML字符串,就像RDB一样被出现为循环通过数据记录的代码的替代方案。

让我们假设我们使用标准的模板系统;您将遇到三个摩擦来源,我将以升序的重要性列出。首先是模板必须驻留在与代码分开的文件中。由于模板系统使用DSL,因此语法不同,因此它不能在同一文件中。在简单的项目中,文件计数低,需要保留单独的模板文件可以复制或重复文件量。

我打开嵌入式Ruby模板(ERB)的例外,因为 那些 被集成到Ruby源代码中。由于这些模板必须存储为不同文件,因此不是其他语言编写的ERB启发工具的情况。

第二个摩擦来源是DSL具有自己的语法,与编程语言不同。因此,修改DSL(更不用说自己写作)更加困难。要在引擎盖下进行更改工具,您需要了解销量和解析,这很有意思,挑战,但很难。我碰巧认为这是一个劣势。

你可能会问,“你为什么要修改你的工具?如果您正在进行标准项目,则写出笔记本合理的标准工具。“也许是,也许不是。

DSL从未具有编程语言的全部功能。如果是这样,它就不会是一个DSL,而是一个完整的编程语言。

但不是DSL的整个点?至 不是 具有可用编程语言的全部功能,以便我们可以实现抽象并消除大多数错误来源?也许是吧。然而, 最多 DSLS开始简单,然后逐渐纳入越来越多的编程语言的设施,实际上是 成为一个。模板系统是一个完美的例子。让我们看看模板系统的标准功能以及它们如何与编程语言设施相关联:

  • 替换模板中的文本:可变替代。
  • 重复模板:循环。
  • 如果不符合条件,请避免打印模板:条件。
  • 部分:子程序。
  • 助手:子程序(与部分唯一的差异是帮助器可以访问底层编程语言,让您退出DSL直接跳线)。

这argument, that a DSL is limited because it simultaneously covets and rejects the power of a programming language, 与DSL的特征直接贴入编程语言的特征,直接成比例。在SQL的情况下,该论点是薄弱的,因为大多数SQL提供的东西都不像你以正常的编程语言所找到的那样。在频谱的另一端,我们发现几乎每个特征都使DSL收敛的模板系统 基本的.

现在让我们返回并考虑这三个典型的摩擦来源,由概念求助 分离。因为它是分开的,所以需要位于单独的文件上;修改(甚至更难地写自己)更难,(通常,但并不总是)需要您一个接一个地添加,您可以从真正的编程语言中错过的功能。

无论是如何设计的,分离是任何DSL的固有问题。

我们现在转向第二个声明工具的问题,这是普遍的,但不是固有的。

另一个问题:缺乏展开导致复杂性

如果我几个月前写了这篇文章,那部分就会被命名 大多数陈述工具都是#@!$#@!复杂但我不知道为什么。在写这篇文章的过程中,我找到了更好的方法来放置它: 大多数陈述工具比需要更复杂。我将花费剩下的部分解释原因。要分析工具的复杂性,我提出了一种称为的措施 复杂性差距。复杂性间隙是解决刀具在较低级别(概括,普通命令代码)中解决替代工具的给定问题之间的差异。当前解决方案比后者更复杂时,我们存在复杂性间隙。经过 更复杂,我的意思是更多的代码,代码更难读取,更难修改和更难维护,但不一定在同一时间。

请注意,我们没有将较低级别的解决方案与最佳工具进行比较,而是反对 工具。这回应了医学原理 “首先,没有伤害”.

具有大复杂性差距的工具的迹象是:

  • 即使您知道如何使用该工具,也需要几分钟的时间才能在命令条款中以富裕的术语描述为富裕的细节。
  • 你觉得你一直在围绕工具而不是工具工作。
  • 您正在努力解决一个直接属于您正在使用的工具领域的直接问题,但您发现的最佳堆栈溢出答案描述了一个 解决方法.
  • 当这种非常简单的问题可以通过某个特征(工具中不存在)来解决时,并且您在库中看到GitHub问题,其中包含对所述功能的长时间讨论 +1散布了。
  • 慢性,瘙痒,渴望抛弃工具,并在_ for-loop_中自己做整件事。

由于模板系统不是,我可能会在这里堕落到情感 复杂,但这种比较小的复杂性差距是他们的设计的优点,而是因为适用性的领域非常简单(请记住,我们只是在这里生成HTML)。无论何时使用相同的方法都用于更复杂的域(如配置管理),复杂性间隙可能会快速将项目转换为Quagmire。

也就是说,对于一种工具比其打算更换的较低水平更复杂,这是不可接受的。如果工具产生更易读,简洁和正确的代码,则可以值得。这是一个问题,当工具比它替换的问题更复杂几倍;这是平坦的不可接受的。 Brian Kernighan着名,“控制复杂性是计算机编程的本质。“如果工具对项目增加了重大复杂性,为什么甚至使用它?

问题是,为什么一些声明工具比他们需要的更复杂更多?我认为在糟糕的设计中归咎于它是一个错误。这样的一般解释,一个关于这些工具的作者的毯子ad-hominem攻击,是不公平的。必须有更准确和启发的解释。

我的争用是任何提供高级界面的工具,必须摘要较低级别 展开 从较低的水平较高。这个概念 展开 来自克里斯托弗亚历山大的Magnum Opus, 订单的性质 - 特别是第II卷。它(绝望地)超出了本文的范围(更不用说我的理解)总结了这种纪念性工作对软件设计的影响;我相信它在未来几年的影响将是巨大的。它还超出了本文,提供了对展开流程的严格定义。我将在这里使用这个概念 启发式的方式.

展开过程是一种,以逐步的方式在不否定现有的时尚的情况下创建进一步的结构。在每一步中,当先前的结构都是先前的结构时,每一步,每个变化(或差异化,使用Alexander的术语)仍然与任何先前的结构相处。

有趣的是, unix. 是从较低的水平展开更高级别的一个很好的例子。在UNIX中,操作系统系统,批处理作业和考程(管道)的两个复杂功能只是基本命令的扩展。由于某些基本的设计决策,例如使一切都是字节流,壳牌是一个 百分形计划标准I / O文件,Unix能够提供这些复杂性的复杂功能。

强调为什么这些是展开的优秀例子,我想引用一些摘录 1979年 由Dennis Ritchie,Unix的作者之一:

关于批处理工作:

… the new process control scheme instantly rendered some very valuable features trivial to implement; for example detached processes (with &) and recursive use of the shell as a command. Most systems have to supply some sort of special batch job submission facility and a special command interpreter for files distinct from the one used interactively.

关于金融素:

UNIX管道的天才恰恰是它由具有单纯性方式不断使用的非常相同的命令构成。

这elegance and simplicity, I argue, comes from an 展开 过程。批处理作业和考程从以前的结构展开(命令在userland shell中运行)。我认为由于创建了UNIX的团队的小组的极简主义哲学和有限的资源,因此该系统逐步进化,因此,能够纳入高级功能,而无需将其重新转到基本的功能,因为没有足够的资源否则。

在没有展开过程的情况下,高水平将比必要的更复杂。换句话说,大多数陈述工具的复杂性源于他们的高水平不会从他们打算取代的低水平展开。

这lack of 展望如果您原谅新神奇主义,通常通过必要地证明从较低级别屏蔽用户。这种强调Poka-yoke(保护用户免受低级错误)的牺牲品是自我打败的大量复杂性差距,因为额外的复杂性将产生新的错误类别。为了造成伤害,这些错误与问题域无关,而是与工具本身无关。如果我们描述了这些错误,我们不会走得太远 认识性.

声明性模板工具,至少在应用于生成HTML视图的任务时,是高级的原始典型情况,其旨在替换的低级别。为何如此?因为 生成任何非平凡视图需要逻辑, 和templating systems, especially logic-less ones, banish logic through the main door and then smuggle some of it back through the cat door.

笔记: 对于大型复杂性差距的甚至较弱的理由是工具销售时 魔法或者那些东西 只是作品,低水平的不透明应该是一个资产,因为魔法工具总是应该在没有理解原因或如何理解的情况下工作。在我的经验中,莫名其妙的工具言论越多,它将越快地传播我对挫折感的热情。

但对担忧的分离呢?不应该查看和逻辑仍然分开?这里的核心错误是在同一包中放置业务逻辑和演示逻辑。业务逻辑肯定没有在模板中的位置,但仍然存在演示逻辑。从模板中排除逻辑将演示文稿逻辑推入服务器处于笨拙的服务器中。我欠了这一点的清晰配方到Alexei硼尼,为此做出了一个极好的案例 在本文中.

我的感觉是,模板的大约三分之二的工作驻留在其演示逻辑中,而另一个第三个处理诸如连接字符串,结束标签,逃避特殊字符等的通用问题。这是生成HTML视图的双面低级性质。模板系统与下半场合作妥善处理,但第一个,他们不会良好。较少的模板平坦熄灭了他们的问题,强迫你尴尬地解决它。其他模板系统受到损害,因为他们真的需要提供非琐碎的编程语言,因此他们的用户实际上可以编写呈现逻辑。

总结;声明性模板工具受到影响,因为:

  • 如果他们从问题领域展开,他们必须提供产生逻辑模式的方法;
  • 提供逻辑的DSL不是真正的DSL,而是一种编程语言。请注意,其他域,如配置管理,也缺乏“展开”。

我想用逻辑断开与本文的线程逻辑断开的论点关闭批评,但与其情感核心深度共鸣:我们有限的时间学习。生命是短暂的,在那之上,我们需要工作。面对我们的局限性,我们需要花时间学习将有用和耐用的时间,即使在快速变化的技术方面也是如此。这就是为什么我劝你使用不仅仅是提供解决方案的工具,而是在其自身适用性的领域实际上阐明了一个明亮的灯光。 rdbs教你关于数据的信息,Unix教授了关于操作系统概念的概念,但是没有令人满意的工具,我总是觉得我正在学习次优溶液的复杂性,同时留在黑暗中的问题的暗示中它打算解决。

我建议你考虑的启发式, 价值 tools that illuminate their problem domain, instead of tools that obscure their problem domain behind purported features.

双胞胎方法

为了克服我在这里介绍的宣言编程的两个问题,我提出了一款双胞胎方法:

  • 使用数据结构域特定语言(DSDSL),以克服分离。
  • 创建从较低级别展开的高水平,克服复杂性差距。

DSDSL.

数据结构DSL(DSDSL)是一种DSL 使用编程语言的数据结构构建。核心思想是使用您提供的基本数据结构,例如字符串,数字,数组,对象和函数,并将它们组合以创建要处理特定域的抽象。

我们希望保持声明结构或动作(高级)的力量,而无需指定实现这些构造的模式(低电平)。我们希望克服DSL和我们的编程语言之间的分离,以便我们在需要时可以自由地使用编程语言的全部功率。这不仅可能,而且通过DSDSLS直截了当。

如果你一年前问我,我会认为DSDSL的概念是小说,然后有一天,我意识到这一点 杰森 本身是这种方法的一个完美的例子!解析的JSON对象包括声明方式表示数据条目的数据结构,以便在编程语言中轻松解析和处理的同时,以便获得DSL的优势。 (可能还有其他DSDSL,但到目前为止,我没有遇到任何事情。如果你知道一个,我真的很感谢你在评论部分中提到它。)

像JSON一样,DSDSL具有以下属性:

  1. It consists of a very small set of functions: JSON has two main functions, parsestringify.
  2. 它的功能最常见地接收复杂和递归参数:解析的JSON是数组或对象,通常包含进一步的数组和内部对象。
  3. 这些功能的输入符合非常特定的形式:JSON具有显式和严格强制执行的验证架构,以告诉无效的结构。
  4. 这些功能的输入和输出都可以通过编程语言包含和生成,而无需单独的语法。

但DSDSL在许多方面超越了JSON。让我们创建一个使用JavaScript生成HTML的DSDSL。后来我将谈谈这种方法是否可以扩展到其他语言(SPOILER:它绝对可以在Ruby和Python中完成,但可能不在c)中。

HTML is a markup language composed of tags delimited by angle brackets (<>). These tags may have optional attributes and contents. Attributes are simply a list of key/value attributes, and contents may be either text or other tags. Both attributes and contents are optionals for any given tag. I’m simplifying somewhat, but it is accurate.

表示DSDSL中的HTML标记的直接方式是使用具有三个元素的数组: - Tag: a string. - Attributes: an object (of the plain, key/value type) or undefined (if no attributes are necessary). - Contents: a string (text), an array (another tag) or undefined (if there’s no contents).

For example, <a href="views">Index</a> can be written as ['a', {href: 'views'}, 'Index'].

If we want to embed this anchor element into a div with class links, we can write: ['div', {class: 'links'}, ['a', {href: 'views'}, 'Index']].

要在同一级别列出多个HTML标记,我们可以将它们包装在数组中:

[
   ['h1', 'Hello!'],
   ['a', {href: 'views'}, 'Index']
]

可以应用于在标签中创建多个标记的相同原理:

['body', [
   ['h1', 'Hello!'],
   ['a', {href: 'views'}, 'Index']
]]

Of course, this dsDSL won’t get us far if we don’t generate HTML from it. We need a generate function which will take our dsDSL and yield a string with HTML. So if we run generate (['a', {href: 'views'}, 'Index']), we will get the string <a href="views">Index</a>.

这 idea behind any DSL is to specify a few constructs with a specific structure which is then passed to a function. In this case, the structure that makes up the dsDSL is this array, which has one to three elements; these arrays have a specific structure. If generate thoroughly validates its input (and it is both easy and important to thoroughly validate input, since these validation rules are the precise analog of a DSL’s syntax), it will tell you exactly where you went wrong with your input. After a while, you’ll start to recognize what distinguishes a valid structure in a dsDSL, and this structure will be highly suggestive of the underlying thing it generates.

现在,DSDS对DSL的DSDSL的优点是什么?

  • DSDSL是代码的一个组成部分。它导致下线计数,文件计数以及开销的总体减少。
  • dsdsls是 简单的 解析(因此更容易实现和修改)。解析仅通过数组或对象的元素迭代。同样,DSDSLS比较易于设计,因为您可以坚持使用编程语言的语法(每个人讨厌的新语法(每个人都讨厌)。
  • DSDSL具有编程语言的所有功能。这意味着DSDSL在适当使用时具有高电平和低级工具的优点。

现在,最后一个索赔是一个强大的索赔,所以我将花费剩下的这个部分支持它。我的意思是什么 适当使用? To see this in action, let’s consider an example in which we want to construct a table to display the information from an array named DATA.

var DATA = [
   {id: 1, description: 'Product 1', price: 20,  onSale: true,  categories: ['a']},
   {id: 2, description: 'Product 2', price: 60,  onSale: false, categories: ['b']},
   {id: 3, description: 'Product 3', price: 120, onSale: false, categories: ['a', 'c']},
   {id: 4, description: 'Product 4', price: 45,  onSale: true,  categories: ['a', 'b']}
]

In a real application, DATA will be generated dynamically from a database query.

Moreover, we have a FILTER variable which, when initialized, will be an array with the categories we want to display.

我们希望我们的桌子:

  • 显示表标题。
  • 对于每个产品,显示字段:描述,价格和类别。
  • Don’t print the id field, but add it as an id 属性 for each row. ALTERNATE VERSION: Add an id 属性 to each tr element.
  • Place a class onSale if the product is on sale.
  • 按降价对产品进行排序。
  • Filter certain products by category. If FILTER is an empty array, we will display all products. Otherwise, we will only display the products where the category of the product is contained within FILTER.

我们可以创建与〜20行代码中的此要求匹配的演示逻辑:

function drawTable (DATA, FILTER) {

   var printableFields = ['description', 'price', 'categories'];

   DATA.sort (function (a, b) {return a.price - b.price});

   return ['table', [
      ['tr', dale.do (printableFields, function (field) {
         return ['th', field];
      })],
      dale.do (DATA, function (product) {
         var matches = (! FILTER || FILTER.length === 0) || dale.stop (product.categories, true, function (category) {
            return FILTER.indexOf (category) !== -1;
         });

         return matches === false ? [] : ['tr', {
            id: product.id,
            class: product.onSale ? 'onsale' : undefined
         }, dale.do (printableFields, function (field) {
            return ['td', product [field]];
         })];
      })
   ]];
}

我承认这不是一个直截了当的例子,但是,它代表了持久存储的四种基本功能的相当简单的视图,也称为 cr 。任何非琐碎的Web应用程序都有比这更复杂的视图。

Let’s now see what this code is doing. First, it defines a function, drawTable, to contain the presentation logic of drawing the product table. This function receives DATAFILTER as parameters, so it can be used for different data sets and filters. drawTable fulfills the double role of partial and helper.

   var drawTable = function (DATA, FILTER) {

这 inner variable, printableFields, is the only place where you need to specify which fields are printable ones, avoiding repetition and inconsistencies in the face of changing requirements.

   var printableFields = ['description', 'price', 'categories'];

We then sort DATA according to the price of its products. Notice that different and more complex sort criteria would be straightforward to implement since we have the entire programming language at our disposal.

   DATA.sort (function (a, b) {return a.price - b.price});

Here we return an object literal; an array which contains table as its first element and its contents as the second. This is the dsDSL representation of the <table> we want to create.

   return ['table', [

我们现在使用表格标题创建一行。要创建其内容,我们使用 戴尔.DO 这是一种功能 array.map., but one that also works for objects. We will iterate printableFields和generate table headers for each of them:

      ['tr', dale.do (printableFields, function (field) {
         return ['th', field];
      })],

请注意,我们刚刚实施了HTML生成的迭代,我们不需要任何DSL构造;我们只需要迭代数据结构并返回DSDSL的功能。一个类似的本地或用户实现的功能,也会做到这一点。

Now iterate through the products contained in DATA.

      dale.do (DATA, function (product) {

We check whether this product is left out by FILTER. If FILTER is empty, we will print the product. If FILTER is not empty, we will iterate through the categories of the product until we find one that is contained within FILTER. We do this using Dale.Stop..

         var matches = (! FILTER || FILTER.length === 0) || dale.stop (product.categories, true, function (category) {
            return FILTER.indexOf (category) !== -1;
         });

注意有条件的复杂性;它精确定制了我们的要求,我们有完全的自由表达它,因为我们处于编程语言而不是DSL。

If matches is false, we return an empty array (so we don’t print this product). Otherwise, we return a <tr> with its proper id and class and we iterate through printableFields to, well, print the fields.


         return matches === false ? [] : ['tr', {
            id: product.id,
            class: product.onSale ? 'onsale' : undefined
         }, dale.do (printableFields, function (field) {
            return ['td', product [field]];

当然,我们关闭了我们开放的一切。不是语法乐趣吗?

         })];
      })
   ]];
}

Now, how do we incorporate this table into a wider context? We write a function named drawAll 那 will invoke all functions that generate the views. Apart from drawTable, we might also have drawHeader, drawFooter和other comparable functions, all of which 将返回DSDSL.

var drawAll = function () {
   return generate ([
      drawHeader (),
      drawTable (DATA, FILTER),
      drawFooter ()
   ]);
}

如果你不喜欢上述代码的外观,我什么都不会说服你。 这is a dsDSL at its best。您可能也停止阅读文章(并降低了平均评论,因为您已赢得了这样做的权利,如果您已经这么做了!)。但是,如果上面的代码不会让你变得优雅,这篇文章中没有其他东西。

对于那些仍然和我在一起的人,我想回到本节的主要索赔,这就是这一点 DSDSL具有高电平和低电平的优点:

  • 低水平的优势 每当我们想要的时候驻留在编写代码中,从DSL的直接跳上开始。
  • 高水平的优势 驻留在使用表示我们想要声明的文字并让工具的功能转换为所需的最终状态(在这种情况下,带有HTML的字符串)。

但是这与纯粹势在必行的代码有何不同?我认为最终的DSDSL方法的优雅归结为这一事实 以这种方式编写的代码主要由表达式组成,而不是陈述。更确切地说,使用DSDSL的代码几乎完全由以下组成:

  • 映射到较低级别结构的文字。
  • 功能调用或lambdas 在那些文字结构中 返回相同类型的结构。

其中主要由表达式组成的代码,并且封装函数中的大多数语句是非常简洁的,因为所有重复模式都可以轻松抽象。只要该代码返回符合非常特定的非任意形式的文字,您可以编写任意代码。

DSDSL的另一个特征(我们这里没有时间探索)是使用类型来增加文字结构的丰富性和简洁性的可能性。我将在未来的文章中阐述这个问题。

可能可以创建超出JavaScript的DSDSLS,这是一个真正的语言?我认为,只要语言支持即可,它就是可能的:

  • 字面为:数组,对象(关联数组),函数调用和lambdas。
  • 运行时类型检测
  • 多态性和动态回报类型

我认为这意味着DSDSL可以在任何现代动态语言中(即:Ruby,Python,Perl,PHP),但可能不在C或Java中。

走路,然后滑动:如何从低位展开高位

在本节中,我将尝试从其域中展开高级工具的方法。简而言之,该方法包括以下步骤

  1. 需要两到四个问题是问题域的代表性实例。这些问题应该是真实的。从低位展开的高水平是归纳的问题,因此您需要真实数据来提出代表解决方案。
  2. 解决问题 没有工具 以最直接的方式。
  3. 待机,好看您的解决方案,并注意到它们之间的常见模式。
  4. 找到表示的模式(高级)。
  5. 找到生成模式(低级)。
  6. 解决与您的高级层相同的问题,并验证解决方案是否正确。
  7. 如果您觉得您可以轻松地代表您的表示模式,并且每个实例的生成模式会产生正确的实现,您完成了正确的实现。否则,返回绘图板。
  8. 如果出现新问题,请用工具解决它们并相应地修改它。
  9. 无论它解决多少问题,该工具应渐眼融合到成品状态。换句话说,工具的复杂性应保持恒定,而不是在其解决的问题中生长。

现在,到底是什么 代表模式生成模式?我很高兴你问道。表示模式是您应该能够表达属于涉及您工具的域的问题的模式。它是一个结构的字母表,允许您编写您希望在其适用范围内表达的任何模式。在DSL中,这些是生产规则。让我们回到我们的dsdsl以生成HTML。

分解HTML片段。线路"&lt;font color="#3863A0"&gt; toptal.com &lt;/font&gt;"有以下标签:"attribute" on the word "color", "value" on the string "#3863A0", "content" on the string "toptal.com", "opening tag"在之前的一切"toptal.com", and "closing tag"在它之后的一切。

谦虚的HTML标记是表示模式模式的一个很好的例子。让我们仔细看看这些基本模式。

HTML的表示模式如下:

  • 单标记: ['TAG']
  • 具有属性的单个标记: ['TAG', {attribute1: value1, attribute2: value2, ...}]
  • 单个标签内容: ['TAG', 'CONTENTS']
  • 具有属性和内容的单个标记: ['TAG', {attribute1: value1, ...}, 'CONTENTS']
  • 单个标记内部有另一个标签: ['TAG1', ['TAG2', ...]]
  • 一组标签(独立或另一个标签内): [['TAG1', ...], ['TAG2', ...]]
  • 根据条件,放置标签或否标记: condition ? ['TAG', ...] : [] /根据条件,放置属性或没有属性: ['TAG', {class: condition ? 'someClass': undefined}, ...]

这些实例可以用我们在上一节中确定的DSDSL表示法表示。这就是表示您可能需要的任何HTML所需的一切。可以通过返回上面的表示模式的功能来实现更复杂的模式,例如通过对象来生成表的特征迭代,例如返回上面的表示模式,并且这些模式直接映射到HTML标记。

如果表示模式是您用来表达所需的结构,则生成模式是您的工具将用于将表示模式转换为较低级别的结构。对于HTML,这些是以下内容:

  • 验证输入(这实际上是一种通用的生成模式)。
  • Open and close tags (but not the void tags, like <input>, which are self-closing).
  • Place attributes and contents, escaping special characters (but not the contents of the <style><script> tags).

相信它与否,这些模式是您需要创建生成HTML的展开的DSDSL图层所需的模式。可以找到类似的模式来生成CSS。实际上, 立体词 两者都在〜250行代码中。

最后一个问题仍有待解答:我的意思是什么 走路,然后滑动?当我们处理问题域时,我们希望使用一个工具,该工具从该域的令人讨厌的细节中提供我们的工具。换句话说,我们希望扫描地毯下的低位,越快越好。这 走路,然后滑动 方法建议完全相反:花一些时间在低水平上。拥抱它的怪癖,并了解哪些是必不可少的,在一组真实,不同的问题和有用的问题面前可以避免。

在较低水平的一段时间后散步并解决有用的问题,你将对他们的域名有足够深刻的理解。然后,表示和生成模式自然会出现;它们完全来自他们打算解决问题的本质。然后,您可以编写使用它们的代码。如果他们工作,你将能够通过最近才能穿过它们的问题来滑动。滑动意味着很多东西;它意味着速度,精确和缺乏摩擦。也许更重要的是,可以感受到这种质量;解决这个工具问题时,你觉得自己走过这个问题,还是你觉得你透过了它?

也许关于展开工具的最重要的事情并不是它让我们不得不处理低水平。相反,通过捕获低级中的重复的经验模式,良好的高级工具允许我们完全了解适用性领域。

一个展开的工具不仅仅是解决问题 - 它将启发你关于问题的结构。

所以,不要远离一个有价值的问题。首先走在它周围,然后滑过它。