面向 JavaScript 开发人员的 Elm 语义
面向 JavaScript 开发人员的 Elm 语义
原文:https://medium.com/hackernoon/elm-semantics-for-javascript-developers-56a83e5ae23
大多数语言教程都从语言特性开始:这里是数字,这里是字符串,这与其他语言略有不同。不是这个。我在猎大猎物。
我希望在这里做的是,参考您已经从 JS 中了解的内容,带您浏览 Elm 的主要思想,而不会被您实际键入的内容所拖累。将不会有代码示例。
这并不是要帮助你决定是否要尝试 Elm。如果你已经迈出了这一步,并在问“现在怎么办?”这是最好的选择。或者,如果你想知道所有的大惊小怪是什么。
我也不打算列出所有 Elm 没有的 JavaScript 拥有的东西。提升,原型,自己的属性,这个,三重等于,所有其他怪异的东西在 Elm 都不存在。你会发现它的语义实际上比 JavaScript 简单得多!
生计
JavaScript 作为第一种普及一流功能的语言值得称赞。这是一个包含四种特定能力的奇特术语。函数可以将其他函数作为参数。它们也可以返回函数。函数可以赋给变量——err,常量——并放入数据结构中。最后,通过使用函数文字作为表达式,函数可以是匿名的。你会期待一种不亚于“功能性”的语言。(顺便说一句,函数声明是将匿名函数赋给常数的语法糖,不像 JavaScript 中提升使这两者有细微的不同。)
另一个主要的共享特性是函数范围,包括闭包。您可以在函数内部定义局部常量(包括函数)。如果父函数返回引用这些值的函数,则即使父函数返回,这些值也是可见的。我们一会儿会看到为什么返回一个函数很常见。
数据流的最佳实践
如果你知道 React/Redux 或 ngrx,那么接下来的这些概念你应该很熟悉。这些 JavaScript 框架正在汇聚到一组思想上,这些思想共同规定了应用程序是如何构造的,以及数据如何在其中流动。尽管想法不同,但 JS 和 Elm 社区都意识到他们合作得非常好。Elm 采取了这些限制,使欺骗变得不可能。毕竟:库不能提供新的能力。
首先是不可变数据:值一旦定义就永远不会改变。相反,您可以创建应用了更改的值的副本。(它的计算成本比你想象的要低。)其次,无状态函数(或纯函数)比那些能读写某些全局状态的函数更容易调试和测试。除了没有全局状态,因为那会是突变。如果您可以将一个变量从一个无状态函数重新绑定到另一个无状态函数,它就不会显得无状态了。看到这些想法是如何联系在一起的了吗?
不可变数据遵循单向数据流。React 表明这是一个好主意,Angular 在版本 2 中放弃了双向流。(Redux 其实是受了 Elm 的影响,而不是相反。)Ember 强调模板将动作发送回它们的控制器,而不是直接更新值。因此,在 Elm 中,您在程序的“顶部”接收消息,它们向下传播以定义新的模型和命令。命令是管理效果的一种形式,第四个主要思想。您不需要设置超时或发出 HTTP 请求,而是创建要完成的工作的描述。就像一个返回承诺的函数,这个描述在你运行它之前什么都不做,或者在 Elm 的情况下,把它交给运行时来运行。
尽管 JavaScript 社区正在向这些想法靠拢,但它们远非标准。库中的每个 Elm 包都使用不可变的数据——甚至是相同的实现——这比 npm 更好。
在你的雷达上
我们还没有谈到的 Elm 的一个主要特性是它的静态类型系统。如果你是一个有角度的开发者,你可能用过 TypeScript。如果你在 React 生态系统中,你至少听说过 Flow。几乎每个人都在使用 Babel 或 Webpack,所以构建阶段的概念对这个社区来说已经不像以前那么陌生了。尽管如此,Elm 的类型系统能够捕捉比 JavaScript 更多的问题,这就是它在实践中没有运行时错误的原因。Elm 的编译器错误通常比“未定义不是函数”更有帮助。
JavaScript 的模块系统——例如 AMD、CommonJS、require js——已经被标准化为 ES6 模块,因此这个领域没有以前那么困难了。也就是说,Elm 文件直接对应于模块,它开箱即用。您可以控制在文件顶部直接公开的内容。您可以在已发布的库中公开模块(或保持模块私有)。
JavaScript 开发人员对包生态系统有些不信任,原因是以左开始,以填充结束。语义版本化更多地被视为一种理想而非标准。但是在 Elm 中,每个包都有语义版本,这是由类型系统和其他工具强制执行的。您可以安全地升级,而不必担心破坏您的代码。
因为 Ramda.js 是一个存在的东西,你可能听说过“currying”。我更喜欢“部分应用程序”这个术语,因为它简洁地描述了传递一个函数的部分而非全部参数,以便它返回另一个函数的思想。您可以手动或通过 JS 中的库来设置它,但是它很笨重。在 Elm 中,默认情况下,每个功能都是部分可应用的,您会发现自己在使用它时并没有太多的限制。需要注意的一点是,Elm 函数不支持可选参数,因为省略一个参数意味着部分应用。这个缺点在实践中出现的次数比你想象的要少得多。
新领域
如果您使用过 Redux,您必须确保程序的许多部分都同意命名动作的字符串常量。更重要的是,您需要确保这些名字中的每一个都在某个地方被一个 reducer 使用。Elm 通过联合类型解决了这个问题以及其他许多问题。您可以定义新的常量,它们都是新类型的值,并且是该类型的唯一值。然后,您可以使用模式匹配(类似于 switch 语句)来处理这些常量中的每一个,编译器将确保您得到所有这些常量。
但是联合类型比这更灵活,因为每个值不一定是常量标识符。它还可以携带其他类型的值,也就是说,它可以是从一个或多个值到新类型的函数。当您进行模式匹配时,您可以访问这些值。作为一个具体的例子,标准的可能是一个类型,可以“仅仅”是一个值(类型为 a ,也就是你喜欢的任何类型)或者“什么都不是”。这允许 Elm 非常明确地处理可空值以避免错误。不接受的函数可以保证被传递正确类型的值,而不是未定义的。
在底层,列表被表示为单链表(即堆栈),递归被用来完成 JavaScript 中迭代所做的大部分工作。除此之外,你很少在列表上显式递归。相反,使用列表库中的函数来映射、过滤和折叠(缩小)列表。这些操作是在 ES5 的 Array.prototype 中介绍的,所以您应该已经很熟悉了。
不变性意味着内存中的对象只能保存对比它们旧的对象的引用。因此,许多功能数据结构是树状的。图通常涉及某种形式的 id。字典和集合被实现为二分搜索法树,因此它们的键必须是可比较的。
但是关于数据结构已经讲得够多了;先说观点。Ember 有 handlebars,这是一种模板语言,写在与 JavaScript 不同的文件中。React 拥有 JSX,并在 JavaScript 中加入了新的语法。Angular 和 vue 也有自己类似 HTML 的语法。大卫·福特展示了模板语言的问题:
在某些时候,你需要有条件地呈现一些内容,也就是说,如果某某是真的,只显示 html 的一些片段。所以模板语言需要类似于 T2 if T3 语句的东西。或者可能是围绕一个值的大量条件。所以模板语言需要类似于 T4 开关 T5 的东西。
然后你可能需要展示一些列表或表格。所以模板语言将需要某种类型的循环结构。
最终你的模板会变大。所以你需要一些方法来组织它们,把它们分成更小的部分。一个模板调用另一个模板的方法。定义可重用模板代码块的一些方法。模板语言将需要所有这些的构造。
然后你需要一些方法来格式化数字和日期。
诸如此类。
随着时间的推移,大多数模板语言最终会彻底改造通用编程语言的几乎所有特性。
这就是模板语言的真正含义。它们是编程语言。通常是糟糕的编程语言。
Elm 没有独立的模板语言。它甚至没有针对模板的特殊语法。相反,它提供每个 HTML 标签作为两个参数的函数:一个属性列表(类、事件处理程序、href 等)。)和一个子元素列表(HTML 元素本身,递归地,或者一个文本节点)。虽然您经常看到这些列表被写成带有方括号的文字,但是您可以用任何列表函数(比如 mapping)来代替它们。不需要特殊的组件、助手和片段,您可以在视图中任何有意义的地方提取助手函数,就像处理任何其他代码一样。
与 JS SPA 框架相比,您将遇到的另一个主要区别是,对于何时调用您的代码没有魔法。您将不会调用代码,因为它的名称与其他一些代码相似,或者因为它用神秘的行为扩展了一个提供的类,或者因为调用了一个生命周期挂钩。当您没有预料到代码被调用时,您将不会有不一致的状态,并且不知道如何调试它。在 Elm 中,只有少数几个由运行时调用的“高级”函数(主要是更新和视图)。所有其他要运行的代码最终都会被这些函数调用。Elm 程序员可以完全控制调用堆栈。
总的来说,这些特性使 Elm 成为一种连贯的语言,即使在您将六个 React 生态系统库混合在一起之前,它也比 JavaScript 小得多。每样东西都与其他东西配合得很好。一切都有道理。