# 概述   NPL 是 YSLib 提供的语言集合,它在语言规范层次上被设计为可扩展的。   通过*派生(derive)* 现有的语言( NPL 的*方言(dialect)* ),避免完全重新设计新的语言,来满足需要一些新语言的场合下的需求。被派生的语言是 NPL 的[抽象语言实现](../Terminology.zh-CN.md#规范)。翻译或执行 NPL 或 NPL 抽象语言实现的程序是 NPL (方言)的[具体语言实现](../Terminology.zh-CN.md#规范)。具有具体语言实现的方言仍可以派生新的语言作为[领域特定语言](https://en.wikipedia.org/wiki/Domain-specific_language)。   派生领域特定语言的一个其它的例子是 [XML](https://en.wikipedia.org/wiki/XML) 。 ## 语法和语义   NPL 的[语法](#语法)基于形式文法上可递归构造的**表达式**。   在操作语义(基于[项重写系统](#项重写系统))的[意义](#规约规则和求值)上,其中的子表达式又称为**项**。   非正式地,NPL 使用类似 [S-表达式](https://zh.wikipedia.org/wiki/S-%E8%A1%A8%E8%BE%BE%E5%BC%8F)的基本语法,但不使用二元有序对(和终止符号)而直接支持列表;即表达式直接以是否为括号作为边界,分为[列表表达式](#列表表达式)和非列表表达式。   正式语法中,作为子表达式的项可以是列表或非列表项的[复合表达式](#复合表达式)规约列表项中的一部分代替有序对的地位,以要求任意项可被无歧义地进行从左到右的语法分析。   NPL 只要求小括号作为列表表达式的边界。其它替代的括号由[派生实现](#略称)约定。对于适用于多个 NPL 实现的可移植代码,避免使用其它语言中习惯作为代替括号边界的字符表示替代的括号以外的含义,特别地,\[ISO C++] 文法 `balanced-token` 中的边界字符 `()[]{}` 。   NPL 对标识符的限制较为宽松。\[ISO C] 和 \[ISO C++] 的所有标识符都是 NPL 标识符。但派生实现可加以限制。   NPL 不提供专用的注释语法。以特定形式的项(如表示字符串的字面量)替代注释是预期的惯用法(idiom) 。这不妨碍派生语言可能添加预处理器扩展特性。   NPL 提供了一些通用的概念和[公共规则](#npl-公共语义),但不构成[具体语言实现](../Terminology.zh-CN.md#规范)的完整语义规则。语义规则由派生实现补充完整。 **注释**   排除注释及[中缀标点 `;` 和 `,`](#中缀语法) ,NPL 的语法和 [Scheme 语言](https://en.wikipedia.org/wiki/Scheme_%28programming_language%29)或[Kernel 语言](https://web.cs.wpi.edu/~jshutt/kernel.html)的语法近似。不过,NPL 不支持构造循环引用,也不提供相关语法。 ## 需求概述   设计满足的需求描述参见[这里(en-US)](https://github.com/FrankHB/pl-docs/blob/master/en-US/calling-for-language-features.md) 。   需求来源: * 出发点:构建一个可用计算机实现的语言。 * 基本目的:在以标准 C++ 环境( \[ISO C++] 定义的*宿主实现(hosted implementation)* )的程序框架中嵌入配置和脚本操作。 * 扩展目的:渐进地向独立的计算机系统演进,探究能适用于各个领域并以计算机实现的*通用目的语言(general-purpose language)* 。   本文档描述基于此出发点的 **NPL(Name Protocoling Language)** (一个替代的递归缩写是“NPL's not a Programming Language”,因其不仅适合作为 PL 的[元语言](../Terminology.zh-CN.md#程序设计语言)特性及其参照实现。   和大部分其它设计不同,为了确保一定程度的适应通用目的的性质,它们被设计整体首要考虑。这样的设计的语言是(自设计(by desing) 用于)满足通用目的的语言(general-purposed language) 。 ## 其它设计和实现参考   NPL 是独立设计的语言,但它和 \[RnRK] 定义的 [Kernel 语言](https://web.cs.wpi.edu/~jshutt/kernel.html)有许多核心设计的相似之处,尽管设计的一些基本特征(如[资源可用性基本约定](#资源可用性基本约定))以及[基本哲学](#领域设计原则)相当不同。   NPL 的[主要实现](#npla1-核心语言)的核心部分实质上支持了 Kernel 的[形式模型](#模型)—— vau 演算(vau calculi) 。 **注释** 另见[操作语义](#形式语义方法)。   具体的 NPL 语言在这些模型的基础上提供。   [NPL 的命名](#需求概述)即体现了 vau 演算和传统 [λ 演算](#λ-完备语义和对应语法)为模型的语言的核心差异:   强调允许在[对象语言](../Terminology.zh-CN.md#程序设计语言)中指定求值上下文的*显式求值(explicit evaluation)*(而非 Lisp 方言中以 `quote` 为代表的显式干预默认的隐式求值)的风格以及[表达式](#表达式)[求值](#基本语义概念)前后的不同,特别地,关注在语言中直接表达的[名称](../Terminology.zh-CN.md#程序设计语言)和[求值](#基本语义概念)后指称的[实体](../Terminology.zh-CN.md#程序设计语言)的不同。   更进一步地,NPL 普遍地支持区分[一等引用](#一等引用)和被引用的[一等实体](#基本语义概念)并具有更精确的资源控制机制,这是与 Kernel 的主要设计上的差异。   关于 vau 演算的形式模型和其它相关内容,详见 \[Shu10] 。特别地,vau 演算提供了 [fexpr](https://en.wikipedia.org/wiki/Fexpr) 类似的抽象。 **注释** 另见[求值算法设计的实例](#npla1-规范求值算法)。   关于一些其它支持 fexpr 特性的语言设计,参见: * [PicoLisp](https://software-lab.de/doc/faq.html#lambda) * [newLISP](http://www.newlisp.org/downloads/newlisp_manual.html#define-macro)   和 Kernel 以及本设计不同,这两个例子的设计使用[动态作用域](#函数和函数应用的求值环境);在主要的特性中存在一些关键的不同而在形式模型的适用性上有显著的区别。 **注释**   NPL 和历史上同名的 [John Darlington](https://en.wikipedia.org/wiki/John_Darlington) 的 [NPL (New Programming Language)](https://en.wikipedia.org/wiki/NPL_(programming_language)) 没有直接渊源;特别地,后者的多个等式的函数定义语法和高阶类型没有被内建支持,而[静态类型](#类型系统和类型机制)和纯函数式限制被避免。   Kernel 语言的原始参照实现是 SINK 依赖 MzScheme version 103 的解释器实现,和 \[RnRK] 有一定差异。例如,字面量 `#ignore` 和 `#inert` 用 `%ignore` 和 `%inert` 代替。   *[klisp](https://web.archive.org/web/20210301121505/http://klisp.org/)* 是 Kernel 语言的一个更完善的实现。   有些特性(如复数支持)都没有在这两个中实现提供,而仅在 \[RnRK] 中指定。 ## 实现   在 YFramework/NPL 提供一些参考[具体语言实现](../Terminology.zh-CN.md#规范)。当前 YFramework 主要使用[抽象语言实现](../Terminology.zh-CN.md#规范) [NPLA](#npla) 的具体*派生(derived)* 的实现 [NPLA1](#npla1-核心语言) ,在这个基础上用于不同的目的,如[程序配置](../Tutorial/Configuration.zh-CN.md)、[动态加载的 GUI](../Tutorial/GUI.zh-CN.md) 等。   NPLA 提供了比大多数现有的程序设计语言更强大的一般抽象。这集中体现在: * 和 NPL 的原始设计一致,不提供也不要求区分实现的阶段(phase) 。 * 支持[一等环境](#npla-环境)不修改现有语言的[求值算法](#求值规约)即可实现共享类似语法的新语言。 * 允许以一般手段表达求值和未求值表达式的差异。   这意味着 NPLA 是本质上动态的语言,但和一般语言不同,用户可以很大程度上动态地替换现有语言实现,包括在运行时替换一个解释实现为一个或多个优化编译器。这也意味着语言设计上既不需要区分解释实现和编译实现(本质上不对立),也不需要区分动态和静态(因为随时能从基础语言上构造出静态子集)。   这样的特性设计在绝大多数语言中不存在并且几乎无法支持。已知唯一的例外是 [Kernel](http://web.cs.wpi.edu/~jshutt/kernel.html) ,在这些特性上有极大的相似,尽管实际上基本特性是**独立设计的**,并且在基本[设计哲学](#领域设计原则)上有极大不同( NPL 和 \[RnRK] 中明确的 guidelines有很大不同且基本不兼容)。不过,考察设计的完整性,NPL 的派生语言也从中借鉴了一些重要的设计: * [`$vau`](http://lambda-the-ultimate.org/node/4093) 、*合并子(combiner)* /*应用子(applicative)* /*操作子(operative)* 等术语。 * 在[绑定构造](#绑定构造)中支持模式匹配的[形式参数树](#绑定匹配)。 * 一些以合并子形式提供的操作。 * 一般的作为接口提供的合并子在 NPL 中仍称为函数;合并子是作为表达式的函数的特定的[求值结果](#基本语义概念)。 * **注释** 在 Scheme 中,合并子中的应用子对应[过程](#过程)。 * 相似的操作主要体现在[名称](../Terminology.zh-CN.md#程序设计语言)和[语义](../Terminology.zh-CN.md#自指)上。因为一些基本设计的差异,不保证完全兼容。 * 相似操作的实现不尽相同,但其中不通过[宿主语言](#实现的执行阶段)的直接的实现(称为*派生(derivation)* )有一部分几乎完全相同。 * 使用 `$` 作为一些[标识符](#基本词法构造)(如 `$lambda` )的前缀是独立设计的巧合;现在含义已和 Kernel 一致,表示 *special form* 。   一些值得注意的和类似语言的主要设计差异(原理详见开发文档): * NPL 和 Kernel 类似,强调[一等对象](#基本语义概念),但含义有所不同。此处的“对象”和 \[ISO C] 及 \[ISO C++] 中的定义类似,具有比 Kernel 更严格的含义。 * 和 Kernel 合并子及 Scheme 过程类似,NPLA 默认使用[按值传递](#求值策略)参数和结果;但与之不同,不隐式对实际参数[别名](#对象别名),不[共享](#共享引用)对象。 * NPLA 和[派生实现](#略称)的语法和整体的求值类似 Scheme 和 Kernel 大多数基于 S-表达式的 [Lisp](https://en.wikipedia.org/wiki/Lisp_%28programming_language%29) 方言,但有一些显著的区别。 * 和 Scheme 不同,而和 Kernel 一致,NPLA 避免顶层(top-level) 和局部的上下文的差异。 * NPLA1 明确区分约定包括列表项的求值规则。和传统习惯不同,NPLA1 中括号明确不需要表示应用的含义,这可以减少一些场合(如命令行)需要输入的过多的连续括号。 * 和 \[RnRS] 定义的 Scheme 以及 \[RnRK] 定义的 Kernel 一致,不支持某些 Lisp 方言的方括号 `[]` 替代圆括号 `()` 的语法。 * 不提供注释语法。 * 语言实现中[内置预处理处理中缀 `;` 和 `,`](#中缀语法) ,作为前缀合并子 `$sequence` 和 `list` 的语法糖。两者的含义和 Kernel 中的相同(类似 Scheme 的 `begin` 和 `list` )。 * 和 Kernel 相似而和 Scheme 不同,使用操作子及一等环境和 `eval` 代替 Scheme 的[卫生宏(hygienic macro)(en-US)](https://en.wikipedia.org/wiki/Hygienic_macro) 及宏展开的作用。 * 和 Kernel 类似,鼓励使用直接求值风格而不是[引用(quote)](#求值算法实现风格) 。 * 不过 NPLA 也提供了 `$quote` 的派生而非如 Kernel 一样完全避免。 * 和 Kernel 不同,NPL 明确支持资源抽象,**不保证支持**[循环引用](#自引用数据结构和循环引用),而 NPLA 明确**不支持**循环引用。 * NPLA 明确支持基于 \[ISO C++] 实现的对象模型和[互操作](../Terminology.zh-CN.md#规范),且明确**不要求**支持全局 [GC](https://zh.wikipedia.org/zh-cn/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_%28%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8%29) 。 * 从在互操作的目的出发,和 C++ 具有相似性和相容性。 * 支持基于 [C++ 抽象机语义](https://eel.is/c++draft/intro.abstract)的更一般的[副作用](#基本语义概念)(除原生 `volatile` 外)和一等状态(first-class states) 。 * 和 \[ISO C++] 类似,在[语言规则](../Terminology.zh-CN.md#规范)中保留特定的[未定义行为](../Terminology.zh-CN.md#程序设计语言),而不要求实现避免。 * 暂时不直接支持多线程环境,但可以在不同线程上同时使用不同的实现的实例。 * 函数默认使用不隐式别名的按值调用和返回传递复制或转移值,和 C++ 对应上下文的复制初始化(copy initialization) 语义一致。(不过求值为操作子的 NPL 函数在 C++ 没有直接的对应。) * 在 vau 演算的论文 (\[Shu10]) 中,提及不支持全局 GC 有较大的管理开销(admistrative cost) 但没有详细讨论和[语言特性](../Terminology.zh-CN.md#规范)的联系。 * 即便不支持全局 GC ,当前实现仍然**明确支持** [PTC(proper tail call)](#尾调用和-ptc) 。 * PTC 基于语言规则而不是[实现行为](#实现行为)定义,详见 [proper tail recursion](https://www.researchgate.net/profile/William_Clinger/publication/2728133_Proper_Tail_Recursion_and_Space_Efficiency/links/02e7e53624927461c8000000/Proper-Tail-Recursion-and-Space-Efficiency.pdf) ,这里和 Kernel 提供的保证含义一致。 * 没有在其它语言发现这种不支持全局 GC 和支持类似 C++ 副作用的情形下的 PTC 支持的先例。 * 和 Kernel 不同,NPLA 不完全强制对象类型的[封装](#封装);且基于支持互操作的考虑,支持[开放](#类型全集)的[类型系统](#类型系统和类型机制),而不要求覆盖所有值(即要求对象类型分区(partition) )。 * 对机器数(不论是整数还是浮点数)的操作被剥离了,当前不被支持,需要用户代码添加个别操作。 * NPLA 的[规约](#基本语义概念)框架和 vau 演算的[操作语义](#形式语义方法)几乎完全一致,不过实际上(因为先前语言设计上的不确定)显著地保留了更多的可扩展和可修改性。 ## 当前具体实现   当前派生实现的 [NPLA1](#npla1-核心语言) 由 YFramework 提供 [API](../Terminology.zh-CN.md#程序设计语言) 。其中包括 [REPL (read-eval-print loop)](https://zh.wikipedia.org/wiki/%E8%AF%BB%E5%8F%96%EF%B9%A3%E6%B1%82%E5%80%BC%EF%B9%A3%E8%BE%93%E5%87%BA%E5%BE%AA%E7%8E%AF) 的解释实现。外部文件的形式的 NPLA1 脚本可被基于这些 API 实现的 [stage 1 SHBuild](../Tools/SHBuild.zh-CN.md) 调用并用于 YFramework 的构建。   由 YFramework 对外部编码的假设,NPLA1 实现加载的文件流的剩余内容的编码视为 UTF-8 ;同时支持 CR+LF 或 LF 为换行符。 **注释** 这些实现基于 YSLib API 提供互操作支持。 # 绪论 ## 正式引用   仅在此给出本文档中的外部引用的名称。其它引用文献的内容详见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 * \[ISO C] [ISO/IEC 9899](https://www.iso.org/standard/74528.html) * \[ISO C++] [ISO/IEC 14882](https://www.iso.org/standard/79358.html) * \[ISO C++11] [ISO/IEC 14882:2011](https://www.iso.org/standard/50372.html) * \[ISO C++14] [ISO/IEC 14882:2014](https://www.iso.org/standard/64029.html) * \[ISO C++17] [ISO/IEC 14882:2017](https://www.iso.org/standard/68564.html) * \[ISO C++20] [ISO/IEC 14882:2020](https://www.iso.org/standard/79358.html) * \[WG21] (ISO/IEC JTC1/SC22/WG21) [C++ Standards Committee Papers](https://www.open-std.org/jtc1/sc22/wg21/docs/papers) * \[WG21 P0135R1] Richard Smith, [Wording for guaranteed copy elision through simplified value categories](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0135r1.html), 2016-06-20. * \[RnRK] [Revised Report on the Kernel Programming Language](https://web.cs.wpi.edu/~jshutt/kernel.html) * \[Shu09] John N. Shutt, [Revised-1 Report on the Kernel Programming Language](ftp://ftp.cs.wpi.edu/pub/techreports/pdf/05-07.pdf), Technical report WPI-CS-TR-05-07, Worcester Polytechnic Institute, Worcester, MA, March 2005, amended 29 October 2009. * **注释** 当前 \[RnRK] 只有 n = -1 的版本。引用确切版本时,同 \[Shu10] 中的用法,使用 \[Shu09] 标记;但引用时一般同样不涉及其版本差异。 * \[Shu10] John N. Shutt, [Fexprs as the basis of Lisp function application; or, `$vau`: the ultimate abstraction](https://web.wpi.edu/Pubs/ETD/Available/etd-090110-124904/unrestricted/jshutt.pdf), Ph.D. Dissertation, WPI CS Department, 2010. * \[RnRS] [Revised Report on the Algorithmic Language Scheme](http://www.scheme-reports.org/) * \[R5RS] [Revised5 Report on the Algorithmic Language Scheme](https://schemers.org/Documents/Standards/R5RS/r5rs.pdf) * \[R6RS] [Revised6 Report on the Algorithmic Language Scheme](http://www.r6rs.org/final/r6rs.pdf) * \[R6RS-Rationale] [Revised6 Report on the Algorithmic Language Scheme -Rationale-](http://www.r6rs.org/final/r6rs-rationale.pdf) * \[R7RS] [Revised7 Report on the Algorithmic Language Scheme](https://small.r7rs.org/attachment/r7rs.pdf) * \[Fl91] Matthias Felleisen, [On the Expressive Power of Programming Languages](https://www.ccs.neu.edu/racket/pubs/scp91-felleisen.ps.gz), _Science of Computer Programming_ [Volume 17, Issues 1–3](https://www.sciencedirect.com/journal/science-of-computer-programming/vol/17/issue/1), December 1991, pp. 35–75. * \[EGAL] James Noble, Andrew P. Black, Kim B. Bruce, Michael Homer and Mark S. Miller, [The Left Hand of Equals](http://web.cecs.pdx.edu/~black/publications/egal.pdf), Onward! 2016: _Proceedings of the 2016 ACM International Symposium on New Ideas, New Paradigms, and Reflections on Programming and Software_, October 2016, pp. 224–237. * \[So90] Harald Søndergaard and Peter Sestoft, [Referential transparency, definiteness and unfoldability](http://www.itu.dk/people/sestoft/papers/SondergaardSestoft1990.pdf), [_Acta Informatica_](https://link.springer.com/journal/236) 27, 1990, pp. 505–517. * \[Rust] [The Rust Reference](https://doc.rust-lang.org/reference/index.html) * 不定期更新。 * \[Fi94] Andrzej Filinski, [Representing Monads](https://dl.acm.org/doi/pdf/10.1145/174675.178047), POPL '94: _Proceedings of the 21st ACM SIGPLAN-SIGACT symposium on Principles of programming languages_, February 1994, pp. 446–457. * \[Hi90] Robert Hieb, R. Kent Dybvig and Carl Bruggema, [Representing Control in the Presence of First-Class Continuation](https://legacy.cs.indiana.edu/~dyb/pubs/stack.pdf), _ACM SIGPLAN Notices_, Volume 25, Issue 6, Jun. 1990, pp. 66–77. * \[Racket] [Racket Documentation](https://docs.racket-lang.org/) * 不定期更新。 * \[Chu41] Alonzo Church, [_The Calculi of Lambda-Conversion_](https://compcalc.github.io/public/church/church_calculi_1941.pdf), Annals of Mathematics Studies, Princeton: Princeton University Press, 1941. * \[Bare84] Hendrik Pieter Barendregt, [_The Lambda Calculus: Its Syntax and Semantics_](https://philpapers.org/rec/BARTLC) \[_Studies in Logic and the Foundations of Mathematics_ 103], Revised Edition, Amsterdam: North Holland, 1984. * \[Cl98] William D. Clinger, [Proper Tail Recursion and Space Efficiency](https://www.researchgate.net/profile/William_Clinger/publication/2728133_Proper_Tail_Recursion_and_Space_Efficiency/links/02e7e53624927461c8000000/Proper-Tail-Recursion-and-Space-Efficiency.pdf) * \[IEC 60559] [ISO/IEC 60559](https://www.iso.org/standard/80985.html) * \[ECMAScript] [ECMA-262](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/) * \[ECMAScript 2019] [ECMA-262 10.0](https://262.ecma-international.org/10.0/) ## 领域设计原则   本节描述被本文档中的一些原理讨论引用的的公共依据。   原则指关于设计和实现的哲学,同时作为一般规则约束设计和实现的工程阶段。   关于需求特别是通用目的语言的讨论,参见[需求概要(en-US)](https://github.com/FrankHB/pl-docs/blob/master/en-US/calling-for-language-features.md#module) 。 ### 本体论   为使论述有效,约定*本体论(ontology)* 规则。   基本的本体论规则是约束逻辑系统构造的公理。 #### 正规性   有效的陈述(如需求描述)应保证操作上可预期结果。   在此意义下,缺乏约束性的规则不可预期的风险是代价。   推论:规则应适当约定适用范围,以避免外延不清。 #### 存在性   语义的存在体现本质。   仅仅应用语法规则,即限定为*语法的文法(syntactic grammar)* 的形式系统归纳的设计,不被视为表示任何[有效](#正规性)的含义。 #### 名实问题   名义概念的内涵和外延应被足够显式指定,避免指涉上的歧义,允许构造[有效的陈述](#正规性)。 #### 不可分的同一性   [不可分的同一性(the identity of indiscernibles) (en-US)](https://plato.stanford.edu/entries/identity-indiscernible/) 比较陈述的客体之间是否相同而不需要被重复地处理。 ### 价值观   价值观是关于价值判断的规则,其输出为二元的值,决定是否接受决策。   作为应对普遍需求场景的不同解决方案选型时的价值判断的抽象归纳,价值观被作为比较是否采用设计相关决策的全局依据。   以下陈述形式表达价值优先的选项,同时作为公理。 **注释** 相同推理结果仍然可能不唯一,这来自于自然语言描述的输入的不精确性。 #### 变化的自由   在明确需求的前提下,尽可能保证对现状按需进行改变的可行性和便利性。   适用于一般需求。   对计算机软件或其它可编程的实体:尽可能避免不必要地损失可修改性,便于保障按需引入或除去[接口](../Terminology.zh-CN.md#非自指)及其实现的自由。 **原理**   一般地,需求可能随着不可控的外部条件变化。假设已明确的需求不变只能适合相当有限的情形。积极应对变化能提供价值。 #### 避免不必要付出的代价   尽可能消除对满足需求无意义的代价,减少影响需求实现的整体成本。   适用于一般需求中设计决策的比较。   对计算机软件或其它可编程的实体:不为不需要的[特性](../Terminology.zh-CN.md#规范)付出代价。 **注释**   一个类似的表述: > Efficiency has been a major design goal for C++ from the beginning, also the principle of “zero overhead” for any feature that is not used in a program. It has been a guiding principle from the earliest days of C++ that “you don’t pay for what you don’t use”.   — [ISO/IEC TR 18015](https://www.iso.org/standard/43351.html) #### 最小接口原则   在满足需求的前提下,尽可能使用符合倾向减小实现需求代价的单一良基关系下具有极小元的接口设计。 **注释** 减小实现需求的代价,如减小设计工作量。   这是一条模式规则,依赖具体情形何者符合良基关系的极小元这条非模式规则作为输入。   实际使用时,非模式规则可以直接指定为二元关系的子集,或者一种良序的度量。 **注释** 例如,“公开函数声明数”“模块数”。   这个输入也可能直接对应符合需求集合的某种最小功能集合而不需要附加度量,如表示某种设计的裁剪。   注意规则指定的基数是对实现需求有意义的代价,因此不涵盖[避免不必要付出的代价](#避免不必要付出的代价)。   在确定的范围内尽可能少地提供必须的接口,避免不必要的假设影响接口适应需求的能力,同时减少实现需求的各个阶段的复杂性。   适用于一般需求的实现,特别地,强调“通用”目的时。   对需要在计算机上实现的人工语言设计:设计语言不应该进行功能的堆砌,而应该尽可能减少弱点和限制,使剩下的功能显得必要。 > Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.   — \[RnRS] & \[RnRK] **注释**   其它各个领域中的实质等价一些表述包括: * 用于安全系统设计的[最小特权原则](#最小特权原则)。 * 用于自然科学理论设计的[奥卡姆剃刀(Occam’s Razor)](https://zh.wikipedia.org/zh-cn/%E5%A5%A5%E5%8D%A1%E5%A7%86%E5%89%83%E5%88%80) 原理,避免不必要的假设引入诉诸无知(argument from ignorance) 谬误。 #### 关注点分离原则   [关注点分离(separation of concerns, SoC)](https://zh.wikipedia.org/zh-cn/%E5%85%B3%E6%B3%A8%E7%82%B9%E5%88%86%E7%A6%BB) 原则 :局部设计的内容应和需求的陈述或其它隐含的设计前提分别一一对应。   适用于一般需求的实现,特别是其中依赖认识论观点的过程。 **原理**   这条规则利用需求和设计内容陈述中概念外延普遍蕴含的*局域性(locality)* ,提供给定代价下更多的可行性或求解给定问题时使用较小的代价,用于: * 应对不可控复杂条件下使问题可解。 * 局部可复用现有解的子集。   此外,尽管并非总是必要,应用知识内容的简单假设、[最小接口原则](#最小接口原则)和本原则可在认识论上导出还原论。 ### 形而上学   根据作为需求的[价值观](#价值观),归纳适用于通用目的语言应有的构成及其性质(形而上学(metaphysics) )的设计规则,包括三条价值判断实现公理: * 设计应尽可能满足[正确性](#正确性)。 * 设计应尽可能满足[简单性](#简单性)。 * 设计的正确性应优先于简单性。   具备这些性质的设计可视为由[价值判断](#价值观)蕴含,预设前提为真的设计[方法论](#方法论)的实现。 **注释**   注意和 [worse is better](https://en.wikipedia.org/wiki/Worse_is_better) 或 [the MIT approach](https://en.wikipedia.org/wiki/Worse_is_better#The_MIT_approach) 不同,设计的性质并非完全并列。特别地,[完整性](#完整性)和[一致性](#一致性)都被作为正确性的一部分考虑。   因为[变化的自由](#变化的自由),具体需求以及判断正确性和简单性的确切依据都可能会随着项目的进展而变化。 #### 正确性   设计应正确地反映需求,不在需求的基础上新增作为实现细节以外的不确定性。   无法确保满足这种*正确性(correctness)* 要求时,不应继续设计。   正确性包含*可行性(feasibility)* 。   若无法满足正确性,则需求输入存在问题。   正确性不包含但应逻辑蕴含设计的一些其它性质。若无法实现,则具体性质的定义存在问题。   保持正确性作为设计评价的首要依据以使决策简单,同时能符合[价值判断](#价值观)。 ##### 完整性   正确性应蕴含*完整性(completeness)* ,即确保没有需求被遗漏。   推论:设计应包含完整的需求响应。 **原理**   对通用编程语言的一个完整性要求是支持[计算](../Terminology.zh-CN.md#计算机科学)上的*可表达性(expresiveness)* 。   这种性质被称为*可有效计算性(effective computability)* ,或 *Turing 完备性(Turing completeness)* 。在可物理实现的计算普遍遵循 [Church–Turing 论题(Church–Turing thesis)](https://zh.wikipedia.org/zh-cn/%E9%82%B1%E5%A5%87%EF%BC%8D%E5%9B%BE%E7%81%B5%E8%AE%BA%E9%A2%98) 的情形下,这同时是*可计算性(computability)* 。以上性质一般不加分辨。   具体的语言中允许的表达的可计算性是*表达能力(expressive power)* 。另见 \[Fl91] 。   特定的场合要求更弱的性质。例如,类型检查等情形需要*全(total)* 计算而确保实现总是[可终止](#计算复杂度约定)。这种要求在完整的实现中可通过附加的设施(用户提供的标注或证明)保证,而不应通过系统设计的规则静态地排除,否则实现是不完整的。仅在作为领域特定语言时,通过从需求中排除可计算性,静态规则作为优化是被允许的。 ##### 一致性   正确性应蕴含一致性,即内部的逻辑无矛盾性。   推论:设计应保证一致性。 #### 简单性   在满足正确性的前提下,接口设计应尽可能满足简单性(simplicity),即尽可能少地具有可被继续简化的内容。   接口设计的简单性优先于实现的简单性。 #### 可修改性   *可修改性(modifiablity)* :在满足需求的前提下,修改应尽可能少地有碍于其它的接口。   这是[变化的自由](#变化的自由)的推论。 #### 避免抽象泄漏   *泄漏的抽象(leaky abstraction)* 指抽象的底层复杂性没有被抽象合理地[隐藏](#信息隐藏),而在一定程度上构成了利用抽象时的不必要的依赖。   这种*抽象泄漏(abstraction leak)* 的结果直接和[避免不必要付出的代价](#避免不必要付出的代价)、[关注点分离原则](#关注点分离原则)和[简单性](#简单性)冲突。   同时,抽象的有效性被削弱,泄漏构成的不被预期的依赖难以满足[正确性](#正确性);只要有避免抽象泄漏的方法,就不满足[最小接口原则](#最小接口原则)。   因此,只要可能,避免抽象泄漏。 **注释** 在信息安全意义上,抽象泄漏还可能提供难以抵御的附加的攻击信道。 #### 关注资源限制   为了可实现性,*宿主(host)* 系统对总的资源(典型地,运行程序需要的存储)有[未指定](../Terminology.zh-CN.md#程序设计语言)的上限。   除此之外,接口抽象不附加接口语义要求以外的限制。   这个原则同时利于满足[正确性](#正确性)和[简单性](#简单性)。而不遵循这个原则的设计在接口描述上违反[最小接口原则](#最小接口原则)。   在允许实现的前提下,附加具体[特性](../Terminology.zh-CN.md#规范)上的使用限制(如 \[ISO C] )可放宽对实现的要求;但无原则地随意选取此处的限制不足以直接证明具体的限制的有效性,而依赖实际实现的情况才能判断,造成[抽象泄漏](#避免抽象泄漏)。 **注释** 实例:[PicoLisp](https://software-lab.de/doc/faq.html#lambda) 使用符合此原则的设计。 #### 开放性   *开放性(openness)* :除非另行指定,不假定实体不存在。   这个原则主要用于建模(modeling) 的依据。对一般的模型,这个原则称为[开放世界假定(open-world assumption)](https://zh.wikipedia.org/zh-cn/%E5%BC%80%E6%94%BE%E4%B8%96%E7%95%8C%E5%81%87%E5%AE%9A)。   与之相对,[封闭世界假定(closed-world assumption)](https://zh.wikipedia.org/zh-cn/%E5%B0%81%E9%97%AD%E4%B8%96%E7%95%8C%E5%81%87%E5%AE%9A) 需要提前设置一个*全集(universe)* 以保持至少在逻辑的意义上[合规](#正规性)。   开放世界的元素的全集是模型的结构化规则推断得到的,而非名义上的定义决定。这同时称为模型的语言的*论域(universe of disclosure)* 。 **原理**   封闭世界假定表面上可能简化实现,但在一般的模型中是不必要的,因为保持问题合规性的论域应已由清晰的需求描述规范,不应为此阻碍实现[变化的自由](#变化的自由)。   使用封闭世界假定的一个主要实用意义是使模型在有限的信息下能推理出逻辑上更强的结论。在重视结论的知识系统中,这通常是一种优化;但在重视[表达能力](#完整性)(而通过其它方式辅助求解问题)的通用模型中,这种前提是一种直接的限制。同时,封闭世界假定的优化不保证对所有输入有效,对否定输入还可能导出一些矛盾。 **注释**   开放世界包含的元素的外延及其语言的论域伴随随[语言规则](../Terminology.zh-CN.md#规范)的修改而改变。   开放世界不限制论域中的某个子集是封闭的。例如,论域中可能存在某个子集的所有元素通过一定方式被枚举。 ### 结构和依赖原则 #### 接口设计和实现分离   语言设计独立于[语言实现](../Terminology.zh-CN.md#规范)。   这是同时应用[最小接口原则](#最小接口原则)和[关注点分离原则](#关注点分离原则)的推论。   这种分离允许[避免抽象泄露](#避免抽象泄漏)。   典型地,使用提供接口抽象层作为必要构造的架构方法,即分层设计。 #### 最小特权原则   [最小特权原则(principle of least privilege, PoLA)](https://zh.wikipedia.org/zh-cn/%E6%9C%80%E5%B0%8F%E6%9D%83%E9%99%90%E5%8E%9F%E5%88%99) :除非有必要,接口抽象不提供满足需求以外的其它信息和资源。   这是[最小接口原则](#最小接口原则)在限制适用领域前提下的等价表述之一,用于避免不必要的访问路径引入额外的安全(safety) 风险,更容易满足(针对恶意使用风险的)安全性(security) 和可信性保证相关的需求。   实质上提供例外的必要性之一是接口正确性:不附加不存在于需求以外的安全设计;根据[可修改性](#可修改性),这应是实现细节。 #### 最小依赖原则   最小依赖原则(principle of least dependencies) :除非有必要,接口实现仅使用必要的依赖。   这是[最小接口原则](#最小接口原则)的推论之一,其非模式规则的输入为: * 已知必要的依赖较已知必要的依赖和不必要的依赖的并集要求较小的使用和维护成本。 * 这里的使用包括演绎抽象自身的推理(reasoning) 。依赖较少时,推理时需要搜索的解空间也越小。 ##### 单一模块依赖倒置原则   依赖倒置原则(dependence inversion principle) 在单一模块下包含以下含义: * 抽象(的接口)不应该依赖(实现)细节。 * (实现)细节应依赖抽象(的接口)。   这是最小依赖原则应用在不同抽象的[模块化设计](#模块化)中使用以下公设的推论:   抽象是细节包含的子集,依赖抽象的接口较依赖实现细节具有更少的依赖。 #### 可复用性   设计应具有*可复用性(reusability)* :高层抽象设计的实现应包括复用此设计的实现的设计。   这是[最小接口原则](#最小接口原则)的推论之一,其非模式规则的输入为以下公设:   一般地,高层抽象设计和复用此设计的实现较单一的高层设计的实现更复杂。   此前提条件由对需求工作量可行性分析中的度量验证总是成立。   推论:除非必要,不分离抽象设计的实现和复用此设计的实现的设计,避免复杂性。   全局意义上的不分离设计不违反[关注点分离原则](#关注点分离原则)。 **注释**   典型实例:语言是一种高层抽象设计,语言的*库(library)* 是一种复用语言的设计。因此,语言实现应包括库设计。   另一个实例是[对象语言](../Terminology.zh-CN.md#程序设计语言)设计复用[元语言](../Terminology.zh-CN.md#程序设计语言)的[语言规则](../Terminology.zh-CN.md#规范)。 #### 可组合性   *组合(composition)* 是一种特定形式的涉及多个实体的复用,允许复用时不修改被复用的其它实体。   *可组合(composability)* 原则:接口的设计应允许不同设计之间的组合满足这些设计响应以外的需求。   这是[最小接口原则](#最小接口原则)的推论之一,其非模式规则的输入为以下过程推断得到的引理。   公设:一般地,在存在充足基础解决方案的情形下,组合现有解决方案的设计较重新给出不依赖这些解决方案的设计的解节约成本。   应用[避免不必要付出的代价](#避免不必要付出的代价),得到引理:   一般地,在存在充足基础解决方案和满足需求限制的情形下,组合现有解决方案的设计优于重新设计。   即提升可组合性可减少实现被[复用](#可复用性)的设计的成本。 ### 接口设计性质和原则 #### 统一性   接口的设计应具有*统一性(uniformity)* :尽可能避免特例。   这是要求[变化的自由](#变化的自由)的推论之一,以[一致性](#一致性)作为非模式规则输入。   无限制的特例要求指定更多的附加规则避免潜在的违反一致性的风险,而违反这个要求。   因为不需要特设只有对象语言中可用的规则,[复用元语言规则](#可复用性)有利于实现统一性。   以统一的方式复用元语言和对象语言公共设施在[语法](../Terminology.zh-CN.md#计算机科学)设计上称为*光滑性(smoothness)* ,而这可推广到[语义](../Terminology.zh-CN.md#计算机科学)上(另见[正交性](#正交性)),以避免对*抽象能力(power of abstraction)* 的限制(\[Shu10] §1.1.2) 。 **原理**   在语言设计上,这类似 \[RnRK] 的设计原则 G1 : * G1a 对象状态(object status) :语言操作[一等对象](#基本语义概念)。 * G1b 可扩展性(extensibility) :用户定义的设施能重现内建[特性](../Terminology.zh-CN.md#规范)的*能力(capability)* 。   以上原则在 NPL 中略有变化。   同 \[RnRK] ,被 G1b 重现能力的特性是内建的(built-in) 。这不同于如 \[RnRK] G2 指定的基本的(primitive) 特性。   \[RnRK] 的基本特性指不要求作为派生的(derived) ,即以[对象语言](../Terminology.zh-CN.md#程序设计语言)程序实现的特性。而内建特性适合整个[语言规范](../Terminology.zh-CN.md#规范)的接口设计约定,不论其实现是否被派生。不被要求重现的部分是实现细节。   但是,因为基本特性不要求能通过对象语言特性的组合实现,在不考虑派生特性的可实现性时,G1b 不会限定基本特性的能力。   整体上的 G1b 在和[正确性](#正确性)冲突时不被要求。这也避免了 \[RnRK] §0.1.1 指出的“妥协”。   因为语言规范不依赖使用[对象语言](../Terminology.zh-CN.md#程序设计语言)表达,G1b 仅表示用户使用语言的扩展,不表示语言自身的可扩展性;后者通过[满足需求的能力](#变化的自由)和强调支持[开放性](#开放性)体现。   仅通过[用户程序](#程序实现)实现的这种原则在 NPL 的设计中不被视为必要。但偏离这个原则的设计一般同样是不必要的。 **注释**   关于 G1a 的改变,详见[一等实体和一等对象](#一等实体和一等对象)。 #### 适用性   设计应提供*适用性(usability)* :合乎预期满足的问题领域的[特性](../Terminology.zh-CN.md#规范)。   对通用目的的领域,应进行权衡。 **注释**   这个原则存在以下的侧重不同使用方式或场景的具体表述。   结合用户的经验,这个规则的变体是之一[最小惊奇原则](#最小惊奇原则),强调降低接口的学习和适应成本。 ##### 易预测性   设计应符合*易预测性(predictability)* :允许但难以偶然实现的危险操作。   同 \[RnRK] 的设计原则 G3 。   这里的危险的操作指引起较大代价的不预期或无法预期结果的操作。   这是[变化的自由](#变化的自由)和[避免不必要付出的代价](#避免不必要付出的代价)的推论,包含两方面: * 避免危险操作的风险是[正规性](#正规性)和[避免不必要付出的代价](#避免不必要付出的代价)的推论。 * 不直接禁止危险的操作以满足上述的允许变化的要求。   避免危险的操作在许多上下文中可减少程序中易错(error-prone) 的实现的风险。 ##### 可用性   一旦提供[特性](../Terminology.zh-CN.md#规范),应提供*可用性(availablity)* :保证一定程度的典型场景下能被使用。   绝大多数情形都不能使用的特性是对接口设计的一种浪费,很难符合也通常不符合[简单性](#简单性)。   可用性的概念有时也指抽象和实体具有的符合这个原则的属性。 #### 最小惊奇原则   最小惊奇原则(principle of least astonishment):在保持合理性的前提下,若能评估目标用户的接受能力,避免违反其直觉的设计。   其中,合理性至少应蕴含[正确性](#正确性),一般也蕴含[简单性](#简单性)和[适用性](#适用性)同时不违反其它原则(特别应注意尽量保持[可复用性](#可复用性)和[可组合性](#可组合性))。   这个原则主要适用于人机交互接口的设计,但也适用于一般的 [API](../Terminology.zh-CN.md#程序设计语言) 。   推论:[约定优于配置(convention over configuration)](https://zh.wikipedia.org/zh-cn/%E7%BA%A6%E5%AE%9A%E4%BC%98%E4%BA%8E%E9%85%8D%E7%BD%AE) :约定接口的合理的默认[行为](../Terminology.zh-CN.md#程序设计语言),而不是隐藏其行为而提供配置另行实现。 #### 正交性   在满足[正确性](#正确性)的前提下,接口的设计应具有*正交性(orthogonality)* :根据需求适当分解为[排除冗余和重复](#可复用性)且[能合理组合](#可组合性)的部分。   这是[最小接口原则](#最小接口原则)和[关注点分离原则](#关注点分离原则)在接口设计上的应用。   一般地,正交的设计使相同目的可使用更精简的接口组合方式实现。这也使接口具有更强的[抽象能力](#统一性)。 ### 方法论   *方法论(methodology)* 是严格独立[价值判断](#价值观)的规则,是关于[价值判断](#价值观)结果参数化的判断规则。   不同的价值判断的结果作为方法论输入,决定是否适用此方法。   其它方法详见以下各节。 **注释**   一些规则因其主要表述包含价值判断而不在此归纳为方法论,尽管其中一些表述中的前提可以被参数化(如[奥卡姆剃刀](#最小接口原则)的“如无必要”的具体必要条件)。 #### 避免不成熟的优化 > Premature optimization is the root of all evil (or at least most of it) in programming.   — _The Art of Computer Programming_   原始含义适合计算机程序设计中以效率为目标的决策。   扩展的外延适用于一般需求,要求: * 适时收缩理论长度以照顾可操作性。 * 注意断言一个优化过早自身可能就是一个过早的优化。 * 主动适应需求变更。 * 不同时明确全部的具体需求,只限定需求范围:能使用计算机实现部分语义的任务。 #### 封装   *封装(encapsulation)* 是接口设计的合理性准则。   封装是[不可分的同一性](#不可分的同一性)的一种实现方式:封装提供的接口以下的所有实现在接口从使用者的角度都是不可分的。 **注释** 若存在使用者可感知的[抽象泄漏](#避免抽象泄漏),这种实现可能失效。   以接口的预设风格的价值判断为输入,封装性要求接口满足以下*多态性(polymorhism)* :   给定接口的替代接口,则替代接口应能代替原接口,当且仅当不引起非预期的可观察的差异。   在语言设计中,去除风格参数化的这条原则被作为 [LSP(Liskov Substitution Principle)](https://zh.wikipedia.org/zh-cn/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99) 。   参数化风格限定并非任意符合 LSP 的接口设计都符合封装性要求。这便于从不期望的设计中剔除不符合其它原则的设计。 **注释**   一些程序设计语言中的封装提供符合 LSP 的[面向对象](https://zh.wikipedia.org/zh-cn/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1)风格的设施。这些设施把数据和代码组合在一起提供,但仅仅组合并不体现封装性。因此,同时具有[信息隐藏](#信息隐藏)的[特性](../Terminology.zh-CN.md#规范),如 \[ISO C++] 的类成员的访问控制的机制,被认为是典型的封装。   即便如此,封装在严格意义上和信息隐藏是相互独立的。即便语言不提供信息隐藏而仅仅指定违反封装性不关心实现细节的假设的操作[未定义](../Terminology.zh-CN.md#规范),也不失去封装性。事实上,\[ISO C++] 中,使用 `reinterpre_cast` 无视类的访问控制就是这种例子。   另一方面,LSP 事实上关于[子类型](#类型序),不限于以类作为类型的基于类的面向对象风格,实际外延更广。 #### 信息隐藏   [信息隐藏(information hiding)](https://zh.wikipedia.org/zh-cn/%E8%B3%87%E8%A8%8A%E9%9A%B1%E8%97%8F_%28%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8%29) 保持不需要公开的信息不被公开,以使设计符合[最小接口原则](#最小接口原则)并支持[避免抽象泄漏](#避免抽象泄漏)。   适用于接口及其实现。   信息隐藏以是否需要公开信息的[价值判断](#价值观)(特别地,关于如何符合最小接口原则)的结果参数化。 **注释**   [封装](#封装)的接口通常有助于实现信息隐藏。直接限定避免接口规格具有过多的信息,是另一种直接的实现方式。   例如,基于类的[面向对象](https://zh.wikipedia.org/zh-cn/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1)通过对[名称](../Terminology.zh-CN.md#程序设计语言)组成的表达式限制对类成员的外部访问,隐藏了类成员的信息,同时提供[封装性](#封装)。   其它方式可直接不在外部提供任何访问被封装实体的名称,如 \[RnRK] 的封装类型(encapsulate type) 和 [\[ECMAScript\] 通过 `WeakMap` 实现的封装](https://github.com/tc39/proposal-class-fields/blob/main/PRIVATE_SYNTAX_FAQ.md#how-can-you-model-encapsulation-using-weakmaps)。这些封装也同时实现了被封装实体的信息隐藏。   即便如此,如关于封装的讨论指出的,封装不一定需要实现信息隐藏。更一般地,信息隐藏的目的也不一定是提供封装。例如,系统的安全性可能直接在需求上要求隐藏特定信息,不论这种信息是否关于某种接口的实现。 #### 模块化   接口和实现的设计应具有足够的模块化(modularity) :被划分为若干保持联系的组件即*模块(module)* ,至少满足[正确性](#正确性)和[可组合性](#可组合性),并强调实现[可复用性](#可复用性)。   模块化设计通常有利于使设计具有[正交性](#正交性),但模块化相对可复用性,更侧重可组合性。   参数化的输入是需被评估模块化程度的结构设计(包括模块的*粒度(granularity)* 和组成部分的依赖关系)相对给定需求的实现质量的价值判断。 ### 其它推论和比较   从对[正确性](#正确性)的强调可知,较[简单性](#简单性)优先考虑*通用性(generality)* 。   这和 \[RnRK] 中讨论的设计哲学虽然相当不同,但仍允许和 Kernel 具有相似的特性。   作为典型的 NPL 的一个[派生实现](#略称),[NPLA1](#npla1-核心语言) 具有以下和 Kernel 相似的核心设计: * 相似的[求值](#基本语义概念)算法(差异详见 [NPLA1 求值算法](#对象语言求值算法))。 * [环境](#求值环境)可作为[一等对象](#基本语义概念)。 * 支持 [vau 抽象](#vau-抽象),且使用[词法作用域](#实现环境提供的求值环境)。 * 强调支持[对象语言](../Terminology.zh-CN.md#程序设计语言)中的[显式求值风格](#其它设计和实现参考)及[表达式](#表达式)求值前后的不同。 * 强调直接求值而非传统 LISP 方言的 [`quote`](#求值算法实现风格) 。 # 规格说明   在[附录](#附录)之前的以下章节给出 NPL 的正式规格说明的公共部分,即[语言规范](../Terminology.zh-CN.md#规范)。   本文档仅提供部分[派生实现](#略称)的规格说明。关于其它具体规格说明,详见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。   在不和其它[语言规则](../Terminology.zh-CN.md#规范)冲突时,派生实现可能补充或覆盖更确切范围中生效的定义和具体语言规则。 ## 略称   仅在不致混淆时使用。 * 实现(implementation) :[语言实现](../Terminology.zh-CN.md#规范)。 * 环境(environment) :[实现环境](../Terminology.zh-CN.md#规范)。 * 外部环境:(当前描述的系统边界的)外部的实现环境。 * 派生实现(derived implementation) :[派生语言实现](../Terminology.zh-CN.md#规范)。 ## 补充领域定义   以下术语的定义参见[计算机体系结构](../Terminology.zh-CN.md#计算机体系结构)。 * 指令 * 指令集 * ISA # 整体设计   一些[语言规则](../Terminology.zh-CN.md#规范)可能显式地由[派生实现](#略称)指定,或补充具体规则。 **注释**   具体讨论设计策略另见需求描述文档。   另见[设计原则](#领域设计原则)的讨论;对本章内容的描述的理解应符合其中的原则。 ## 模型   可用计算机实现的语言首先是*计算的模型(model of computation)* ,或者[计算模型](../Terminology.zh-CN.md#计算机科学),对[计算](../Terminology.zh-CN.md#计算机科学)进行建模得到。   与之相关地,为计算机系统建模作为*计算机的模型(model of computer)* ,需对有限计算资源的现实进行适应。   这些模型可使用[形式方法](../Terminology.zh-CN.md#计算机科学)建立,即[形式模型](../Terminology.zh-CN.md#计算机科学)。   被计算机实现的语言应同时具有这两方面的特征。   作为实用的语言,语言还应强调提供[可编程性](../Terminology.zh-CN.md#程序设计语言)以允许用户利用;这样的语言称为*编程语言(programming language)* 。   本设计尝试在语言的原生设计中应对现有语言缺乏模型问题以避免这些妥协带来的消极影响,同时取得比非模型方法更强的可用性。   这种可用性至少体现在语义的精确性可通过模型直接决定;仅为精确性,不需要另行补充模型设计(尽管现有模型可能仍然是不完全形式化的)。 **原理**   以无限的计算资源为前提,理想的模型无法被物理地完全实现,无法直接作为计算机实现的语言的模型。   同时,这些模型仅适合对计算建模,并没有强调允许可编程性的实现;扩充可编程设计而保持模型自身的主要性质相当困难。   因此,基于计算的模型适配编程语言的设计必然需要妥协:对这些模型的裁剪和补充能提供若干编程语言的模型,但这无可避免地显著地复杂化模型自身,且不利用用户使用简单有效的规则实现通用目的上的可编程性。   事实上,使用严格形式化的模型描述编程语言的[行为](../Terminology.zh-CN.md#计算机科学)较编程语言自身的发展更落后: * 大部分编程语言并没有使用模型支持它们的设计。 * 现实的实用语言,特别地,包括所有主流的*工业语言(industrial language)* ,几乎都没有在[语言规范](../Terminology.zh-CN.md#非自指)中给出完整的模型。 * 通常的实用语言只形式化基本的[语法](../Terminology.zh-CN.md#计算机科学)上的规则,无法指导用户精确理解程序的含义。   这些落后集中体现在的[语义](../Terminology.zh-CN.md#计算机科学)模型的缺失,使对编程语言语义的判断取决于规格说明中模型外规则的理解。   后验(postpone) 的语义模型可以使用不同[形式语义方法](#形式语义方法)设计,但和语言规范差异的一些本应避免的附加工作,并且通常难以完整地作为标准规格的描述。 **注释**   Turing 机、[无类型 λ 演算(untyped lambda calculus)](https://zh.wikipedia.org/zh-cn/%CE%9B%E6%BC%94%E7%AE%97) 等早期计算模型不考虑有限计算资源限制。 ### 计算复杂度约定   特定的算法过程具有[计算复杂度](../Terminology.zh-CN.md#计算机科学)要求。除非另行指定: * 这些复杂度是任意避免符合[错误条件](#错误)的方式调用时求值蕴含的*渐进(asymptotic)* 时间复杂度。 * 若指定边界,明确的输入规模以*哑变量(dummy)* n 表示。 * 指定复杂度的计算保证可*终止(terminate)* 。 **注释**   算法过程也适用[对象语言](../Terminology.zh-CN.md#程序设计语言)上的操作。 ### 资源可用性基本约定   在[抽象机](#实现行为)的配置中,任意通过[*元语言(metalanguage)*](../Terminology.zh-CN.md#程序设计语言) 语法描述的资源总是可用的。   为避免对具体资源的总量和实现细节做出假设,除此之外,本设计只要求模型蕴含[所有权](#所有权抽象)语义(即便不严格形式化——注意作为元语言的描述模型使用的形式语言仍然可能是实现细节)。   具体计算机系统的实现中,保证基本可用的资源被直接映射到程序执行(execution) 的环境中。尽管和适配的软件环境相关,这最终由硬件实现物理地保证。 **原理**   在严格的[资源限制要求](#关注资源限制)下,[模型](#模型)不能隐藏预设的无限资源的前提。   因此,有必要做出基本的[可用性](#可用性)约定以允许表达明确的要求以避免不可实现。 ## 适用领域   为尽可能解决[模型](#模型)相关的问题,优先以[通用目的](#需求概述)而不是*领域特定(domain-specific)* 语言作为评估[语言特性](../Terminology.zh-CN.md#规范)设计的参考原则。   领域特定语言的特性应能合理地从支持通用目的的特性中[派生](../Terminology.zh-CN.md#非自指),且不影响实际的可用性。 ## 形式语义方法   [形式语义](../Terminology.zh-CN.md#计算机科学)方法是建立[语义模型](#模型)的形式方法。   形式语义方法主要有*公理语义(axiomatic semantics)* 、*指称语义(denotational semantic)* 和*操作语义(operational semantics)* 。   操作语义可分为在模型中指定具体[规约](#基本语义概念)步骤状态的*结构化操作语义(structural operational semantics)*(或*小步(small-step)* 语义),及仅指定规约的输入和输出的*自然语义(natural semantics)*(或*大步(big-step)* 语义)。 **注释** [抽象机](#实现行为)和[演算](#项重写系统)是使用操作语义的模型的两类例子,虽然后者也可以对[对象语言](../Terminology.zh-CN.md#程序设计语言)以外的表示建模而实现其它的语义方法。   非确定语义:经验语义,不需要使用自然语言解释的部分。   本文档不直接给出形式语义。[语言规则](../Terminology.zh-CN.md#规范)确定的经验语义可在一定条件下转写为上述形式语义方法表达的形式。 ## 程序实现   [程序](../Terminology.zh-CN.md#程序设计语言)是语言的具体派生。实现程序即在语言的基础上指定具体派生规则。   [语言实现](../Terminology.zh-CN.md#规范)外的程序是*用户程序(user program)* 。   以程序或另行指定的其它形式实现的可复用程序被归类为[库](#可复用性)。 **注释** 一般地,不论是语言实现还是用户程序,都可能使用库。   除非另行指定,一个程序支持多个库的实例,之间不共享内部的状态。   语言特性包含不依赖库的*核心语言特性(core language feature)* 和*库特性(library feature)* 。 ## 规范模型   NPL 是抽象的语言,没有[具体语言实现](../Terminology.zh-CN.md#规范),但一些直接影响实现表现形式的规则被本节限定。   NPL 的实现可进行*抽象解释(abstraction interpret)* ,其目标不一定是[程序](../Terminology.zh-CN.md#程序设计语言)。   任一 NPL 实现(和派生实现)的[符合性](../Terminology.zh-CN.md#非自指)由以下 NPL 符合性规则定义:文档指定的满足对实现的[要求](../Terminology.zh-CN.md#非自指)的[语言规则](../Terminology.zh-CN.md#规范)子集,包括本节、[基本文法](#基本文法)、[语义](#npl-公共语义)和其它派生实现定义的规则。   这类规则总是包含对应语言的[语义的 NPL 公共子集](#npl-公共语义),且蕴含[实现行为](#实现行为)的要求。   [语言规则](../Terminology.zh-CN.md#规范)约定的[未指定](../Terminology.zh-CN.md#程序设计语言)的程序或实现的属性及实现行为在符合性要求上等价。满足这类规则的前提下,实现选取特定的未指定的属性及对[未指定行为](../Terminology.zh-CN.md#程序设计语言)的特定实现的选择不影响实现的符合性。 **原理**   基于抽象机可直接定义最小的符合性要求,如 [C++ 的规则](https://eel.is/c++draft/intro.abstract#6)。   NPL 没有直接在此定义同等具体的规则,而以一般的要求取代。这允许派生实现对不同的具体规则[进行补充和调整](#变化的自由)。特别地,这允许[不同的方式](#形式语义方法)提供语义规则。   蕴含实现行为的要求的一个主要例子是关于[状态](#状态和行为)的规则。除了允许由实现定义和派生实现指定的不同,这实质上提供和上述具体规则等价的默认情形,而简化派生实现需要的对语言规则的补充和调整。 ### 实现的执行阶段   一个 NPL 的完整实现应保证行为能符合以下的*执行阶段(phase of execution)* : * *分析(analysis)* 阶段:处理代码,取得适当的 [IR](../Terminology.zh-CN.md#程序设计语言) 。 * *(目标)代码生成(target code generation)* :以 IR 作为输入,生成可被其它阶段执行的代码,即[目标代码(target code)](../Terminology.zh-CN.md#程序设计语言) 。 * **注释** 一般意义的代码生成可以有多个子阶段,包括多种内部 IR 的翻译,直至得到*最终目标代码(final target code)* 作为输出。 * 运行:运行生成的最终目标代码。 * **注释** 最终目标代码的形式视不同而定,可能有附加的封装格式。例如[编译器](../Terminology.zh-CN.md)的[目标代码(object code)](../Terminology.zh-CN.md#程序设计语言) 经*链接(linking)* 为可[执行](../Terminology.zh-CN.md#程序设计语言)的*映像(image)* ,被[加载](../Terminology.zh-CN.md#程序设计语言)后形式才能运行。   其中分析阶段是任意实现必要的,依次包含: * 词法分析(lexical analysis) :必要时转换字符编码;*转义(escape)* 并提取记号。 * 语法分析(syntactic analysis) :语法检查(检验语法[正确性](#正确性))并尝试匹配记号和语法规则中的[语法元素](#语法)。 * 语义分析(semantic analysis) :语义检查(检验语义正确性)并实现其它语义规则。   以上的具体阶段不要求和实际实现中的一一对应,但应保证顺序一致。   运行之前的阶段总称为[*翻译(translation)*](../Terminology.zh-CN.md#程序设计语言) ,包含各个*翻译阶段(phase of translation)* 。   对有[宿主语言](../Terminology.zh-CN.md#规范)支持的[嵌入实现](../Terminology.zh-CN.md#规范)或目标不是程序的情况,代码生成及之后的阶段不是必须的。   宿主语言实现可提供作为[客户语言](../Terminology.zh-CN.md#规范)的 NPL 的*本机(native)* 实现。   宿主语言实现提供 NPL 实现环境,同时对 NPL 环境的操作可影响 NPL 程序,这些情形都是[元编程](../Terminology.zh-CN.md#程序设计语言),NPL 在此同时是[对象语言](../Terminology.zh-CN.md#程序设计语言)。   嵌入实现的宿主语言可直接运行语义分析的结果(中间表示)。   在语义不变的前提下,允许实现一次或多次翻译部分代码产生部分中间结果并复用。   *运行时(runtime)* 程序实现运行阶段。   其它可能的阶段由派生实现定义,但应满足所有阶段具有确定的全序关系,且不改变上述指定的阶段的顺序。符合这些条件的附加阶段称为扩展阶段。 **注释**   字符编码是被翻译的源中的二进制表示相关的模式。 ### 并发实现   一个实现可能具有计算模型意义上的[并发](../Terminology.zh-CN.md#计算机科学)属性,即*并发实现(concurrent implementation)* 。   一个实现中顺序执行以上执行阶段的一组[状态](../Terminology.zh-CN.md#非自指)称为一个*执行线程(thread of execution)* ,简称*线程(thread)* 。   一个实现在整个执行过程中可以有一个或多个线程被执行。是否支持多线程执行(多线程翻译和/或多线程运行)由派生实现定义。   若实现支持多线程执行,则执行阶段的状态区分不同的并发执行线程,此时具体的状态构成由实现定义。 ### 阶段不变量约束   若某些状态在某个执行阶段 k 被唯一确定为不可变状态,且在之后的状态下是*不变量(invariant)* ,则此状态称为满足 k 阶段不变量约束的。 ## 正确性   *正确性(correctness)* 规则约束被执行的程序,包含语法正确性和语义正确性。   当正确性规则被发现违反时,实现进入异常执行状态。   [翻译时正确性规则](#翻译时正确性规则)以外的异常执行条件和状态由派生实现定义。 ### 翻译时正确性规则   翻译时的异常状态要求给出用于区分正常状态特定的[行为](../Terminology.zh-CN.md#程序设计语言)作为[诊断](../Terminology.zh-CN.md#程序设计语言),包括[诊断消息](../Terminology.zh-CN.md#程序设计语言)和其它派生实现定义的[实现行为](#实现行为)。   语法正确性规则是翻译时正确性规则。   部分形式上的正确性规则在翻译时确保。   允许翻译时确保的形式上正确的程序是*合式的(well-formed)* ;反之*不合式(ill-formed)* 。   合式的程序符合语法和语义的正确性的规则。   其中,实现被要求确保通过翻译的程序符合语法规则和翻译时确保的*可诊断(diagnosable)* 语义规则。   不合式的程序不保证被完整地翻译,应在运行前终止执行阶段。 ### 错误   *错误(error)* 是不满足预期的正确性或其它派生实现定义的不变性质时的特定诊断。   非正确性或不满足这些不变性的条件是*错误条件(error condition)* 。   满足错误条件时,实现可*引起(signal)* 错误。 **注释**   和 \[RnRS] 中的某些版本指定错误可以不诊断不同,引起错误蕴含诊断。 ## 实现行为   实现的[行为](../Terminology.zh-CN.md#程序设计语言)由具有存在非特定空间上限的存储的*抽象机(abstract machine)* 描述。这种描述对应的语言的语义是*抽象机语义(abstract machine semantics)* 。   若语言规则明确特定的行为可被忽略,则被忽略之后的实现行为与之前在语言规则中视为等价。翻译的实现可选取这些等价行为中的任一具体行为。   [派生实现](#略称)可通过[显式的未指定规则](#规范模型)定义附加的等价性。   不论程序是否满足[正确性规则](#正确性),实现对程序的执行都可能存在[未定义行为](../Terminology.zh-CN.md#程序设计语言),此时实现的行为不需要满足正确性规则指定的行为要求。   特定的语言规则引入未定义行为。程序的执行在适用这些规则指定的条件时,引起未定义行为。   特定的语言规则排除未定义行为的引入,以满足一定的[可用性](#可用性)。这不排除程序的执行可能因同时使用的其它语言规则引起的未定义行为。 **注释**   抽象机语义是一种[操作语义](#形式语义方法)。   抽象机语义也可非形式地定义语言的正式的(normative) 语义和行为要求,例如 [C++ 抽象机](https://eel.is/c++draft/intro.abstract) 。 ## 简单实现模型约定 ### 嵌入宿主语言实现   一个派生实现使用外部语言 L 简单实现模型 NPL-EMA ,若满足: * 以 L 为宿主语言的嵌入实现,不包含[扩展执行阶段](#实现的执行阶段)。 * 单一实现不保证提供[多线程执行](#并发实现)的支持,但对资源的使用进行适当的分组,以允许多个实现同时在宿主中多线程执行。   宿主语言提供的实现环境称为宿主实现环境,简称*宿主环境(host environment)* 。 **注释**   若支持多线程执行,需要附加的显式同步。   这种实现可能提供宿主多线程对应的实体,其中包含需要的被隔离的资源。   其它语言的实现也可能提供类似的设计,例如 [V8 的 `v8::Isolate`](https://v8.github.io/api/head/classv8_1_1Isolate.html) 。   另见[可移植](../Terminology.zh-CN.md#兼容性和可移植性)和[互操作](../Terminology.zh-CN.md#规范)意义上的[宿主环境](../Terminology.zh-CN.md#环境)。 # 基本文法   本章约定基本的 NPL [文法](../Terminology.zh-CN.md#程序设计语言)规则中,包括语法及对应的基础词法。对应的语义[在下文列出](#npl-公共语义)。   多态文法规则:[派生实现](#略称)可完全不提供本章明确定义的词法和语法构造的支持,仅当提供同构的替代文法且符合语义规则。   本章定义的[对象语言](../Terminology.zh-CN.md#程序设计语言)的正式文法以 [BNF(Backus–Naur Form ,Backus–Naur 形式)](https://zh.wikipedia.org/zh-cn/%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F)作为元语法形式地描述,且在与对象语言之间无歧义时不在终结符(terminal) 边界使用引号(即 `"` 和 `'` )。 **注释**   因为这种 BNF 使用符合 [`highlight.js` 支持的语法](https://github.com/highlightjs/highlight.js/blob/main/src/languages/bnf.js),在 Markdown 源代码中直接使用[支持的语言别名 `bnf` 标记](https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md)。 ## 基本文法概念 * 字符(character) :组成语言代码的最小实体。 * 基本翻译单元(basic transation unit) :作为[翻译](#实现的执行阶段)输入的任意连续字符的有限序列(可以是空序列)。 * 翻译单元(translation unit) :基本翻译单元的集合,之间满足由派生实现定义的规则。   [程序](../Terminology.zh-CN.md#程序设计语言)以翻译单元或具体操作指定的以翻译单元进行[翻译](../Terminology.zh-CN.md#程序设计语言)得到的其它变换形式表示。 ## 字符集和字符串 * 字符集(character set) :对一个实现而言不变的字符的有限集合。 * 基本字符集(basic character set) :实现环境必须支持的字符集。具体由派生实现定义。 * 字符串(character string) :字符集上的序列。   除非另行指定,关于字符集定义的其它概念同 \[ISO C++11] 对 character 和 character set 的有关定义。 **注释**   [字符编码](#实现的执行阶段)基于字符集定义。   一般地,一个翻译单元只具有同一个字符编码。 ## 词法规则   *词法规则(lexical rules)* 约定在字符基础上的最小一级的可组合为[语法](#语法)元素单位直接关联的文法规则。   约定[元语言](../Terminology.zh-CN.md#程序设计语言)语法 `` 表示词法元素 `x` ,`::=` 表示定义,`|` 表示析取。 ### 基本词法构造   文法: ```bnf ::= | <$punctuator> | <$identifier> ``` * 分隔符(delimiter) :代码中标记特定字符序列模式的字符序列。 * 词素(lexeme) :代码中以分隔符确定边界的字符序列。 * 记号(token) :词素的顶级分类。   属于记号的语法元素可以是以下的词法分类: * 字面量(literal) :一种记号,参见[以下描述](#字面量)。 * 标点(punctuator) :由派生实现定义的特定字符序列的集合,可具有一定语义功能。 * 标识符(identifier) :除字面量和标点以外的记号   代码中邻接的分隔符和非分隔符不构成一个词素。   不在记号内包含的空白符是分隔符,而不是词素。   标点是分隔符,也是词素。   超过一个字符的标点可能在匹配字符序列确定是否构成词素时具有词法歧义。此时,应指定消歧义规则确保存在唯一可接受的匹配方式,或引起词法[错误](#错误)终止翻译。   除非派生实现指定,字面量以外的记号不包含分隔符。   记号是可能附带附加[词法分析](#词法分析)信息的词素。词法分析后得到的记号可以用词素映射到词法分类的有序对表示,但 NPL 不要求在此阶段保持分类也不限定表示的构造。   可以保证 \[ISO C++11] 的 identifier 的定义,或在上述标识符中插入字符 `$` 构造得到的标识符属于 NPL 标识符。   派生实现可定义其它能构成标识符的词素。 **注释**   NPL 不指定超过一个字符的分隔符,因此默认没有词法歧义。派生实现可指定这些规则。   NPL 是*自由形式(free form)* 的语言,空白符原则上不构成字面量以外的词素和语义。 ### 转义序列和字符序列   文法: ```bnf ::= <$single-escape-char> | <$escape-prefix-char><$escape-content-seq> ::= <$literal-char> | ```   包含 `` 的 `` 包括: * `\'` * `\"` * `\\` * `\a` * `\b` * `\f` * `\n` * `\r` * `\t` * `\v`   `` 的含义同 \[ISO C++] 的对应转义序列。 **注释** 这是 \[ISO C++] 的 `` 词法分类中除了 `"\?"` 的情形,也是 \[R6RS] 在 `` 中支持的字面情形。 ### 字面量   文法: ```bnf ::= | ::= '' ::= "" ::= | ::= | <$derived-impldef-literal> ``` * 代码字面量(code literal) :以 ' 作为起始和结束字符的记号。 * 数据字面量(data literal) :以 " 作为起始和结束字符的记号。 * 字符串字面量(string literal) :代码字面量或数据字面量。 * 扩展字面量(extended literal) :由派生实现定义的非代码字面量或数据字面量的记号。 * 字面量(literal) :代码字面量、数据字面量、字符串字面量或扩展字面量。   派生实现定义的解释可排除代码字面量作为字符串字面量。 **原理**   传统的字面量一般是[自求值项](#范式),这包括一般的字符串字面量。   代码字面量可提供非自求值项的处理方式。 ### 分隔符   以下单字符标点是 NPL 图形分隔符: * `(` * `)` * `,` * `;`   以下单字符标点是 NPL 分隔符: * NPL 图形分隔符 * 空白符(字符串 " \n\r\t\v" 中的字符之一) **注释**   空白符同 \[ISO C++] `std::isspace` 在 C 区域下的定义,不含`空字符(null character)` 。 **原理**   NPL 图形分隔符可不和其它字符组合而作为单独的记号。因此,这不包含构成字面量的字符 `'` 和字符 `"` 。   NPL 分隔符用于一般分隔记号(而不是识别[字面量](#字面量))的外部描述,也没有显式地包含这些字符,但词法分析仍应把按字面量规则把这些字符作为必要时区分不同记号的边界。 ### 词法分析   [词法分析](#实现的执行阶段)输入[翻译单元](#基本文法概念),输出记号序列。   以下规则(按优先顺序)定义了词法分析转换输入为输出的步骤: * 反斜杠转义:连续两个反斜杠被替换为一个反斜杠。 * 引号转义:反斜杠之后紧接单引号或双引号时,反斜杠会被删除。 * 断行连接:反斜杠之后紧接换行符的双字符序列视为续行符,被删除使分隔的行组成逻辑行。 * 字面量:未被转义的单引号或双引号后进入字面量解析状态,无视以下规则,直接逐字节输出原始输入,直至遇到对应的另一个引号。 * 窄字符空白符替换:单字节空格、水平/垂直制表符、换行符被替换为单一空格;回车符会被忽略。 * 原始输出:其它字符序列逐字节输出。   不对空字符特殊处理。 **注释**   因为不一定是 [NPL 分隔符](#分隔符),转义字符不总是分隔标识符。 ## 语法   本节指定 NPL 作为对象语言的[语法](../Terminology.zh-CN.md#程序设计语言)。   约定[元语言](../Terminology.zh-CN.md#程序设计语言)语法 `` 表示语法元素 `x` ,`::=` 表示定义,`|` 表示析取。   程序被作为[语言实现](../Terminology.zh-CN.md#规范)组成部分的[语法分析](#实现的执行阶段)程序[规约](#基本语义概念),结果能确定其和一定的语法元素匹配。   规约时应进行语法规则的检查。 ### 基本语法构造   NPL 的基本语法单元是可递归构造的[表达式](#表达式),或派生实现指定的其它语法构造。   构成基本语法单元的规则参见[词法规则](#词法规则)。   [合式](#翻译时正确性规则)的[基本翻译单元](#基本文法概念)应是一个或多个基本语法单元。 ### 表达式   文法: ```bnf ::= | | ```   *表达式(expression)* 是受表达式语法约束的记号序列,可以是: * *[原子表达式(atom expression)](#原子表达式)* * *[复合表达式(composite expression)](#复合表达式)* * *[列表表达式(list expression)](#列表表达式)*   构成表达式的表达式是被构成的表达式的*子表达式(subexpression)* 。 #### 原子表达式   文法: ```bnf ::= ```   原子表达式不能被表示为其它表达式的语法构成形式的复合。 #### 复合表达式   文法: ```bnf ::= | ```   复合表达式是原子表达式和表达式的复合,即语法意义上的直接*并置连接(juxtaposition)* ,不在被复合的表达式之间存在其它记号。   同一个表达式可能被按原子表达式出现的位置以不同的方式规约为复合表达式。允许的规约复合表达式的方式由派生实现定义。 #### 列表表达式   文法: ```bnf ::= | ::= | | ::= ::= ( | ::= ) | ```   其中,`` 是 [NPL 分隔符](#分隔符)。   列表表达式是在其他表达式的序列(可能为空)左右附加一组 `` 和 `` 作为边界构成的表达式。   `` 和 `` 是不同的标点。   边界为 `(` 和 `)` 的表达式是基本列表表达式。其它可能的边界由派生实现定义,构成扩展列表表达式。 **注释**   对 `` 的定义,以[形式语义使用的元语言](#npl-公共语义) 中的元语法符号 `*` 扩展元语法,可以简记作 ` ::= * ` 。   [列表表达式](#列表表达式)的边界是 [NPL 图形分隔符](#分隔符)。 ### 名称   NPL 的*名称(name)* 是符合语法规则约束的若干[记号](#基本词法构造)的集合。   存在非空的名称集合可被作为[表达式](#表达式)。 **原理**   [名称](../Terminology.zh-CN.md#程序设计语言)的集合是[广义实体](../Terminology.zh-CN.md#程序设计语言)和[实体](../Terminology.zh-CN.md#程序设计语言)的差集。   语言规则对语言可表达的名称添加要求,以使语言的[源代码](../Terminology.zh-CN.md#程序设计语言)能够直接使用名称。   名称在源代码形式之外也可广泛存在,且能通过不唯一的方式构造。因此,语言规则允许不和源代码形式一一对应的名称。 **注释**   构成名称的集合的表现形式不唯一。   特定的名称可能为空集。   约束通常包含顺序,即其中的记号构成确定顺序的序列。   记号或记号集合经[编码](../Terminology.zh-CN.md#程序设计语言),一般可实现为可表达的字符串。 ### 语法形式   *语法形式(syntactic form)* 是词法上满足特定[形式](../Terminology.zh-CN.md#非自指)的语法构造。   除非派生实现另行指定,语法形式总是表达式。 ### 语句   以派生实现定义的标点结尾的表达式称为*语句(statement)* 。   语句语法的*分组(grouping)* 规则以及是否隐式地作为列表表达式[求值](#基本语义概念)由派生实现定义。 ### 简单文法约定   一个派生实现使用简单文法 NPL-GA ,若满足: * 翻译单元同[基本翻译单元](#基本文法概念)。 * 只支持左原子表达式构成[复合表达式](#复合表达式)。 * 只支持基本[列表表达式](#列表表达式)。 * 标点为单个[字符](#基本文法概念)。 * 若支持语句,总是 NPL-GA [表达式](#表达式)。 **原理**   NPL-GA 允许一些典型的*分析器(parser)* 简化设计作为实现。   在[表达式](#表达式)的形式文法仅作为语法规则,使用[词法分析](#词法分析)的结果提供作为*语法类别(syntactic category)* 的[词素](#基本词法构造)的[串](../Terminology.zh-CN.md#程序设计语言)作为输入的情况下,NPL-GA 支持 LL(1) 文法分析,即使用 NPL-GA 语法。   若延迟[复合表达式](#复合表达式)和[列表表达式](#列表表达式)中的选择到分析器外(之后可能由语义处理),检查语法的判定程序可进一步简化,仅判断记号 `(` 和 `)` 的匹配。   若词法分析处理直接对 `(` 和 `)` 和进行*记号化(tokenize)* 标记,则 NPL-GA 分析器不需要支持其它判定。这样的分析器实现的 NPL-GA 子集等效 LL(0) 文法。但由于 NPL-GA 不限定语法元素具体数量,等效 LL(0) 分析器当且仅当输入的串终止时接受输入,因此是*平凡的(trivial)* ,通常不具有实际意义,因为: * 形式上这里只有算法步骤的多少的差异,而几乎所有实现的语言都不把它作为[可观察行为](#状态和行为)。 * 即便需要统计串的长度,也应可以在之前(词法分析)计算,使用语法分析完成这个任务在此是低效的。   反之,在分析 NPL-GA 语法前扩展其它语法*预处理(preprocessing)* 规则可以支持更多的文法扩展。这样的文法扩展可接受扩展的非 NPL-GA 文法,但仍允许保持语法分析器的实现使用 NPL-GA 语法。 # NPL 公共语义   NPL 的[语义规则](../Terminology.zh-CN.md#程序设计语言)构成[*演绎系统(deductive system)* (en-US)](https://en.wikipedia.org/wiki/Formal_system#Deductive_system) ,通过对[翻译单元](#基本文法概念)中的[表达式](#表达式)的[求值](#基本语义概念)表达。   除非[派生实现](#略称)另行指定,仅使用表达式指定关于[对象语言](../Terminology.zh-CN.md#程序设计语言)中的计算的语义。   基本语义规则要求: * 所有不需要[诊断消息](../Terminology.zh-CN.md#程序设计语言)的规则由派生实现定义。 * 本章内的规则应不引入[未定义行为](../Terminology.zh-CN.md#程序设计语言)。   NPL 允许程序具有语义等价的[未指定行为](../Terminology.zh-CN.md#程序设计语言)。派生实现可能通过约定和限制其具体选项的选取以指定更具体的[实现行为](#实现行为)。   本章内的语言语义基于一种[EBNF(扩展 BNF )](https://zh.wikipedia.org/zh-cn/%E6%89%A9%E5%B1%95%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F) 的方言,其中: * 规则定义符号(defining symbol) 为 `::=` 。 * 除非元素的含义和用法被单独另行指定,作为文法类的元素使用 `<` 和 `>` 作为分隔符。 * **注释** 这和 BNF 相同,但和常见 EBNF 可能不同。 * 在和对象语言之间没有歧义的情形时: * 使用 `*` 作为修饰前一个元素的 [Kleene 星号](https://zh.wikipedia.org/zh-cn/%E5%85%8B%E8%8E%B1%E5%B0%BC%E6%98%9F%E5%8F%B7),表示之前修饰的项可出现 0 次或多次。 * 使用 `(` 和 `)` 组合之间的元素。 * 若对象语言直接出现元语言的元符号(metasymbol) 而出现歧义,需要用一对 `"` 或 `'` 标记。 * **注释** 此时和非原始版本的 BNF 以及常见 EBNF 可能相同。 **原理**   [ISO/IEC 14977:1996](https://www.cl.cam.ac.uk/~mgk25/iso-14977.pdf)正式定义了最常见的方言,但这一标准没有简化业已分歧的元语法方言,以至于[仅在 ISO 的不同编程语言标准中,就存在多种记法并用](https://www.grammarware.net/text/2012/bnf-was-here.pdf)。   BNF 作为元语法形式地描述适用描述形式语言的[串](../Terminology.zh-CN.md#程序设计语言)的[重写系统](#演绎规则),而非对描述语义更灵活的[项重写系统](#项重写系统)。   在语法之外,元素之间的空白符通常不是重要的。因此,可直接省略元语言分隔符 `<` 和 `>` 。这也是大多数 EBNF 的典型用法(即便描述的是语法)。描述语义的例子如 \[Shu10] ,其中使用特定的字体支持元语言中出现的元素。   然而,为便于在代码中表示而不依赖特设的字体,此处仍然使用 `<` 和 `>` ,除非元素的含义和用法被单独另行指定。 **注释**   因为这种 EBNF 使用不符合 [`highlight.js` 支持的语法](https://github.com/highlightjs/highlight.js/blob/main/src/languages/ebnf.js),在 Markdown 源代码中不使用[支持的语言别名 `ebnf` 标记](https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md),而记作自定义语言标记 `xbnf` 。   ISO EBNF 被 `highlight.js` 的 `ebnf` 语法定义支持。   \[Shu10] 也使用 `::=` 作为规则定义符号。 ## 基本语义概念 * 区域(region) :和特定位置代码关联的有限[实体](../Terminology.zh-CN.md#程序设计语言)集合。 * 范围(range) :一个连续区间。 * 此处“连续”的概念由派生实现定义,默认参照数学的形式定义。 * 声明(declaration) :引入单一[名称](#名称)的表达式。 * 声明区域(declarative region) :对某一个声明及其引入的名称,通过[声明区域规则](#声明区域约定)决定的范围。 * 有效名称(valid name) :可以唯一确定[指称](../Terminology.zh-CN.md#程序设计语言)的实体的名称。 * 有效命名实体(valid named entity) :有效名称指称的实体。 * 名称隐藏(name hiding) :若同一个名称在同一个位置属于超过一个声明区域,则应能通过[名称隐藏规则](#可见名称)确定唯一有效的声明以指定有效名称和对应的有效命名实体,此时有效名称隐藏其它声明区域声明的名称,有效命名实体隐藏可以使用被隐藏名称指称的实体。 * 作用域(scope) :声明区域的子集,满足其中指定的名称是有效名称。 * 生存期(lifetime) :逻辑上关于实体的[可用性](#可用性)的连续区间的抽象,是一个闭集。 * 属性(property) :实体表现的性质。 * 同一性(identity) :实体上的一种[等价关系](../Terminology.zh-CN.md#计算机科学),允许实体具有标识不相等特定的属性。 * **注释** 特定属性的例子如占据存储。 * 对象(object) :可确定同一性的实体。 * 值(value) :表达式关联的[不可变状态](../Terminology.zh-CN.md#非自指)。   * 作为实体,对象总是关联值作为它的内容,称为*对象的值(value of object)* 。 * 未指定值(unspecified value) :[未指定](../Terminology.zh-CN.md#程序设计语言)的值。 * 修改(modification) :使[状态](../Terminology.zh-CN.md#非自指)被[改变](../Terminology.zh-CN.md#非自指)的操作。 * 作用(effect) :语言支持的一定上下文内的表达式规约蕴含的[计算作用](../Terminology.zh-CN.md#程序设计语言)。 * 副作用(side effect) :对表达式的值以外的表示的改变的作用。 * 幂等性(idempotence) :重复后即不改变状态的性质。 * 项(term) :特定的演绎系统中处理的对象,是带有基本递归构造的元素,可对应语法中的表达式。 * **注释** 这样的演绎系统主要是[项重写系统](#项重写系统)。 * 子项(subterm) :具有递归形式构造的文法描述的参与构成项的项。 * 变量(variable) :通过声明显式引入或通过演绎系统规则隐式引入的以名称指称的实体。 * 绑定(binding) :引入变量的操作或结果,其中后者是变量的名称和引入的被变量表示的实体构成的有序对。 * 约束变量(bound variable) :子项中出现的名称被绑定的变量,即其指称可能依赖具体上下文的变量。 * 同名的约束变量的整体重命名替换不保证不改变指称进而可能影响语义。 * 自由变量(free variable) :子项中出现的非约束变量。 * 组合子(combinator) :不是变量也不含相对任何项的自由变量作为子项的项。 * 常量(constant) :满足某种不变量的约束以和不可变状态关联的实体。具体由派生实现定义。 * **注释** 不和变量对立:蕴含不可变状态的变量可能是常量。 * 转换(conversion) :根据基于特定等价性(假设)前提的两个项之间的自反的演绎。 * 规约(reduction) :两个项之间的、[实例](../Terminology.zh-CN.md#程序设计语言)是某个转换的子集的满足反自反的演绎。 * 抽象求值(abstract evaluation) :对表达式的不取得作用的规约。 * 具体求值(concrete evaluation) :对表达式的取得作用的规约。 * 求值(evaluation) :抽象求值或具体求值。 * **注释** 即对表达式的规约。 * 求值结果(evaluation result) :作用的子集,是求值得到的用于替换被求值的表达式作为它的值的实体,或其它由派生实现定义的实体。 * 不和其它结果混淆时,简称*结果(result)* 。 * 求值中取得求值结果中的表达式的值的过程称为*值计算(value computation)* 。 * 值计算包含确定用于替换的实体以及替换的过程,两者之间具有因果性。 * 值对象(value object) :表示值的对象。 * **注释** 值对象是可作为值使用的对象,例如作为求值结果的一部分。和值不同,值对象不一定是不可变状态。 * 控制状态(control state) :实现中决定求值的状态。 * 程序表现的控制状态通称*控制(control)* 。 * 特定控制状态的改变使不同的实体被求值,这对应控制*转移(transfer)* 。 * *调度(schedule)* 特定可能改变控制作用的实体可决定如何转移控制状态。 * **注释** 这可能实现[并发的](../Terminology.zh-CN.md#计算机科学)计算。 * 除非派生实现另行指定,控制状态是[区分多线程执行中不同线程的状态](#并发实现)。 * 控制作用(control effect) :引起控制状态改变的作用。 * 在 NPL 中,控制作用是在对象或派生实现定义的实体上引起改变的副作用。 * 相等关系(equality relationship) :定义在值的集合上的等价关系。 * 布尔值(boolean value):逻辑真或逻辑假。 * 谓词(predicate) :若具有结果,则结果是布尔值的实体。 * 数据结构(data structure) :[数据](../Terminology.zh-CN.md#程序设计语言)的构造性表示。 * 一等实体(first-class entity) :语言表达的允许支持足够特性的子集的实体,其中特性支持包括: * 可作为语言中的有效命名实体。 * 可作为语言中的值在[特定的对象语言构造](#λ-完备语义和对应语法)中使用。 * 可表达数据结构。 * 满足以上支持的值域中没有任意特设的限制。 * **注释** 使用的判定准则和 \[RnRK] Appendix B 的 first-class object 的约定实质上一致。 * 一等对象(first-class object) :可确定同一性的一等实体。 * 访问(access) :从实体上取得状态或修改实体。 **原理**   一些设计中,值对象是专用于[表示(不可变的)值的对象(en-US)](https://en.wikipedia.org/wiki/Value_object) 。本设计不使用这个定义,因为: * 值对象作为对象,蕴含[表示](#表示)的目的,在语言设计而非实现的上下文中不是值的等义词。 * 以实现角度考察值对象提供值的表示时,不关心它[是否可作为一等对象](#可变状态和普遍性)而要求[不可变](#实体的不可变性)可允许其上的[副作用](#基本语义概念)替换表示具有其它的值。 * 作为对象的值,它可能因为[互操作](../Terminology.zh-CN.md#规范)等目的在外部被直接作为其它语言实现中可作为(允许可变的)一等对象的实体。 **注释**   在实现执行的上下文,生存期概念兼容 [ISO/IEC 2382](https://www.iso.org/standard/63598.html) 的 lifetime 定义: > portion of the execution duration during which a language construct exists   定义绑定的有序对作为抽象表示,不需要被对象语言支持。对象语言可支持其它具体的有序对数据结构。   典型地,作用包括计算得到的值、引起的副作用以及其它可由区域和变化的状态二元组描述的实体。   一等对象同时是对象。   为满足可在表达式中通过求值被使用,一等实体总是能关联表达求值结果的值,称为实体的值。 ### 表示   [表示](../Terminology.zh-CN.md#程序设计语言)用于表现演绎实例、具体实现及其中一部分实体的状态。 **注释** 其中的一部分实体可以是某个值。   因为保证同一性,对象的值作为[可变状态](#状态和行为)的表示时,即对象存储的值。 **注释** 变量不一定是可变状态的表示。   [外部表示](../Terminology.zh-CN.md#程序设计语言)和[内部表示](../Terminology.zh-CN.md#程序设计语言)是相对的。不同[外部环境](#略称)可以有不同的外部表示,这些外部表示相对其它外部环境而言可以不是外部表示。   外部表示可能被*读取(read)* 处理为内部表示。内部表示可能被*写入(write)* 处理为外部表示。   读取和写入操作的副作用分别是*输入(input)* 和*输出(output)* 。   外部表示为元素序列时,读取和写入是非特定格式数据和元素序列之间的转换,若不含其它[作用](#基本语义概念),其操作是进行*反序列化(deserialize)* 和*序列化(serialize)* 。   内部表示为对象时,读取和写入包含对象和非特定格式数据之间的转换,其操作是进行*列集(marshall)* 和*散集(unmarshall)* 。   除非另行指定,不要求对象语言提供内部表示到外部表示的转换。   [文法](#基本文法)约定基准的表示作为[翻译](../Terminology.zh-CN.md#程序设计语言)的输入。这种表示是翻译所在外部环境的外部表示,即[源代码](../Terminology.zh-CN.md#程序设计语言);翻译结果是对象语言代码,简称*对象代码(object code)* ,可以是另外的外部表示。   [翻译单元](#基本文法概念)是这里被翻译的外部表示。   由[基本文法](#基本文法),空白符和参与的表示,不一一对应。为便于输出标准化,NPL 约定以下*规范(canonical)* 外部表示: * 对列表,输出的表示是以 `(` 和 `)` 作为边界,元素以单个 ` ` 为分隔符的序列,其中的元素在括号中被递归地嵌套表示。 * 对非列表的存在唯一的对应词法形式(如字面量)的值,输出这个值的词法形式。 * 其它值的外部表示[未指定](../Terminology.zh-CN.md#程序设计语言)。 * **注释** 值自身可能[未指定](../Terminology.zh-CN.md#程序设计语言)。[未指定值](#基本语义概念)通常蕴含未指定的外部表示,但不绝对。   谓词在模型中表示为数学关系、映射或单值函数;在对象语言中可有不同的表示,如[函数](#函数)。   其它外部表示和内部表示的外延由派生实现定义。 #### 同像性   外部表示和内部表示可能部分共享相同的规则。这些表示是*同像的(homoiconic)* 。语言支持同像的表示及其有关特性的性质是[*同像性(homoiconicity)*](https://zh.wikipedia.org/zh-cn/%E5%90%8C%E5%83%8F%E6%80%A7) 。   典型地,同像性允许复用代码和数据之间的表示。特别地,同像性允许对象语言中的代码作为数据(code as data) ,而不需要显式地处理为和代码不同的数据结构,显著简化[元编程](../Terminology.zh-CN.md#程序设计语言)的接口复杂性。   除非另行指定,NPL 和派生实现不限制语言中任何不同表示之间可能具有的同像性。 **原理**   [存储程序的体系结构](https://zh.wikipedia.org/zh-cn/%E5%86%AF%C2%B7%E8%AF%BA%E4%BC%8A%E6%9B%BC%E7%BB%93%E6%9E%84)自然而普遍地依赖代码和数据具有相同的表示,以便有效地把存储的数据直接作为代码提供给控制部件。   存储程序型计算机因此能自然地支持[自修改代码](https://zh.wikipedia.org/zh-cn/%E8%87%AA%E4%BF%AE%E6%94%B9%E4%BB%A3%E7%A0%81)。在更高层次的抽象中,高级语言可能改变这些性质的可用性,使其符合[最小接口原则](#最小接口原则),符合安全设计的需要。   但是,自修改程序在一些情形下仍然必要。为了[通用目的](#变化的自由),这些设计应符合[易预测性](#易预测性),而非完全禁止。   通过语言规则指定的同像性不具有体系结构设计时依赖的具体数据表示容易引起非预期操作的风险,有必要作为公开特性。   不需对代码和数据分别提供不同的特性有利于语言设计和使用的[简单性](#简单性)。   这同时使对象语言不需要提供特设的对自身的*反射(reflection)* 特性,因为潜在可被反射的对象伴随一般的元编程无处不在而可被随时引入或排除,直至另行指定的规则限制这种能力。   这也是使对象语言的设计符合[光滑性](#统一性)的主要机制。 **注释**   自修改程序在一般意义下对运行时生成代码的 JIT(just-in-time) [编译器](../Terminology.zh-CN.md) 是必要的。这有助于提升程序运行时性能。   另行指定的规则包含显式的涉及表示的转换规则,例如[语法分析等阶段](#实现的执行阶段)可能转换外部表示为不同的(不确定种类的)内部表示,这些表示不保证其中的组成在变换前后一一对应。但是,NPL 规则没有明确指定破坏可能具有对应的规则,因此不同内部表示之间的非同像性仅在派生实现中可能指定。 ### 演绎规则   演绎系统具有的演绎规则决定演绎推理(deductive reasoning) 的输出。   指定转换输入和输出之间的关系的演绎规则是转换规则。   两两可转换的对象的传递闭包构成[等价类](../Terminology.zh-CN.md#计算机科学),称为可转换等价类。除非另行指定,以下只讨论具有单一可转换等价类的转换规则演绎系统,即(抽象)*重写系统(rewriting system)* 。   对象之间的转换保持某种[等价关系](../Terminology.zh-CN.md#计算机科学)的等价*变换(transformation)* 。对象之间的规约是其中的子集,即以存在等价关系的一个对象替代另一个对象的有向转换。   若两个对象具有规约到相同结果的变换,这两个对象*可连接的(joinable)* 。   若任意两个对象等价蕴含对象可连接,则此重写系统具有 *Church–Rosser 属性(Church–Rosser property)* 。   若可从任意一个对象规约到的任意两个对象可连接,则重写系统具有*汇聚性(confluence)* 。   若可从任意一个对象的一步规约到的任意两个对象可连接,则重写系统具有*局部汇聚性(local confluence)* ,或称为*弱汇聚性(weak confluence)* 。   若可从一个对象规约到的任意两个对象可连接,则此对象具有汇聚性。   若可从一个对象的一步规约到的任意两个对象可连接,则此对象具有局部汇聚性,或称为弱汇聚性。   规约中可包括涉及[实现环境](../Terminology.zh-CN.md#规范)的交互。   若规约用于求值,汇聚性限定为:满足任意以此规则变换前和变换后的项被分别规约时,两者的[作用](#基本语义概念)相等。 ### 项重写系统   作为[重写系统](#演绎规则)的实例,一个[*项重写系统(term rewriting system)*](https://en.wikipedia.org/wiki/Rewriting#Term_rewriting_systems) 包含以下组成: * 语法元素(syntactic element) 的集合。 * 项及其子项是语法元素的非空的串。 * 辅助语义函数(auxiliary semantic function) 的集合。 * 可通过语义变量(semantic variable) 指称其中的元素。 * *重写规则(rewrite rule)* 的集合。 * 重写规则指定*重写(rewrite)* :接收项输入并产生作为重写的输入的项,和被重写的项之间满足某种[等价关系](../Terminology.zh-CN.md#非自指)即*重写关系(rewrite relation)* 。 * 重写规则集合以包含语法元素和语义变量的重写关系在[元语言](../Terminology.zh-CN.md#程序设计语言)中表达为*模式(scheme)* 。   语法上蕴含[自由变量](#基本语义概念)的项是*开项(open term)* 。*闭项(closed term)* 是开项在项上的补集。   为表达计算,限制特定的重写关系使之不满足自反性,得到*规约关系(reduction relation)* ,即指定[规约](#基本语义概念)。对应地,双向的重写规则限制为其子集的单向的*规约规则(reduction rule)* 。经限制的系统是*项规约系统(term reduction system)* 。   规约关系视为表达计算查询(computational query) 的项和答案(answer) 的项之间的映射。此时,项规约系统被作为一种[计算模型](#模型)。 * **注释** 为表达计算的答案的确定性,需要确保规约可能取得[范式](#范式)。   一般地,项规约系统关联的结构总称为演算(calculus) 。   对每个演算,存在和项对应的*上下文(context)* 。元语言中,一般的上下文以语义变量 `C` 表示,形式化为具有元变量(meta variable) □ 的以下构造: ```xbnf C ::= □ | ... ```   其中 `...` 是演算支持的项的语法中替换[子项](#基本语义概念)得到的对应构造。 **注释** 例如,对作为对象语言的 λ 演算,语义变量 `x` 表示约束变量,其上下文为:`C ::= □ | (CT) | (TC) | (λx.C) 。这里的记法基本同 \[Shu10] 。   一般的项记作语义变量 `T` ,则 `C[T]` 表示上下文 `C` 中作为元变量通过语法代换(syntactic replacement) 为项 `T` 的结果,它是一个项。   作为对象语言的[变量](#基本语义概念)的项可依赖不同的上下文指称不同的实体。   一个变量 `x` 被上下文 `C` *捕获(capture)* ,若对任意 `x` 是其[自由变量](#基本语义概念)的项 `T` ,`T` 中自由出现的 `x` 在 `C[T]` 中是[约束变量](#基本语义概念)。 **注释** `C` 中仍可因自由出现的 `x` 而使 `x` 是 `C[T]` 中的自由变量。 ### 状态和行为   状态不变蕴含语言规则中或可选地由实现定义的[等价关系](../Terminology.zh-CN.md#非自指)决定。   除非派生实现另行指定,约定: * [实现行为](#实现行为)总是可使用状态进行描述。 * 存在[副作用](#基本语义概念)为*可观察(observable)* 行为的必要条件。 * 在实现外部[访问](#基本语义概念)某个状态的操作(输入/输出操作)是副作用。   若存在状态等价性以外描述的行为描述,由派生实现指定。   可观察行为如有其它外延,由派生实现指定;否则存在副作用是存在可观察行为的充分条件。   实现应满足实现行为和语义蕴含的可观察行为等价。除派生实现指定的更特定的具体行为等价性外,其余的行为等价性[未指定](../Terminology.zh-CN.md#程序设计语言)。   实现可支持实体具有对外部不引起可观察行为差异的*隐藏状态(hidden state)* 。   隐藏状态和程序约定的一些状态作为*管理状态(administrative state)* ,以隐藏局部的状态变化对程序中其它状态的影响。   非管理状态是*数据状态(data state)* 。 **原理**   形式上,可观察的性质影响特定的项的*操作等价性(operational equivalence)* :替换操作等价的项得到的两个规约在可观察性质上是等价的,即两个规约的结果相等(对应行为不可分辨)。因此,可观察的性质可形式化为作为这些等价规约的结果的参数。   最简单的做法,如 \[Shu10] §8.3.2 把具有可观察性质的项处理为常量语法域(syntactic domain) ,不需要附加定义相等性或影响其它规约规则。   对语义蕴含的可观察行为等价的要求指定了允许实现进行*语义保持变换(semantic preserving transformation)* 不能修改可观察性质的内涵,进而明确了实现对程序的可优化的界限。   数据状态和管理状态的分类类似 \[RnRK] 中改变对象的性质上对状态的划分,但不仅仅应用在关于改变对象的判断上。   改变对象意义上和 \[RnRK] 对应的具体实例是[实体的不可变性](#实体的不可变性)。 **注释**   关于实体的状态,参见[实体的等价性](#实体的等价性)。   不严格要求实现行为和[抽象机语义](#实现行为)蕴含的所有推论一致。   NPL 派生实现不保证是纯函数式语言,其中的[计算](../Terminology.zh-CN.md#计算机科学)允许描述状态的[改变](#基本语义概念)。表达式的求值的[作用](#基本语义概念)和 \[ISO C] 以及 \[ISO C++] 类似。不同的是,本文档的定义明确指定[控制作用](#基本语义概念)的更一般外延:改变[控制状态](#基本语义概念),即便这些状态并非从属[一等实体](#基本语义概念)。特别地,最简单的条件分支也明确具有副作用。 ### 作用使用原则   派生实现可定义其它的作用。   在推理[实现行为](#实现行为)时,副作用应仅在必要时引入。   作用具有*种类(kind)* 。[值计算](#基本语义概念)是作用的种类。   [副作用](#基本语义概念)中,对象的改变是一种作用的种类。   是否存在副作用是互斥的,即一种作用不可能同时是副作用和不是副作用。其它作用的种类可能相交,即可能同属不同的作用。   派生实现可定义其它作用的种类。   求值可引起副作用的*起始(initiation)* 。副作用的存在(如改变状态)可继续保持到求值结束后,并可影响[可观察行为](#状态和行为)。   副作用的*完成(completed)* 即副作用的存在的终止(如改变状态完成)。   引起作用的求值*蕴含(imply)* 求值关联的作用,以及其中蕴含的副作用的起始决定的其它作用。派生实现可定义特定的求值使之蕴含的其它的作用。   作用之间可存在[等价关系](../Terminology.zh-CN.md#计算机科学)。等价的作用相互替换不影响可观察行为。 **原理**   允许派生实现定义不同的作用以维护[变化的自由](#变化的自由)。   不同[副作用](#基本语义概念)对[行为](../Terminology.zh-CN.md#程序设计语言)的影响可能依赖作用之间的顺序。   因此,副作用应仅在必要时引入,不能在推理行为时无中生有(out of thin air) ,除非证明引入的副作用不蕴含[被许可的等价的实现行为](#实现行为)以外的其它行为。通常需明确区分是否依赖副作用以避免非预期的行为。这有助于保持[易预测性](#易预测性)和[可组合性](#可组合性)。   NPL 及其派生实现中的作用可描述一般的[计算作用](../Terminology.zh-CN.md#程序设计语言),不限定作用的种类的外延。   明确副作用的起始是必要的,因为语言至少需要支持允许无法反馈外部状态完成改变的副作用,即 I/O 操作,此时副作用的存在应被允许保持到求值结束后,否则求值无法终止而被*阻塞(blocked)* 。   副作用的完成是和起始相对的概念,在讨论有关[顺序](#规约顺序)时可能实用。 **注释**   派生实现可定义的其它的作用可能是副作用。   副作用的起始在 \[ISO C++] 的关于求值(引起的作用)的规则中同样被明确。 ## 实体语义   实体是语言中主要表达的目标。   本节提供和实体相关的公共定义和语义规则,并归纳关于[一等实体](#基本语义概念)和[一等对象](#基本语义概念)的性质。   除非另行指定,语言中不引入[非一等实体](#实体语义)。仅在特定局部上下文中操作非一等实体。 **原理**   限制非一等实体出现的规则有助于[统一性](#统一性)。 **注释**   根据[一等实体和一等对象](#一等实体和一等对象),[规则 G1a](#统一性) 是限制非一等实体的规则的推论。   一等实体的*一等(first-class)* 性质体现在语言支持的操作限制足够小,使之实例的全集可以涵盖任意[求值上下文](#上下文相关求值)中。   一个一等性质的反例是 \[ISO C] 的数组类型的[值](#基本语义概念)无法作为函数的形式参数。推论:\[ISO C] 的数组对象不是一等对象。 ### 实体的等价性   *等价谓词(equivalence predicate)* 是判断[等价关系](../Terminology.zh-CN.md#计算机科学)的[谓词](#基本语义概念)。   等价谓词可定义一些[等价类](../Terminology.zh-CN.md#计算机科学)划分。   语言提供等价谓词判断两个项之间是否满足等价关系,满足判断等价关系的需要。   作用于[实体的值](#基本语义概念)的等价谓词(若存在)定义实体的*相等(equality)* 关系。 **注释** 这类似一般的值的集合上可能存在的相等关系。   决定相等关系的谓词是相等谓词,可判断实体和实体的值*相等(equal)* 。   除非另行指定,默认实体上的具体等价关系是[实体的同一性](#实体的同一性)。   对象语言不要求提供默认的具体等价关系,即任意两个实体不一定可以比较等价。   已知可比较等价的任意实体之间的等价关系也不具有唯一性。   一般地,设计等价谓词需注意避免一些现实的使用困难,如[关于相等性的困难](https://www.craigstuntz.com/posts/2020-03-09-equality-is-hard.html)。   为使等价关系在实体全集上[良定义](../Terminology.zh-CN.md#规范),等价谓词可能在特定情形弱化为同一性。   一般地,弱化应具有可用性理由,这可能和既有等价谓词和等价关系的蕴含的设计相关。 **原理**   等价谓词在避免依赖良序(well-ordering) 和良基(well-founded) 的理论中满足最小依赖原则,尽管其实现仍可能依赖序关系。   等价谓词的用途和上下文相关。   [同一性](#基本语义概念)在作为等价关系蕴含实体上的任何其它等价关系。被蕴含的等价关系可具有更多的限制条件。   实体的同一性是普遍的,但不是普适的,它仍不足以在所有上下文中都被关注。   同时,同一性无法保证在对象语言的所有实体上被实现;否则,会引起根本上显著的问题,限制[语言的可扩展性](#变化的自由),而和[通用目的语言的一般属性](#形而上学)冲突: * 这类普遍同一性的具体判断依赖[外延性](https://zh.wikipedia.org/zh-cn/%E5%A4%96%E5%BB%B6%E6%80%A7),实质上是语言要求的相等性。 * 这要求任意具体的实现通过已知的、有限的方式构造,直接破坏[封装性](#封装)。 * 特别地,在[函数](#函数)等实体上的定义体现[函数外延性(function existionality)](https://ncatlab.org/nlab/show/function+extensionality) ,非常依赖具体实体的构造。 * 即便不考虑实现细节,如 λ 演算这样较为简单(但足够有[表达能力](#完整性)))的形式系统,一般的外延性即是[不可判定的](https://zh.wikipedia.org/zh-cn/%E6%B1%BA%E5%AE%9A%E6%80%A7%E5%95%8F%E9%A1%8C)。 * **注释** 这种不可判定使某些具体编码具有不唯一的表示,如 [Church 数(en-US)](https://en.wikipedia.org/wiki/Church_encoding#Use) 。 * 尽管有的系统如[公理化集合论](https://zh.wikipedia.org/zh-cn/%E5%85%AC%E7%90%86%E5%8C%96%E9%9B%86%E5%90%88%E8%AE%BA)通过[公理语义](#形式语义方法)(具体地,[外延公理](https://zh.wikipedia.org/zh-cn/%E5%A4%96%E5%BB%B6%E5%85%AC%E7%90%86))可以确切定义外延性,这同样依赖破坏封装的具体实现。 * 即便非通用目的语言也可能类似地因为外延性上的不可判定等问题,[存在充分理由拒绝默认提供函数外延性的等价谓词](https://github.com/coq/coq/wiki/extensional_equality)。 * 进一步,为满足[简单性](#简单性)和[可用性](#可用性),这自然地要求任意具体的实现是对象语言的[源代码](../Terminology.zh-CN.md#程序设计语言)中可表达,而破坏语言的可扩展性。 * **注释** 例如,语言中不能引入隐藏具体实现细节的[互操作](../Terminology.zh-CN.md#规范)。 * **注释** 作为反例,λ 演算中的对象都被 λ 项编码。但这引起的限制在通用目的语言中无法接受。 * 例如,[原生的算术](https://zh.wikipedia.org/zh-cn/%E9%82%B1%E5%A5%87%E6%95%B0)在[算法复杂度意义上即是低效](https://en.wikipedia.org/wiki/Church_encoding#Use)的,而通过机器数等方式的实现在复杂度和常数上都更高效。但后者的实现依赖外部系统,不能表达为 λ 项。   决定普适的等价谓词中蕴含的统一的等价关系是不可能的,因此语言中应允许共存多个等价谓词。具体等价谓词的设计可由派生实现及语言的用户提供。   等价谓词设计中弱化等价性的一个例子是 \[R6RS] 的[记录(record) 对象的相等性](http://www.r6rs.org/r6rs-editors/2005-August/000840.html)。 **注释**   [实体的同一性](#实体的同一性)是实体上的[可用于定义状态不变](#状态和行为)的等价关系的例子。它蕴含了实体没有被替换为不同的实体的判断,满足保持这种判断的[不变量](#阶段不变量约束)。   关于实体的关联的[值](#基本语义概念)是相对同一性更弱的等价关系。因为[不可分的同一性](#不可分的同一性),同一实体蕴含其值相等。   一些情况部分值的集合不满足数学意义上的等价(如浮点数的 [NaN](#数值类型) ),但在此忽略这种可被单独扩展的情况。   以下不同准则的操作是相等关系的实例(参见 \[EGAL] ): * *抽象相等(abstract equality)* * *[引用](#一等引用)相等(reference equality)* * EGAL (\[EGAL]) #### 实体的同一性   [同一性](#基本语义概念)是实体上的等价关系的一个主要实例。   同一性决定的等价类之间的实体相同,即其整体和任意的[属性](#基本语义概念)在任意上下文中等价。   相同的实体在语言中不需要被区分,可直接替换而不影响程序的语义和行为。后者蕴含[可观察行为等价](#状态和行为)。   实体的同一性可体现在以下隐含的默认规则: * 不同上下文的实体默认相互独立而不保证能被视为相同(在任意上下文中等价)。 * 通过语言构造引入的超过一个实体,默认为不相同的实体。 * 除非另行指定,表示具有同一性的实体的语言构造和其它实体不被要求[共享](#共享引用)指称相同的具有同一性的实体。   语言在[一等实体](#基本语义概念)上提供的同一性的[具体判断依据](#一等实体和一等对象)和具体语言支持的特性相关。 **原理**   同一性决定任意两个实体可在语言中不依赖具体操作的行为被直接区分,即满足 Leibneiz 法则(Leibneiz's law) ,或称为[不可分的同一性](#不可分的同一性)。   基于这个性质,可在实体上定义和 \[So90] 相容的更强的(不依赖语言设计中不存在副作用的)*引用透明性(referential transparency)* 。   同一性的引入默认是名义的,即断言具有同一性的实体和其它实体上的行为相互独立,而不需要附加证明。这种假设避免了一般地证明任意实体具有同一性的困难。   若不依赖直接在实体上标记等价类等依赖名义同一性假设的方法,证明一个实体具有同一性而非已知的其它实体,需证明任意的其它允许在程序中构造的实体和这个实体上的任意作用的可观察行为无关。在不限定具体的[计算作用](../Terminology.zh-CN.md#程序设计语言)属于会影响可观察行为的计算作用的确切集合时,这是计算上不可能的。因此,支持这类证明会有效地限制语言在支持不同的计算作用种类上的[可扩展性](#变化的自由)。   反之,从不同的对象上取消同一性(而允许实现共享资源等目的)一般是容易的:只要证明不存在影响可观察行为的计算作用即可。这种证明可以由程序名义地表达,例如标记某个实体上只涉及纯计算而没有副作用。   另一方面,这也提示纯计算在各种计算作用中具有的特殊性不足以使其作为唯一的可扩展配置的起点。   最平凡的起点应是没有任何计算作用的空计算。这无法表达计算,而必须要求扩展才具有实用性。而[通用目的语言](#需求概述)需要支持一般的计算作用,这同时包含支持纯计算。   从一般的计算作用排除副作用而得到纯计算,只需要添加可被系统证明的假设,这种机制可以嵌入到系统的规约规则中;而以支持纯计算的系统扩展表达一般的计算,需要引入不足以被对象语言求值规则描述其语义的间接表示(即需要被规约以外的规则翻译),并暴露更多和表达一般计算的目的无关的实现细节。 **注释**   按不可分的同一性,实体的属性在形式逻辑中通过量化的谓词判断而实现。   和不可分的同一性相对,存在同一性的不可分性(the indiscernibility of identicals) 。两者可被二阶语言形式地描述。   对象语言可提供同一性的相关操作,如: * \[ISO C] 的非空对象指针的比较操作比较指向的相同类型对象的同一性。 * \[RnRS] 和 \[RnRK] 的 `eq?` 过程/应用子比较两个[操作数](#规范化中间表示)的同一性。 #### 实体的不可变性   [通过特定的等价关系可定义](#状态和行为)具体的[不可变状态](../Terminology.zh-CN.md#非自指)的集合。   这些集合可用于定义以这些状态为值的实体的*不可变性(immutability)* ,进而定义不保持可变性的[改变](../Terminology.zh-CN.md#非自指)操作和具体的其中可能影响[可观察行为](#状态和行为)的[修改](#基本语义概念)操作。   通过限定不同的修改操作,定义不同的*可修改性(modifiability)* 和对立的*不可修改性(nonmodifiability)* 。   通过明确不可修改性拒绝支持修改操作(例如通过通过实体的[类型检查](#类型检查)拒绝特定的修改操作),或通过不提供修改操作,语义规则保证实体不被修改操作改变状态。 **注释** 例如,关于 \[ISO C++] 的非类且非数组类型的[纯右值](#值类别)不可修改,尽管要求非纯右值的语义规则可被视为是一种类型检查。   (不依赖和影响[实体同一性](#实体的同一性)的)同一个实体上的修改操作是改变操作。只有具有[可变状态](../Terminology.zh-CN.md#非自指)的实体可能支持这些操作。   不论是否能区分同一性,实体可能关联不排除影响[可观察行为](#状态和行为)的可变状态。   一般地,一个实体不一定保证可区分是否具有不可变性以及具有何种不可变性(也蕴含一般不可区分可修改性),因为不可变性依赖实体的[表示](#表示)进行约定。   改变操作可能继续保持实体不变。   潜在引起实体的一些内部状态的变化的操作可不被视为影响不可变性而不被视为实体的(整体意义上的)改变操作。这种实体具有*内部可变性(interior mutability)* 。   可引起实体变化的状态按设计上是否[隐藏局部变化](#状态和行为)分为两类: * *可变管理状态(mutable administrative state)* * 可变管理状态的改变作为[管理状态](#状态和行为)的改变,不被视为对象(整体)改变的对象内部状态的改变。 * *可变数据状态(mutable data state)* * 可变数据状态的改变是对象的改变。   [隐藏状态](#状态和行为)在可变性的意义上视为可变管理状态。   推论: * 引起实体内的可变管理状态的改变的操作不一定是改变对象的操作。 * 引起实体内的隐藏的可变状态的改变的操作不一定是修改操作。 **原理**   基于等价关系而不是预设具体表示之间的相等定义可变性,避免抽象的目的(如[封装性](#封装))[依赖特定相等关系的实现细节](#避免抽象泄漏),支持[开放世界假定](#开放性)。   这种设计的一类典型反例是在预设排除副作用的[纯的](#求值性质)的设计为基础进行扩展定义改变操作,包括: * 默认使用不可变[数据结构](#基本语义概念),并在此基础上扩展出可变的数据结构(如 \[Rust] )。 * 默认支持保证排除副作用的[纯求值](#求值性质),仅在有限的上下文中通过特定构造模拟支持非纯求值(如 Haskell 等纯函数式语言)。   一般地,这类策略对[通用目的语言](#需求概述)是过度设计,因为这实质上要求所有不存在改变操作的实体操作都完全排除副作用,不支持指定不同类别或层次保留不同改变操作并划分不同等价类的可能性,而限制表达的能力或增加实现相同抽象的复杂性。   关联可变状态的实体通常是对象,因为支持区分[同一性](#实体的同一性)而能支持发生在不同实体上的作用引起独立的状态的改变而分别影响可观察行为,但这并非绝对。只要允许构造出按等价关系判断具有不相同状态,非对象实体仍可支持内部可变性等不能排除影响可观察行为的性质。这不通过需要区分同一性的状态改变。   不区分同一性允许实现任选其中的实例代替其它实例。因此,在抽象机语义上依赖这些实体的不同等价状态表现的所有[良定义行为](../Terminology.zh-CN.md#程序设计语言)都应被允许,即未指定行为。   内部可变性同 \[Rust] 的 `RefCell` 等使用的模式以及 \[ISO C++] 的 `mutable` ,允许对象具有可变管理状态,而不影响依赖可变或可修改的对象整体意义上的类型检查。   和 \[Rust] 不同而和 \[ISO C++] 更加类似,这里的内部可变性仅限关于对象不可变性,和对象是否被[别名](#对象别名)正交(一些实例分析参见[这里](https://stackoverflow.com/questions/63487359))。   但是,和 \[Rust] 及 \[ISO C++] 都不同,这里不要求不可变性通过[类型检查](#类型检查)强制。 **注释**   可变和不可变的状态的区分类似 \[RnRK] 。   其它语言也遵循类似的设计。作为非对象实体的可变性的一个例子,[C++ 引用是否要求存储未指定](https://eel.is/c++draft/dcl.ref#4),尽管占用存储这一状态并非是语言支持的可变状态。这一规则直接允许 C++ 实现不需要依赖 [as-if 规则](https://eel.is/c++draft/intro.abstract#footnote-6)即可选取占用和不占用存储的方式实现引用的实例(乃至在运行时改变选取策略),即便是否占用存储可能对应 C++ 程序的不同的可观察行为。   改变或修改实体后,实体可能不变,即仍然具有和之前等价的状态。例如: * 改变操作使用等价的状态替换先前的状态。 * 连续的改变操作使回复原始的状态,则这些改变操作的组合的作用不改变实体。   按[定义](#基本语义概念),蕴含引起表达式的值以外的改变的操作的作用是副作用。这里的改变是名义的,允许改变前后的状态等价。   支持不同等价的不可变性的一个用例是,有序的数据结构中的键需要保持的(通过序关系定义的)等价关系和键的可修改性是两种不同的等价关系。作为它的一个具体的反例,C++ 标准库要求关联容器的键具有 `const` 修饰,没有区分两种等价性,导致无法修改等价的键(除非具有 mutable 数据成员),而引起一些不必要的复杂。 ### 实体的副本   在已知的实体以外,实体,作为其*副本(copy)* ,满足: * 实体和实体的副本满足某种[等价](#实体的等价性),至少蕴含[实体的值](#基本语义概念)之间满足[不可变性](#实体的不可变性)。 * 若一个实体是对象,它的副本和它不[同一](#基本语义概念)。   除非另行指定,若实体的副本无法被创建,引起创建副本的操作[引起错误](#错误)。   若实体的副本可被创建,它可能通过: * *复制(copy)* 实体:创建副本后,保持原实体的值[不变](#实体的不可变性)。 * *转移(move)* 实体:创建副本后,原实体被转移而具有*有效但未指定(valid but unspecified)* 的状态;若可能取得实体的值,其值[未指定](../Terminology.zh-CN.md#程序设计语言)。 * **原理** 要求有效,隐含其[访问](#基本语义概念)时具有[良定义行为](../Terminology.zh-CN.md#程序设计语言)。 * **注释** 参见[未指定状态](#状态和行为)。 * **注释** 这类似 \[ISO C++] [\[lib.types.movedfrom\]](https://eel.is/c++draft/lib.types.movedfrom) 。 * *析构性转移(destructively move)* 实体:创建副本后,原实体的[生存期](#基本语义概念)结束,不再可[访问](#基本语义概念)。 * 其它派生实现指定的创建实体副本的不同方式。 ### 实体数据结构   实体的集合上可定义关联关系:集合的包含关系或其它实现定义的[严格偏序关系](../Terminology.zh-CN.md#计算机科学)。被关联的实体称为*子实体(subentity)* 。   子实体可以是作为[数据结构](#基本语义概念)的一部分。这种数据结构可以是一般的*图(graph)* 。   数据结构也可在对象语言中通过实体包含关系以外的途径定义。 **注释**   例如,限定包含关系构成的图中的[所有权关系](#所有权抽象)附加限制,详见[自引用数据结构和循环引用](#自引用数据结构和循环引用)。 ### 续延   *续延(continuation)* 是特定[上下文](#上下文)中描述未来的[计算](../Terminology.zh-CN.md#计算机科学)的实体。   续延描述的计算可能在尚未发生的后继的规约中实现,在此之前可能被[调度](#基本语义概念),其中可指定不同的计算内容。上下文可决定这些计算中可变的参数化部分。   计算可通过切换续延蕴含[控制状态](#基本语义概念)改变而具有[控制作用](#基本语义概念)。   当前规约的上下文中对应的续延是*当前续延(current continuation)* 。   按对控制的限制,续延可分为*无界续延(undelimited continuation)* 和*有界续延(delimited continuation)* 等不同形式。   续延蕴含*子续延(child continuation)* 作为最后的一系列子计算。   形式上,若续延表示为一个计算步骤的序列,子续延的表示是续延的表示的后缀。   推论:无界续延的子续延是无界续延;有界续延的子续延是有界续延。 **注释**   续延可由符合[项规约系统](#项重写系统)的[规约步骤](#规约规则和求值)的集合或未指定的其它形式的[表示](#基本语义概念)。   不同形式的续延的调用都能具有类似的控制作用,但[表达能力](#完整性)不尽相同。   有界续延可从无界续延或有界续延通过添加续延的*界限(delimiter)* (或称为*提示(prompt)* )派生。派生的结果是原续延的一部分,表达原续延对应的计算的*子计算(subcomputation)* ,又称*部分续延(partial continuation)* 。   在仅使用*局部变换(local transformation)* 即 Felleisen *宏表达(macro-expressible)* (\[Fl91]) 的意义上,\[Fi94] 指出: * 有界续延和可变状态(存储)可实现无界续延。 * 嵌入在语言中的任意表达[计算作用](../Terminology.zh-CN.md#程序设计语言)的*单子(monad)* 的构造可用有界续延实现。 ### 一等实体和一等对象   NPL 区分两类不同的一等实体:只关心关联(作为对象时)的值的,和同时关心作为对象的[其它属性](#实体的同一性)的一等对象。   其中,后者允许更多的操作,且允许作为前者使用,反之无法直接保证:一等对象总是一等实体,一等实体不保证可作为一等对象使用。   逻辑上,一等实体可以关联其它对象(作为一等对象时关联可以是存储)。关联的对象的(表达式相关的)值是一等实体关联的值,可对应一等对象存储值。关联的值或存储的值是一等实体或一等对象的[属性](#基本语义概念)。   除非派生实现指定,NPL 的一等实体都是一等对象。   结合[实体语义](#实体语义)相关规则,存在推论:除非另行指定,语言中不引入非一等对象。 **原理**   一等对象的值是一等对象的属性。   一些设计中,显式地不区分对象和值,因为这些设计中不支持普遍的一等对象。在这些设计中,一等实体被称为一等对象。因为不保证提供其它属性,一等对象的值和一等对象也不再被区分。这有助于[简单性](#简单性),但阻碍[实体语义的可扩展性](#变化的自由),直接无法从语言设计中允许在一等实体中区分一等对象。因此,NPL 不使用这种设计。 **注释**   派生实现可以定义非一等对象的其它一等实体。   除非派生实现指定,非一等对象也不是一等实体。   显式地不区分对象和值如 \[RnRS] 。这些设计中,[值对象](#基本语义概念)若被使用,仍被作为实现细节;且因为互操作和允许支持副作用,值对象并非全部一等对象的内部表示。 #### 一等对象的同一性   一等对象通过保证具有[同一性](#基本语义概念)强调不相同的对象总是存在至少一种[总是不相同的属性](#实体的同一性)。   一般地,语言规则选取其中一种属性作为名义(nominal) 同一性属性。   一等对象具有名义同一性,定义为可比较名义同一性属性[相等](#实体的等价性);名义同一性的相等即名义同一性属性相等。   名义同一性在名义上标识相同的对象,区分不相同的对象,即便后者可能仍然在行为上完全符合同一性的要求。   形式上,一等对象是名义同一性属性和它作为一等实体的关联的对象作为非对象(无视同一性)的其它属性集合(如[存储的值](#表示))的二元组。   为简化设计,NPL 约定以下默认规则: * 除非另行指定,名义同一性属性指定为对象在[抽象机语义](#实现行为)下的存储位置。 * 对象占据存储位置起始的若干存储。 * 存储位置的[表示](#表示)未指定;派生实现可指定具体的表示。 * 在语言规则中,一等对象满足[实体的同一性](#实体的同一性)的默认规则。 **原理**   由语言特性而非[用户程序](#程序实现)提供表达同一性的支持是必要的,这体现在通过在[通用目的语言](#需求概述)中省略同一性的表达再由实现或用户程序引入的做法一般是不可行的: * 由 [Rice 定理](https://zh.wikipedia.org/zh-cn/%E8%8E%B1%E6%96%AF%E5%AE%9A%E7%90%86),非平凡(non-trivial) 的程序语义性质无法被[可计算地](#完整性)实现,而确定程序中任意对象的同一性蕴含判定“和特定程序行为一致”这种非平凡语义性质,无法被通过证明程序行为的等价或其中的[实体在任意上下文上的等价](#实体的同一性)任意地引入,因此若无法确定用户程序不需要任意的同一性(这是一种平凡情形),指定“不需要引入同一性”总是只能在特定的程序上由语言设计者或用户具体地决定。 * 作为通用目的语言若需要描述能适应语言自身实现问题的特性,总是依赖具体语言的逻辑上的[直谓(predicative) 的](https://zh.wikipedia.org/zh-cn/%E9%9D%9E%E7%9B%B4%E8%B0%93%E6%80%A7)规则(如资源抽象),除非语言规则是空集(这是一种平凡情形),这不可能完全由用户程序提供。   语言的设计中显式区分一等实体和一等对象的支持而非只直接支持一等对象仍然是必要的,主要原因是: * 一等实体的具体表现形式通常是实现细节而要求不被依赖,为了支持前者不被显式表达,满足[关注点分离原则](#关注点分离原则)。 * 一等实体的普遍支持允许以[统一](#统一性)的方式抽象[可变状态](#状态和行为),且扩展使便于满足[变化的自由](#变化的自由)。 #### 可变状态和普遍性   NPL 对一等实体提供普遍的支持。   除非另行指定,NPL 不限制一等实体上可能具有的[作用](#基本语义概念),包括[副作用](#基本语义概念)。 **原理**   一等实体的普遍支持体现在: * 在一般的一等实体上引入可变状态,实质上提供了*一等副作用(first-class side effect)* ,而不把[可修改性](#实体的不可变性)限于特定的数据结构(如[求值环境](#求值环境))。 * 允许以一致的方式和实现的外部环境进行[互操作](../Terminology.zh-CN.md#规范),特别地,允许物理上提供状态抽象的设备实体的状态直接映射为一等对象。 **注释**   特别地,一等对象默认支持可变状态。   派生实现可附加规则改变本节中对一等对象的默认要求,提供不同的保证或性质,包括[非一等对象](#一等实体和一等对象)上的其它一等实体上的不同作用。 ### 同一性关联扩展性质   NPL 中,对象的同一性关联的属性包括明确开始和终止的[生存期](#基本语义概念)。   推论:对象是表示能明确生存期开始和终止的实体。   一等对象之间总是能准确地判断影响程序语义的[同一性](#实体的同一性):仅当能证明不改变[可观察行为](#状态和行为)时,两个一等对象的同一性可能未指定。 **原理**   通过一等对象关联同一性,允许语言提供依赖同一性差异的特性。 **注释**   同一性在这个意义上不是对象自身确定的性质(而是对象和解释对象表示的可能由外部提供的实现的共同确保的性质),不是应被隐藏的内部实现,因此 \[EGAL] 中有关自我诊断(autognosis) 的结论不适用;而代理(proxy) 仍然可通过语言提供适当的隐藏同一性的手段可靠地实现。 #### 一等状态   确保区分同一性的[状态](#状态和行为)是*一等状态(first-class state)* 。   一等对象能直接表示一等状态。   一等状态是否通过其它特性派生是未指定的。 **原理**   一等对象相对一等实体的附加规则限制集中体现在允许一等对象映射到的支持上。   注意并非所有一等对象都需要支持一等状态;否则几乎总是会[付出本不必要的代价](#避免不必要付出的代价)也难以避免违反[适用性](#适用性);因此有必要区分一等状态的对象和非一等状态的对象。   这种区分实质上更普遍地对具体的计算操作也存在意义,自然地引入了类似 \[ISO C++] 的[值类别](#值类别);最简单的设计如区分*左值(lvalue)* 和*右值(rvalue)* 分别关联是否需要支持一等状态的对象。   为支持一等状态,有必要支持判断两个对象的同一性,确保[修改](#基本语义概念)某个对象的操作不会关联到任意其它对象,以允许特定对象关联特定的一等状态。   为允许一等状态和[外部环境](#略称)的[互操作](../Terminology.zh-CN.md#规范),不能总是假定只有一类总是可被程序局部预知的[修改操作](#实体的不可变性)(典型地,定义为“设置[被引用对象](#一等引用)”操作,如 \[RnRK] §3.1 )影响状态,而应允许和特定对象关联的求值时的不透明的副作用。   若不考虑互操作,则一等对象用有限的不同[等价谓词](#实体的等价性)]即能提供区分同一性的操作;否则,等价谓词的设计即便保持[正交](#正交性),也需区分不同的一等对象对各种[副作用](#基本语义概念)的不同支持情况。   避免指定一等对象的可派生方式有助于[统一性](#统一性)。   基于 \[Fi94] ,结合可变状态能被表达为单子(如[这里](https://www.pauldownen.com/publications/delimited-control-effects.pdf))的事实,[有界续延](#续延)可实现状态。   相对地,基于 \[Fl91] ,[无界续延](#续延)和[异常](#异常)不能实现一般意义的可变状态,参见[这里](https://www.researchgate.net/profile/Hayo-Thielecke/publication/2487383_Contrasting_Exceptions_and_Continuations/links/53e12dba0cf2235f352738e5/Contrasting-Exceptions-and-Continuations.pdf)的推论 5.13 。   因为同一性可以在引入状态时被编码而在之后不需改变,使用有界续延等非一等的状态可支持实现状态的同一性。因此,在此不对是否基本要求作出限定。   但是,使用有界续延实现状态仅仅是实现细节,且通常具有一些非预期的实现性质: * 这在控制状态和支持一等状态的实现之间建立的不对等(地位不同,相互之间交换后不等效)的偶然耦合;这种耦合不存在简化实现等益处而具有必要性。 * **注释** 例如,一等状态可能直接使用对应的*寄存器(register)* 实现。实现控制状态则通常需要更复杂的实现。 * 尽管理论可行,没有必要只是用其中一种作为另一种的实现的基础实现。 * 在现有实现普遍提供状态的原生支持(存储器)的常见情况下,单独通过其它方式编码状态反而会付出本不必要的代价。 * 这实质要求实现同一性无界续延具有区分同一性的能力(相当于 \[ISO C++] 的左值),而引起不正交的内部设计。   为满足非常规的实现环境或更优先的原则(如[变化的自由](#变化的自由)和[正确性](#正确性)),派生实现仍可使用有界续延派生一等状态,同时提供访问更基本的不依赖可变状态的接口,以使上述影响不再是非预期的。   [用户程序](#程序实现)仍不被禁止使用这种方式自行提供类似的实现,以确保[不约定一等状态作为基本的内建特性时](#统一性),语言的设计不违反 [G1b](#统一性) 。 **注释**   实现在一般实体上支持的[隐藏状态](#状态和行为)不被程序可编程地指定,不是一等状态。   允许和特定对象关联的求值时的不透明的副作用的一个实例是 \[ISO C] 和 \[ISO C++] 的 `volatile` 类型对象。 #### 一等作用   语言可指定特定的求值自动创建对象。   基于此规则可在传递时附加不同的[作用](#基本语义概念),即实现可随一等对象传递的*一等作用(first-class effect)* 。 **原理**   典型地,[按值传递](#求值策略)时,被传递后的对象和[实际参数](#函数合并)表示的对象具有不同的同一性,即按值传递时创建[新的对象](#实体的副本)。   基于被创建的[副本](#实体的副本)的[不变性](#实体的不可变性),这里的一等作用可包括用于维护对象的[不变性](../Terminology.zh-CN.md#自指)的[作用](#基本语义概念),[包括可能的副作用](#可变状态和普遍性),作为[契约式编程(programming by contract)](https://zh.wikipedia.org/zh-cn/%E5%A5%91%E7%BA%A6%E5%BC%8F%E8%AE%BE%E8%AE%A1) 的基础实现方式。   这种不变性可包括对象的生存期。通过限制特定表达式求值的[作用域](#基本语义概念)内销毁对象以确保对象生存期有限,即基于作用域的对象管理(scope-based object management) 。   基于作用域的对象管理可直接对应有限资源的普遍性质,使一等对象作为资源的抽象,确保资源的创建和销毁的副作用符合资源操作的语义,同时避免隐式的泄漏。   配合[一等状态](#一等状态),对象语言中的一等对象允许直接表示超过程序运行时自身的生存期的状态。这允许不在程序运行时持久储存的数据能直接被一等对象进行操作,而不需要依赖外部系统的约定并减少冗余操作(例如,从外部持久的“文件”上打开“流”以及其上的持久化操作),更符合[简单性](#简单性)。 **注释**   这里的资源抽象的惯用法在 C++ 中称为 RAII(resource aquisition is initialization) 。 #### 所有权抽象   配合[一等作用](#一等作用),实体的*所有权(ownership)* 自然地适用对抽象为对象的资源进行约束。   使用对象代表资源,则*所有者(owner)* 约束被其所有的其它对象的创建和销毁的时机。被所有的对象的生存期是所有者的生存期的并集的子集,且: * 被所有的对象的生存期的起始不[先序](#规约顺序)所有者的生存期起始。 * 被所有的对象的生存期的终止不[后序](#规约顺序)所有者的生存期终止。   NPL 的设计避免要求对象语言隐含单一的*根(root)* 所有者作为其它资源的所有者。 **原理**   避免单一所有者适应抽象不同系统的需要,并满足[变化的自由](#变化的自由): * 当不需要这样的所有者时,保持设计的[简单性](#简单性),同时满足[避免不必要付出的代价](#避免不必要付出的代价)和[最小接口原则](#最小接口原则)。 * 当需要这样的所有者时,仍然允许实现或派生实现引入。   注意[规约](#基本语义概念)允许蕴含[非一等对象](#一等实体和一等对象)的所有者用于提供规约时不在对象语言中可抽象为一等对象访问的资源,这样的所有者不需要是全局的;若实现为在不同规约实例乃至全局共享的资源,也不应在对象语言被依赖。   只要程序没有明确要求所有者,单一的全局所有者违反[最小依赖原则](#最小依赖原则),且不支持不清楚所有者状态时对特定对象之间进行所有权的局部推理(local reasoning) : * 这种情形若不配合原始的明确目的(而间接明确资源的所有者)的设计说明,人类读者直接阅读实现理解和验证其正确性是困难的,即损失了可读性。 * 一种解决方式是读者自行模拟运行程序再从中推理出可简化的资源所有关系,这首先相当于要求读者模拟非确定性垃圾回收(GC, garbage collection) 的运行机制。这通常是困难的工作。 * 而机器通常更无法推理这些问题,因为设计和抽象的目的一般不是以机器可读的方式编码的。 * GC 可以回收资源,但无法准确统计哪些回收是必要的,也无法准确追溯原始实现并推理出应当在何种情况下静态地插入释放资源的操作,因为 GC 自始至终缺乏“允许任意延迟释放操作”以外的程序变换的[保持语义不变](#状态和行为)的证明所需的程序元信息(包括目的)。   为满足[变化的自由](#变化的自由),当需要表达局部所有权关系时,使用单一的全局所有者使用户无法直接在对象内嵌(embedding) 这种关系而需另行编码所有权信息,这存在以下问题: * 使整体设计直接违反[避免不必要付出的代价](#避免不必要付出的代价)。 * 要求局部所有权以和全局默认机制的不一致的方式表达,损失[统一性](#统一性)并放弃[局域性](#关注点分离原则)而在满足需求时造成接口[抽象泄漏](#避免抽象泄漏)。   此外,即便使用时不要求区分对象的局部所有权关系,全局的分配释放机制也比局部的机制有更大的实现复杂性和约束。为实现对内部有限的资源的有效管理,局部所有权在实现中仍是必要的。   在使用全局所有者如全局的垃圾回收的实现中,这种必要性被隐藏在全局所有者内部实现,语言的整体设计不会更[简单](#简单性)。   使用全局所有者的资源管理假定启发式(heuristic) 策略以节约现实中无法接受的非预期性开销。这仍无法保证总是对不同的场景同样有效,以至于默认存在以下问题: * 设计至少违反变化的自由和[简单性](#简单性)之一。 * 在不引入支持用户配置策略的扩充设计时,违反变化的自由总是无法避免的。 * 若引入其它设计支持用户配置策略,简单性违反难以避免,且实际基本上没有被避免。 * 即便能通过扩充设计避免违反简单性,也不能避免[不必要付出的代价](#避免不必要付出的代价)。 * 不论是否引入扩充设计,都会使资源管理的一般开销更难以估计,而使设计整体的可用性评估更困难,容易使用户决策和避免不必要付出的代价冲突。 ### 一等引用   NPL 的一等对象即对象自身,不要求区分引用和*被引用对象(referent)* 的普遍概念。   反之,通过使引用和其它一些非引用的对象同为[一等对象](#一等实体和一等对象),NPL 支持作为一等对象的*一等引用(first-class reference)* 。   一等引用支持一等对象作为被引用对象。除非另行指定,若实现允许[非一等对象](#一等实体和一等对象)作为被引用对象,可作为被引用对象的非一等对象由实现定义。   特定的操作可能预期非引用,或总是隐含通过引用[访问](#基本语义概念)被引用对象,这不改变引用被作为一等对象使用的普遍支持。   一等引用的[相等关系](#实体的等价性)定义为被引用对象的[名义同一性相等](#一等对象的同一性)。   一等对象的使用仍然可以通过要求引用访问以避免在任意上下文中需要不同的对象副本。但这并不应排除其它形式的一等对象操作。 **原理**   尽管满足 \[RnRK] Appendix B 的准则(criteria) ,一等对象和 \[RnRK] 及 Java 等语言要求的设计不同。   注意有引用的语言的语义中不能排除被引用对象,否则无法确定引用对象的值的表达式的[求值结果](#基本语义概念)(例如来自对象[存储的值](#表示))以表达[计算](../Terminology.zh-CN.md#计算机科学);相反,无视引用而直接对值操作仍然能实现一些足够有意义的程序。   因此,若存在引用,无法忽略非引用(即便非引用不能在对象语言被直接使用)。   另一方面,引用可以由不指定为引用的一般对象上添加语义规则区分,而作为一般的对象的特例。   要求语言操作的一等对象总是关联到引用的设计实质上使对象语言的一等对象都是引用。但这不表示引用是自然的一等实体,因为引用的作用仅是操作被引用对象,不要求引用自身能被作为一等对象。   一等引用的相等性定义允许在相等的引用上推理[引用透明性](#实体的同一性)。   考虑此设计决策时关注的有以下几节中的依据。其它依据参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 #### 共享引用   共享引用是共享资源的引用。共享的资源(通常是存储空间)自身具有同一性,以*位置(location)* 标记。共享不同位置(即作为不同一等对象的)的引用可能引用同一个被引用对象。 **原理**   合理的共享引用可以节约实现占用的资源,提供更好的性能。但共享引用的实现仍可能有附加的开销,因此并不能保证使用共享引用一定能提供更好的性能。通常这种情形至少包括一些典型的对资源独占一次使用(具有独占所有权(unique ownership) )的情况。   更重要地,并非任意引用的共享都不改变程序的语义和行为,不合理的使用可能造成非预期的作用。   任意地引入共享引用而使用户不便预测其作用破坏[适用性](#适用性): * 这包含直接破坏[易预测性](#易预测性),并在需要排除共享的场景中缺乏[可用性](#可用性)。 * 特别地,这和具有副作用的*非确定性(non-deterministic)* 编程冲突。 * 典型的[多线程并发执行](#并发实现)若需对象上的[副作用](#基本语义概念),需要保护和排除不必要的共享,确保独占[所有权](#所有权抽象)以避免*竞争条件(race condition)* 。 * 也有其它的一些类似的容易被忽略的[非确定性地破坏假设的场景](https://okmij.org/ftp/continuations/map-story.html)。 * 另见共享改变。   区分是否需要表达共享的情形一般不能由语言实现预知。和[使用全局所有者的问题](#所有权抽象)类似,使对象默认共享的设计若需避免违反[避免不必要付出的代价](#避免不必要付出的代价),在此相对不默认共享引用的设计违反[简单性](#简单性)。   默认共享引用可能是隐式的,即语言的实现不通过程序[代码](../Terminology.zh-CN.md#程序设计语言)中的显式标注的操作而引入共享的引用,且往往无法保证通过一等对象上的操作避免被引用的对象被其它一等对象引用——无法使用对象语言的操作排除共享引用(即便是新创建的对象也没有保证,尽管实现上不必要)。   在要求一等对象都是引用的设计中,一般地,只有不要求名义同一性的非对象的实体才能安全地共享引用,但在非对象实体上的类似引用的机制并没有保证通过一等引用提供为语言特性。   其它情形中,允许引用之间的隐式的共享使[不相同的对象](#一等对象的同一性)可能共享状态而破坏[同一性的行为保证](#一等对象的同一性):程序无法可靠地避免共享状态导致的对[可观察行为](#状态和行为)的影响,此时共享状态的改变非预期地影响其它对象,其行为不具有[一致性](#一致性)。   为了排除破坏同一性和适用性的问题,语言的设计需要限制引起问题的操作的可用性(例如,\[RnRK] 和 \[RnRS] 不提供使用一等引用的改变操作以保证变化能通过程序[源代码](../Terminology.zh-CN.md#程序设计语言)中有限的语法上下文被推理),但这样的策略限制设计的[通用性](#其它推论和比较)。   因为共享引用的影响的普遍性,不提供可避免隐式共享引用的设计的造成的缺陷也是普遍的。   由于显式的引用可以由用户控制在局部使用,更容易推理其影响,可避免类似的缺陷。   关于共享改变和程序无法可靠地避免共享状态导致的对可观察行为的影响,参见参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **注释**   一些语言的设计指定或隐含的规则在程序代码操作的一等对象上普遍地引入隐式共享的引用,如:   \[RnRK] 中的引用和被引用对象明确地分离,且 `$define!` 和 `set-car!` 等[改变操作](#实体的不可变性)要求设置对象引用的其它对象为特定的[操作数](#规范化中间表示)确定的被引用对象,无法排除被设置的引用被共享;这实质要求所有可能包含其它引用的可被改变的对象中的引用都需要能构成隐式的共享。   \[RnRS] 明确指出特定的空对象的唯一性(即便因为不保证具有位置,不一定保证以位置决定的[名义同一性](#一等对象的同一性)),蕴含这些对象上总是可构造或超过一个引用必须构造隐式的共享引用;其它变量引用(variable reference) 未指定排除隐式的共享。 ##### 对象别名   除非在语言规则中添加复杂的约束(如通过[类型](#类型)的机制)以证明特定上下文可避免共享引用,无法避免引用引入不必要的对象*别名(aliasing)* 。   若公开这样的性质作为接口约束,违反[最小接口原则](#最小接口原则)。   隐式的共享使涉及[修改](#基本语义概念)的操作的特性更难设计,参见共享改变。   关于共享改变,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   对象别名一旦引入,通常难以在所有被别名的对象生存期结束前消除。   证明对象不被别名是困难的,因为这逻辑上要求在局部知悉所有被别名的对象的存在性,而不具有[局域性](#关注点分离原则)。 ### 自引用数据结构和循环引用   特定的数据结构在逻辑上要求内部具有相互指涉的引用,即*自引用(self-referencing)* 。   自引用可实现为一等对象集合内的*循环引用(cyclic reference)* ,即允许对象属于有限次迭代访问被引用对象的操作的传递闭包(非空的*链(chain)* ,称为引用对象链)的构造。   NPL 的不保证支持这种方式实现自引用。 **原理**   NPL 的设计不保证支持通过循环引用实现自引用,以避免一些固有缺陷。即便派生语言允许提供扩展支持,但本节讨论的原理仍然适用。   避免自引用的构造使实体构成的数据结构由一般的[图](#实体数据结构)退化为(可共享节点的)树形数据结构,即 DAG(Directed Acyclic Graph ,有向无环图)。   这样的设计在实现上避免外部所有者(如[全局 GC](#所有权抽象) )。   避免一般的循环引用的普遍理由是:[*非直谓性(impredicativity)*](https://zh.wikipedia.org/zh-cn/%E9%9D%9E%E7%9B%B4%E8%B0%93%E6%80%A7) 并非是抽象上必要的普遍特性。一般的循环引用在抽象上即应通过特殊进行归纳,这并非[泄漏抽象](#避免抽象泄漏)。   反之,需求决定的抽象上不必要的情形下,假定循环引用的存在反而妨碍抽象的构造,可能避免某些有用的普遍性质(例如,保证程序可终止;另见[强规范化性质](#范式)),而违反[简单性](#简单性)、[统一性](#统一性)和[适用性](#适用性),并引起若干具体设计问题。   关于通过任意对象支持循环引用的问题,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 #### 一般引用数据结构的一般实现   通过一些替代原语,在不支持循环引用的情形仍可支持自引用数据结构。   语言可以提供在不支持一般的循环引用的对象构造中保存无所有权的一等实体引用其它实体,构造出不蕴含所有权的仅以特定对象构成的循环引用,而在外部引入对象作为所有这些构成引用的对象的所有者的机制。   在这个基础上,一般的自引用或循环引用需要的附加指涉仍然可通过添加不蕴含所有权语义的引用解决。这些引用是*弱引用(weak reference)* ,区分于具有所有权的引用是*强引用(strong reference)* 。   强引用总是可转换为弱引用使用。弱引用通过*解析(resolve)* 取得强引用。解析可能失败,以允许弱引用指涉已经不存在的对象,而避免影响对象[生存期](#基本语义概念)和所有权关系。   若支持这种受限形式的循环引用,具体特性由派生实现定义。 **原理**   没有理由表明通过任意对象支持循环引用是自引用数据结构的唯一实现方式,不论使用自引用数据结构的普遍程度。   自引用数据结构可通过在更高的抽象层次上编码,转换为由用户(而不是语言实现)指定明确的外部所有者的形式消除上述所有问题,同时对外部保证同等的功能[正确性](#正确性)。   使用受限的循环引用同时避免带有所有权的循环引用也是 C 和 C++ 等语言惯用的实现图(graph) 的数据结构的合理方式。 ### 实体类型   NPL 不要求预设具体的实体及对象类型的设计,因此不要求用户使用语言体现整体上的[可扩展性](#统一性)。   特别地,NPL 不要求表达式具有预设的不同[类型](#类型)。 **原理**   放弃对预设类型的要求允许由派生实现指定类型的外延而满足[变化的自由](#变化的自由)。   除不必涉及[引用](#一等引用)外,\[RnRK] 中定义的封装的(encapsulated) 类型的概念及类型[封装性](#封装)( \[RnRK] 原则 G4 )仍然适用,且一般仍然需要满足;差异是派生实现因为扩展不满足的情形也不影响此实现的[一致性](#规范模型)(尽管使用扩展的程序可能不可移植)。   尽管[值类别](#一等状态)可抽象为特殊的类型,表达式中的对象的类型和值类别的规则应分别讨论,因为两者正交:两者的[确定](#类型)和[检查](#类型检查)机制都相互独立。 ## 名称规则   [名称](#名称)和能标识特定含义、符合[名称词法约束](#词法规则)的表达式一一对应。   具体的外延由派生实现定义。   表示名称的表达式不同于名称,但在无歧义时,语言中可直接以名称代指表达式和对应的词法元素。   [求值算法](#求值规约)中对名称的处理应满足本节的要求。 **原理**   名称规则约定通过程序[源代码](../Terminology.zh-CN.md#程序设计语言)确定的静态的语法性质。   部分规则中的概念定义和约定仅为便于描述这些性质。和这些约定对应的结构不一定需要在求值算法的实现中出现。 ### 声明区域约定   对引入名称 `n` 的[声明](#基本语义概念) `D` ,对应的[声明区域](#基本语义概念)始于紧接 `n` 的位置,终于满足以下条件的[记号](#基本词法构造)`)`(若存在)或[翻译单元](#基本文法概念)末尾(不存在满足条件的记号 `)` ): * 记号 `)` 和与之匹配的记号 `(` 构成的表达式包含 `D` 。 * 此记号之前不存在满足上一个条件的其它的记号 `)` 。 ### 可见名称   名称隐藏规则:若[声明](#基本语义概念) `D` 是表达式 `E` 的子集,且不存在 `D` 的子集声明同一个名称,则 `D` 声明了[有效名称](#基本语义概念),隐藏了 `E` 中其它同名的名称。   在[声明区域](#基本语义概念)中,没有被隐藏的名称是*可见(visible)* 的。有效名称实质蕴含可见名称。 ### 名称解析   *名称解析(name resoultion)* 是通过名称确定名称指定的[实体](../Terminology.zh-CN.md#程序设计语言)的操作。   不保证名称解析总是成功。   除非另行指定,成功的名称解析没有[副作用](#基本语义概念)。   除非另行指定,直接作为求值算法步骤的不成功的名称解析[引起错误](#错误)。   一般地,名称解析包括*名称验证(name verification)* 和*名称查找(name lookup)* 两个阶段。   名称验证确定名称是[可见名称](#可见名称),同时可能排除部分无效名称。   名称查找进一步确定名称唯一指称的实体的(蕴含确定名称有效),仅在名称验证成功后进行。   不同名称经过名称查找的结果可能等效。等效的有效名称视为[同一的](#基本语义概念),规则由派生实现定义。   名称解析从保存名称的目标中查找名称。若查找失败,解析可继续从替代的其它目标中进行。这种机制称为*重定向(redirection)* 。重定向后的解析可继续包含名称验证和名称查找的步骤。   以上约定以外的具体规则以及失败的行为由派生实现定义。 ### 命名空间   *命名空间(namespace)* 是[实体](../Terminology.zh-CN.md#程序设计语言)。命名空间可以由名称指称。   是否实现命名空间为程序中可由用户指定可变的实体及[求值环境](#求值环境),由派生实现定义。 #### 命名空间指称   总是没有名称*指称(denotation)* 的命名空间是*匿名命名空间(anonymous namespace)* 。   没有有效名称指称的命名空间是*未命名命名空间(unnamed namespace)* 。   NPL 定义一个抽象的匿名命名空间,称为*根命名空间(root namespace)* 。未命名命名空间的支持由派生实现定义。   NPL 约定一个在实现中的有效名称总是指称一个命名空间。有效名称指称的命名空间的[同一性](#基本语义概念)和[有效名称的同一性](#名称解析)对应。 **注释**   匿名命名空间和未命名命名空间不同。前者可能是一个系统的默认约定,一般整体唯一存在(如*全局(global)* 命名空间);后者只是对某些接口隐藏,可以有多个。 #### 命名空间成员   除了用于指称的名称外,一个命名空间可以和若干其它名称关联。   通过派生实现定义的对命名空间的操作可以取得的名称是这个命名空间的*成员(member)* 。   若无歧义,命名空间的成员指称的实体也称为这个命名空间的成员。   命名空间直接包含成员,称为直接成员。   除了根命名空间和其它派生实现定义外,命名空间可以作为另一个命名空间的成员,此时命名空间内的成员(若存在)是包含其的命名空间的间接成员。   命名空间对成员的直接包含和间接包含总称为包含,是反自反的、反对称的、传递的二元关系。 #### 简单名称和限定名称   命名空间的直接[成员](#命名空间成员)的标识符在这个命名空间中是有效名称,称为*简单名称(simple name)* 。   命名空间及其成员按包含关系依次枚举标识符组成的序列是一个名称,称为在这个命名空间中的*限定名称(qualified name)* 。   [根命名空间](#命名空间指称)的限定名称称为*全限定名称(fully qualified name)* 。   限定名称的语法由派生实现定义。 **注释**   限定名称的语法的一个实例是标识符之间作为逻辑上的[分隔符](#分隔符)的[记号](#基本词法构造)。 ## 规约规则和求值   对象语言的[操作语义](#形式语义方法)可通过作为计算模型的[项规约系统的规约规则](#项重写系统)中由规约规则描述的规约*步骤(step)* 指定。   除非派生实现另行指定,规约蕴含 NPL 程序的执行,可完全表示程序执行的语义。   推论:NPL 规约规则形式地蕴含 NPL 语义规则。   为表达明确的目的,[语言规则](../Terminology.zh-CN.md#规范)也可约定[其它更抽象形式的求值规则](#可选求值规则),以蕴含这些规约规则,而不是直接描述规约规则的形式语义。   描述 NPL 对象语言的操作语义也可被视为[特定的对象语言](#实现行为),其规约可以视为求值。但除非另行指定,以下表达式仅指对象语言的表达式,其求值仅指关于对象语言中表达式的求值,而非一般的规约。   规约规则可要求被规约的项符合一定的结构(如具有特定类型的值)作为前提,否则规约出错,程序执行中止。   根据规约规则描述的行为是否对应对象语言中的求值,规约分为两类:表达式的[求值规约](#求值规约)和[管理规约](#管理规约)。 ### 求值规约   一个规约可以描述表达式的求值。直接表达一个表达式求值的规约是一个求值规约。   以[项重写系统](#基本语义概念)描述,求值规约的输入是作为表达式的[表示](#表示)的[项](#基本语义概念),称为*待求值项(evaluating term)* 。   待求值项经求值规约取得[求值结果](#基本语义概念)。   除非另行指定,求值结果是通过[值计算](#基本语义概念)取得的值。 **原理** 求值结果也可能是[异常](#异常)退出的等其它作用对应的实体。这些求值结果可能需要派生实现定义的不同规则的处理。   以下项称为*被规约项(reduced term)* : * 待求值项。 * **注释** 因为可附加等价[空求值](#求值性质)的恒等规约,不需要区分项是否已被规约。即使表达式从未被求值,其表示也可视为待求值项。 * 规约步骤的[中间表示](#实现的执行阶段)中完全依赖求值规约的输入的子集的项。 * 表示[求值结果](#基本语义概念)的项。   求值规约规则构成对象语言的*求值算法(evaluaton algorithm)* 。   求值算法的输入是被求值的表达式和支持[上下文相关求值](#上下文相关求值)中的上下文。   求值的基本操作以满足特定规则的*替换(substituion)* 规则或其组合表示。   除非另行指定,以下讨论的排除求值副作用的重写系统具有[汇聚性](#演绎规则)。   这保证求值满足值替换规则:表达式的[值计算](#基本语义概念)通过已知的[子表达式](#表达式)的[值](#基本语义概念)替换决定。   除非派生实现另行指定,子表达式的值仅由求值得到。 **注释** 此时[递归蕴含规则](#递归蕴含规则)中的求值依赖规则是这个规则的推论。 ### 管理规约   求值规约以外的规约称为*管理(administrative)* 规约。   管理规约可以是一个不完整的求值规约,或者和求值规约的步骤没有交集。   管理规约可使用对象语言不可见和不可直接操作的非[一等状态](#一等状态)的[管理状态](#状态和行为)。   表示非[一等对象](#基本语义概念)的项的规约总是管理规约。   [抽象求值](#基本语义概念)中不在对象语言[求值结果](#基本语义概念)中可表达的中间规约是管理规约实现。 **注释**   管理规约描述语言的表达式以外的[操作语义](#形式语义方法)。   实现也可使用的管理规约描述特定于实现的(而在对象语言中未指定的)语义性质。 ### 规约顺序   *先序(sequenced before)* 关系是两个规约之间存在的一种[严格偏序关系](../Terminology.zh-CN.md#计算机科学),对实现中规约之间的*顺序(order)* 提供约束。   *后序(sequenced after)* 是先序的逆关系。   *非决定性有序(indeterminately sequenced)* 是先序或后序的并集。   *无序(unsequenced)* 是非决定性有序在求值二元关系全集上的补集。   规约规则的顺序直接适用于求值,其顺序为*求值顺序(evaluation order)* 。   规约规则的顺序也适用在能以其形式描述相对顺序的*事件(event)* 上。[程序](../Terminology.zh-CN.md#程序设计语言)中蕴含的这些事件称为*规约事件(reduction event)* ,包括: * 求值。 * 对象的[生存期](#基本语义概念)的起始或终止。 * [副作用](#基本语义概念)的[起始](#作用使用原则)和[完成](#作用使用原则)。 * 不涉及副作用的完成[在求值结束后存在](#作用使用原则)时,某个[计算作用](../Terminology.zh-CN.md#程序设计语言)的存在。 * 派生实现定义的其它事件。   一些事件的顺序是通过推理具有*因果性(causality)* 的*依赖(dependency)* 关系决定的,包括: * 规约中[值计算](#基本语义概念)依赖规约的输入,即被求值的表达式和其它可能影响规约的状态。 * 被副作用的起始决定的其它作用依赖这个副作用。 * 从一个实体上确定作为值的属性的*读(read)* 依赖这个属性。 * 在一个实体上可以作为值保留的属性的*写(write)* 被这个属性依赖。 * 由派生实现定义的其它情形。 **注释** 外部[表示](#表示)作为实体的读取和写入是这里的属性的特例。   为了确定相关的值,依赖关系可直接替换为后序关系。   由二元关系的一般性质(特别地,偏序关系的传递性),可推导其它一些事件之间的确定顺序,如同一个实体属性上的读依赖(已知的)决定了这个属性的先前的写。   作为先序和后序的扩展,规约事件可符合*在先发生(happens before)* 和*在后发生(happens after)* 的严格偏序关系,满足: * 对同一个[执行线程](#并发实现)中的事件,在先发生和在后发生分别同先序和后序。 * 组合在先发生或在后发生的关系的不存在*环(cycle)* 。 * 派生实现定义的其它要求。   NPL 约定以下非决定性规约规则:除因果性和二元关系的一般性质的推论外,任意项之间的规约之间无序。   应用在求值顺序上,有以下推论(非决定性求值规则):除因果性和二元关系的一般性质的推论外,任意表达式的求值之间无序。 **原理**   在先发生和在后发生可描述系统中的[并发的](../Terminology.zh-CN.md#计算机科学)事件。[原始定义](https://www.microsoft.com/en-us/research/uploads/prod/2016/12/Time-Clocks-and-the-Ordering-of-Events-in-a-Distributed-System.pdf)包括对*时钟(clock)* 的抽象,但此处不要求指定。   \[ISO C++] 和 \[Rust] 等使用类似的方式描述并发的求值的支持。这些设计中,不同执行线程中具有特定的操作定义具体的顺序关系。其中具体规则的设计可能不同而不保证完全一一对应。   因[可扩展](#变化的自由)和[简单性](#简单性) NPL 不在此明确指定此类具体操作,而由派生实现定义。   及非决定性规约规则允许在语言中表达[并发实现](#并发实现)。 **注释**   读和写作为影响可观察行为的事件结果,具有因果性。此外,也可以抽象为计算作用并由程序操作;这里不做要求。 ### 求值性质   两个具体求值等价,当且仅当两者的作用相等。   两个求值等价,当且仅当作为具体求值时等价,或其中每个求值的变换实质蕴含另一个。   没有[副作用](#基本语义概念)的求值是*纯的(pure)* 。 **注释** 推论:纯求值仅有[值计算](#基本语义概念)或抽象求值。   值为被求值的表达式自身的具体求值或不包含变换为存在不等价求值的表达式的抽象求值为*恒等(identity)* 求值。   恒等的纯求值是*空求值(empty evaluation)* 。   作用是空集的表达式求值是*空作用求值(null effect evaluation)* 。 **注释** 推论:空作用求值是空求值。   [语法形式](#语法形式)固定且求值总是空求值的表达式是*空表达式(empty expression)* ,这仅由派生实现可选提供。 ### 范式   *规范化形式(normalized form)* ,或简称*范式(normal form)* ,是由派生实现定义的[表示](#表示),被一组[规约](#基本语义概念)规则确定,满足: * 通过有限的规约步骤后得到。 * 按规约规则,规范形式上不存在不[和空求值等价](#求值性质)的进一步规约。   在具有 [Church–Rosser 属性的重写系统](#演绎规则)中,一个对象若具有范式则唯一。   表达式在得到规范形式后规约终止,且蕴含求值终止。   得到范式的规约步骤称为*规范化(normalization)* 。   若表达式规约总是能得到规范形式(求值总是能在有限规约步骤后终止),则具有*强规范化(strong normalization)* 性质。   实现应避免引起对象语言的语义表达以外的无法保证强规范化性质的操作(如直接无条件的递归规约调用)。   除非派生实现另行指定,不保证强规范化性质。   保证得到范式的规约是规范化规约。   [具体求值](#基本语义概念)得到的范式若可作为表达式,其[求值结果](#基本语义概念)是和被求值的项等价的表达式的[值](#基本语义概念),即仅允许[恒等求值](#求值性质)而仍是范式;这样的项称为*自求值项(self-evaluating term)* 。   作为表达式的自求值项是*自求值表达式(self-evaluating expression)* 。   重复求值直至取得自求值项的求值结果是*最终求值结果(final evaluation result)* 。 **注释**   推论:最终求值结果上可能的求值是[纯求值](#求值性质)。因此,取得最终求值结果后,即排除具有[副作用](#基本语义概念)的继续求值。 ### 规范化中间表示   第一个[子表达式](#表达式)(头表达式)是[范式](#范式)的表达式是 HNF(Head Normal Form ,头范式)。   头表达式是可直接求值为范式的表达式是 WHNF(Weak HNF,弱头范式)。 **注释** 约定求值到 WHNF 提供保证[强规范化性质](#范式)的一般手段,可用于[非严格求值](#严格性)。   WHNF 的头表达式是*操作符(operator)* ,存在对应 HNF 的头表达式的[最终求值结果](#范式)。 **注释** 详见[合并子](#合并子)。   WHNF 中除了操作符以外的子表达式是*操作数(operand)* 。   操作数以具有限定顺序或不限定顺序的数据结构表示。   按操作数的数据结构对应有*操作数列表(operand list)* 和*操作数树(operand tree)* 。其中操作数树是有限的树形数据结构的 [DAG](#自引用数据结构和循环引用) ,其具体构造和表示由派生实现定义。 **注释** 操作数树和 \[RnRK] 类似。语言可能进一步约定有序的数据结构表示操作数的组成部分之间在求值上不等价。   这种能以操作符和操作数的组合表达的计算形式是*操作(operation)* 。   操作的*结果(result)* 是表达规约步骤得到的范式;操作的[作用](#基本语义概念)是取得对应结果的规约步骤的作用。 **注释** [函数合并](#函数合并)的[求值结果](#基本语义概念)中可蕴含操作的结果,也可[具有其它作用](#程序的控制执行条件)。若操作的结果存在,则同时是这个合并子的[调用](#函数调用)的结果,即[返回值](#函数调用)。   若操作的结果不依赖[管理规约](#管理规约),操作的结果和作用即这种可求值为 WHNF 表达式的[求值结果](#基本语义概念)和作用。 **注释** 另见[函数值](#函数合并)。   关于 DAG ,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 ### 组合求值   表达式和[子表达式](#表达式)之间的求值需满足一定约束。 #### 递归蕴含规则   表达式和子表达式之间的求值满足以下递归蕴含规则: * 求值依赖规则:除非另行指定,表达式被求值实质蕴含子表达式被求值。 * 顺序依赖规则:求值子表达式的[值计算](#基本语义概念)[先序](#规约顺序)所在的表达式的值计算。 * 平凡求值规则:指定一个表达式是[纯求值](#求值性质)或[空求值](#求值性质)对应实质蕴含其子表达式的求值被指定为纯求值或空求值。 **注释**   一般地,一些[求值策略](#求值策略)可以不遵循求值依赖规则。   顺序依赖规则是[因果性](#规约顺序)的具体表现之一。对不被求值的表达式,此规则不生效。构造不同的表达式进行计算可实现和直接违反此规则等效的作用,但因为是不同的表达式,实际上不违反此规则。   附加的顺序依赖规则可由特定的实体构成的表达式的求值隐含指定。相同的表达式可能在不同上下文中使用不同的规则。 #### 严格性   若表达式的任意子表达式的求值总是[非空求值](#求值性质)且[先序](#规约顺序)表达式求值,则这个表达式的求值是*严格的(strict)* ;反之,求值是*非严格的(non-strict)* 。   推论:严格求值满足[顺序依赖规则](#递归蕴含规则)。   非严格求值在规约时可保留未引起作用(通常即未被求值)的部分子表达式,允许实现根据先序的求值作用确定的选择性求值,即包括未指定是否作为[空求值](#求值性质)的子表达式求值,如分支判断或短路求值。 **注释** 例如:ISO C++ 的条件表达式存在可能未被求值的[操作数](#规范化中间表示),属于非严格求值;`++` 表达式不作为*完全表达式(full expression)* 时,副作用可超出此表达式的求值(不满足顺序依赖规则),也是非严格求值。   表达式经过*严格性分析(strictness analysis)* 确定是否严格求值,通过*严格性分析器(strictness analyzer)* 在[语义分析](#实现的执行阶段)时实现。   *中间值(thunk)* 是保留不直接实现[具体求值](#基本语义概念)的部分子表达式的特定的数据结构。 **注释** 例如,通过保留中间值待延迟求值,可实现子表达式值的[按需传递](#求值策略)。 #### 顺序求值   明确的词法顺序可为同一个表达式的若干子表达式提供一致的有序[求值策略](#求值策略):从左到右或从右到左。为一致性,不需要考虑其它特定顺序作为一般规则。   递归文法表示的表达式和子表达式之间存在相对内外顺序:子表达式在表达式的内部。此求值顺序可对应表达式树的遍历顺序。 #### 替换策略   对应项的规约规则的表达式的重写规则由派生实现定义,典型的可选项包括: * 名称替换:保证替换前后项对应的[名称](#名称)不变。 * 实体替换:保证替换前后项关联的实体不变。 * 值替换:保证替换前后项关联的表达式的值满足实现定义的相等关系。这包括以下不同的变体: * 值副本替换:保证替换前后项关联的表达式的值满足值替换的关系,且以实现定义的方式引用不同的[实体的副本](#实体的副本)。 * [引用](#一等引用)替换:保证替换前后项关联的表达式的值满足值替换的关系,且以实现定义的方式引用同一实体。 #### 求值策略   组合严格、顺序求值和替换策略可得到不同性质的求值策略。   除非派生实现约定,表达式求值策略可以随具体[语法形式](#语法形式)不同而不同。   典型性质组合如下: * 严格求值: * 应用序(applicative order) :以*最左最内(leftmost innermost)* 优先的顺序求值。 * 最左的顺序仅在操作数是[有序数据结构](#规范化中间表示)时有意义;不考虑操作数内部构造时,仅表示操作数作为子表达式总是被求值,和严格求值等价。 * 按值传递(pass by value) :使用值替换的严格求值。 * 按值的副本传递(pass by value copy) :创建[值的副本](#实体的副本)进行替换的严格求值。 * 按[引用](#一等引用)传递(pass by reference) :使用引用替换的严格求值。 * 共享对象传递(pass by shared object) :使用的共享机制以及对象和值或引用的关系由派生实现定义。 * 部分求值(partial evaluation) :允许求值分为多个*阶段(phase)* 分别进行。 * 非严格求值: * 正规序(normal order) :以*最左最外(leftmost outmost)* 优先的顺序求值。 * 最左的顺序的意义同应用序。 * 按名传递(pass by name) :使用名称替换且保持作为名称的表达式最后被替换的求值。 * 按需传递(pass by need) :按名传递但允许合并作用相同的表达式。 * [非决定性](#规约顺序)求值: * 完全归约(full reduction) :替换不受到作用之间的依赖的限制。 * 按预期传递(pass by future) :并发的按名传递,在需要使用参数的值时同步。 * 乐观求值(optimistic evaluation) :部分子表达式在未指定时机部分求值的按需求值,若超出约定时限则放弃并回退到按需求值。 ### 可选求值规则   应满足的本节上述约定的最小求值规则和语义外的具体求值的规则和语义由派生实现定义。   派生实现的求值可满足以下节指定语义,此时应满足其中约定的规则。   若可选求值规则逻辑上蕴含规约规则,则被蕴含的规约规则的直接表述可在语言规则中被省略。 ### 上下文相关求值   在被求值的表达式以外,对应的规约规则在实现此规约的[元语言](../Terminology.zh-CN.md#程序设计语言)中可能是[上下文](../Terminology.zh-CN.md#自指)相关的,这种附加依赖的上下文为*求值上下文(evaluation context)* 。   求值上下文被作为元语言实现对象求值规则时的输入,可指定项所在的位置等不被被规约的项必然蕴含的附加信息。   由派生实现定义的特定求值上下文称为*尾上下文(tail context)* 。以尾上下文求值可提供附加的保证。   作为[项重写系统的上下文](#项重写系统)的实例,元语言中,一般的求值上下文 `C` 形式化为具有占位符 `[]` 和可选前缀 `v` 及可选后缀 `e` 的递归组合的串: ```xbnf C ::= [] | Ce | vC ```   其中 `e` 是被求值表达式,`v` 是作为[范式](#范式)的值。   除非另行指定,NPL 对象语言的[求值算法](#求值规约)使用的求值上下文总是[求值环境](#求值环境)。 **原理**   通过附加适当的求值规则保证对象语言中的表达式总是可唯一地被分解为这种表示,抽象的求值上下文可直接实现对象语言的求值。但语义描述和实现的基准都以[抽象机](#实现行为)替代,因为: * 抽象机语义允许不依赖[源程序](../Terminology.zh-CN.md#程序设计语言)的[表示](#表示)和构造(如特定的表达式的文法)。 * 这种分解一般要求遍历对象语言的源程序而难以具有较好的可实现性质,如[计算复杂度](../Terminology.zh-CN.md#计算机科学)。 * 为满足良好的可实现性质,需描述实现中可能具有的离散状态与只和其中个别状态关联的局部的求值规则时,这种分解通常会渐进演化为某种抽象机的表示。 **注释**   使用求值环境作为默认的上下文确保一般的求值总是能支持变量的[绑定](#基本语义概念)。   对象语言的实现同时能够支持其它上下文,即使它不在求值算法中出现。这样的上下文可能被求值上下文蕴含而可被推理确定。 ## λ 完备语义和对应语法   作为通用语言,求值规则表达的系统可具有和[无类型 λ 演算](#模型)对应的形式和计算能力。   基于此语义的派生实现应允许以下几种互不相交的表达式集合: * [名称表达式](#名称表达式) * [匿名函数](#函数) * [函数应用](#函数合并)   NPL 不要求以上表达式中函数以外的表达式求值的[强规范化](#范式)。 **注释**   无类型 λ 演算保证名称表达式(变量)和函数( λ 抽象)的规约的强规范化,但不保证函数应用规约的强规范化。   扩展的 λ 演算(如[简单类型 λ 演算](https://zh.wikipedia.org/zh-cn/%E7%AE%80%E5%8D%95%E7%B1%BB%E5%9E%8B%CE%BB%E6%BC%94%E7%AE%97))可保证规约函数应用的强规范化。 ### 名称表达式   *名称表达式(name expression)* 是表示变量的 λ 项。   [原子表达式](#原子表达式)的由派生实现定义的非空子集是名称表达式。其它作为名称表达式的表达式[语法形式](#语法形式)由派生实现定义。   名称表达式不被进一步规约;其求值是[值替换规则](#求值规约)的平凡形式。 ### 函数   *函数(function)* 是一种参与特定规约规则的实体,也可以指求值为函数实体的表达式。   一般地,函数表达式在 [WHNF](#规范化中间表示) 下作为[操作符](#规范化中间表示)被求值,其[最终求值结果](#范式)为函数实体,或函数对象(若函数在语言中允许作为对象)。   NPL 中,作为[一等对象](#基本语义概念)的函数表达式的最终求值结果是[合并子](#合并子)。   一个函数表达式是以下两种表达式之一: * 保持[等价地](#求值性质)求值到其它函数表达式上的[名称表达式](#名称表达式),称为*具名函数表达式(named function expression)* ,简称*具名函数(named function)* 。 * 满足本节以下规则的由派生实现定义的*匿名函数表达式(anonymous function expression)* ,简称*匿名函数(anonymous function)* 。   函数应确定替换重写规则[被替换的目标](#替换策略),即*函数体(function body)* 。   除非派生实现另行指定,函数不需要被进一步规约,此时其求值是[值替换规则](#求值规约)的平凡形式。   在[类型系统](#类型系统和类型机制)中,函数可被指派*函数类型(function type)* 。函数类型能蕴含[参数](#函数内部的变量)和结果的[类型](#类型)。 **注释** 例如,在简单类型 λ 演算中,函数类型是[类型构造器](#类型) `→` 组合输入(参数)和结果(输出)类型的[复合类型](#类型)。 #### 函数内部的变量   匿名函数可以显式指定(*绑定(bind)* )包含若干变量使之成为[约束变量](#基本语义概念)的语法构造。   通过创建函数时的显式的语法构造引入的这种变量称为函数的*形式参数(formal parameter, parameter)* 。   除绑定外,匿名函数蕴含[上下文](#项重写系统)可以[捕获](#项重写系统)若干在函数体以外的同名的[自由变量](#基本语义概念)。   通过绑定或捕获引入的变量允许在函数体中允许使用。   使用[词法作用域](#实现环境提供的求值环境)时,若匿名函数所在[作用域](#基本语义概念)的存在同名的[名称](#名称),则被捕获的名称被[隐藏](#基本语义概念)。形式参数隐藏被捕获的[变量名](#npla-环境)。   派生实现的语义规则应满足和 λ 演算的语义的 α-转换(alpha-conversion) 规则不矛盾。 **注释** [Vau 演算](#vau-抽象)在没有限定[环境](#求值环境)时不考虑一般意义上的自由变量。   函数应用的求值决定被绑定的变量和函数体内的变量之间的关系,参见[函数合并](#函数合并)。此时,[求值策略](#求值策略)蕴含的[替换策略](#求值策略)蕴含被绑定的变量和函数体内的变量之间的[同一性](#基本语义概念)。   类似地,在被捕获的变量到函数体内捕获的变量之间,也有和替换策略一一对应的不同捕获策略。   除非另行指定,变量被*按引用捕获(captured by reference)* 而非*按值的副本捕获(captured by value copy)* ,即通过捕获引入的变量是被捕获变量的引用而不是副本。 **原理**   捕获为引用而不是副本,保持被捕获的变量和函数体内同名变量的同一性,在实体是对象时不影响可观察行为。若这些捕获未被使用,可被实现直接移除。 #### 过程   *过程(procedure)* 是操作符[具现](../Terminology.zh-CN.md#程序设计语言)的*可调用(callable)* 的实体,决定特定的可提供求值的作用(包括决定求值结果)的[计算](../Terminology.zh-CN.md#计算机科学)。   函数表达式的[最终求值结果](#范式)由过程实体的作用中的[结果](#基本语义概念)决定,以派生实现定义的方式关联。   通过函数表达式可指定可选的[实际参数](#函数合并),发生过程*调用(call)* 。过程的调用蕴含计算。   过程中和过程外的计算的组合满足[因果性](#规约顺序): * 以求值描述的过程中的作用整体非[后序](#规约顺序)于引起过程中作用的外部环境的计算。 * 以求值描述的过程中的任意作用非后序于取得对应结果的[值计算](#基本语义概念),即结果是决定值的作用的[依赖](#规约顺序)。   *主调函数(caller function)* 等*调用者(caller)* 或其它引起过程中的计算的实体[转移](#基本语义概念)计算蕴含的[控制](#基本语义概念)到过程中的计算而使之*进入(enter)* 到*被调用者(callee)* 的过程。   过程可能被限制只有*一次(one-shot)* 调用有效;其它过程是*多次(multi-shot)* 的。   多次过程调用时控制可能通过调用被再次转移,即*嵌套调用(nested call)* 。   一些被多次调用的过程可能被多次进入,即*重入(reenter)* 。   一个调用中的重入相同或不同过程的次数称为调用的*深度(depth)* 。   推论:嵌套调用是深度大于 1 的调用。   通过嵌套调用直接(总是以自身作为调用者)或间接(通过其它调用者转移控制)的重入是*递归调用(recursive call)* 。   过程可以*返回(return)* 取得计算的值并可同时改变[控制状态](#基本语义概念),影响之后的计算。 **原理**   一次过程,特别是在其内部涉及和[续延](#续延)或[闭包](#实现环境提供的求值环境)的实现交互时,相对多次过程可能具有因其对持有资源的要求较宽松,而具有较小的性能开销。 **注释**   对象语言中的过程在描述操作语义的[元语言](../Terminology.zh-CN.md#程序设计语言)中可表示为函数,其应用可对应对象语言中过程的隐式调用。   违反一次过程调用有效地约束的程序典型地[引起错误](#错误)。   注意过程不一定可作为可被对象语言直接表达的*一等(first-class)* 函数,但同时在元语言中仍然可能可行。如[无界续延](#续延),因为可能不符合[函数的类型要求](#函数),详见[续延的捕获和调用](#续延的捕获和调用)中的原理。   一次重入的过程调用分配的资源对应一个[活动记录帧](#活动记录)。 #### 过程调用的计算顺序   按计算的顺序约束和默认返回控制的方式,可能有不同的形式。   *例程(routine)* 的求值不*交叉(interleave)* ,即例程中的计算和例程外的计算[非决定性有序](#规约顺序)。 **注释** 典型地,例程中的计算通过例程作为函数实体创建时的函数体确定。   作为不同的例程,不考虑例程中的计算的[续延](#续延)被保存时: * *子例程(subroutine)* 在返回一次后不[重入](#过程)。 * *协程(coroutine)* 则可能被多次重入并引起多次返回。   和子例程的[正常控制](#程序的控制执行条件)不同,即便其中的计算不涉及显式地改变控制状态,协程可能蕴含控制从协程中的计算到协程外的计算的[转移](#过程): * 引起多次返回对应改变[控制作用](#基本语义概念)。 * 转移控制后,函数体中的计算被*暂停(suspended)* 。 * 重入的协程可*恢复(resume)* 被暂停的计算。 * 不排除可被重入的协程作为函数实体,是*可恢复函数(resumable function)* 。 * 可被暂停和恢复的计算是*异步的(asynchrnous)* 。这和正常控制的*同步的(synchronous)* 的计算相对。   一般的续延支持返回多次并可能支持和调用者并发的计算,包括异步的计算;而协程蕴含的控制作用的改变对应不同续延的替换,也能实现类似的支持。   语言的语法可显式指定例程创建协程,也可以当前的控制状态创建和现有的例程没有直接对应的协程。后者类似[续延捕获](#续延的捕获和调用)。   NPL 支持函数求值得到过程。对象语言中的过程可能支持使用这些形式的一种或多种,具体形式由派生实现指定。   协程可能限制转移向下一步骤的计算转移的方向,即调用者和被调用者被通过创建其的语法构造确定,而不能在之后改变。   根据是否只提供一种不区分转移方向的原语,协程分为*对称(symmetric)* 和*非对称(asymmetric)* 协程: * 对称协程转移控制到另一个协程,不需要单独区分不同的操作。 * 转移控制的源和目标之间没有[调用者](#过程)和[被调用者](#过程)的相对关系。 * 非对称协程对控制的转移分为*调用(invoke)* 和*出让(yield)* 操作,其中: * 调用操作从调用者转移控制到被调用者,恢复之前保存的上下文(若有)或创建时的初始上下文。 * 出让操作暂停和保存当前上下文并返回(转移)控制到它的调用者。 * 一般地,转移控制的具体时机未指定,可蕴含(对应续延的)[调度](#续延)和[并发](../Terminology.zh-CN.md#计算机科学)执行。 * 一些协程称为*半(semi)* 异步协程(半协程),以体现实现异步计算的控制转移形势受限的非典型性。对应地,没有此类限制的协程被称为*全(full)* 异步协程(全协程)。 * 通常半协程指对控制的转移(相对传统的例程调用)受限,不能仅通过调用而需要单独的出让操作实现计算的暂停。这是非对称协程的同义词。 * 但半协程也可能指特指暂停在特定上下文受限的协程实现。 * **注释** 另见[这里](http://www.lua.org/pil/9.1.html)的说明。   根据是否协程持有[活动记录帧](#活动记录),协程分为*有栈(stackful)* 和*无栈(stackless)* 的。 * 两者提供不同的资源[所有权](#所有权抽象),而可能影响使用这些特性的程序中的资源的[可用性](#可用性)。 * 特别地,无栈协程不保证活动记录的可用性,无法直接支持创建的协程作为一等对象使用。   因为具有类似的改变控制的能力,有栈的、可作为[一等对象](#基本语义概念)的*全协程(full coroutine)* 可替代[一等续延](#续延的捕获和调用)。 **原理**   协程可视为是在计算上[可表达性](#完整性)等价的[一次续延](#续延的捕获和调用): * 参见[这里](http://www.cs.tufts.edu/comp/250RTS/archive/roberto-ierusalimschy/revisiting-coroutines.pdf)。 * 其中,对称协程类似一次[无界续延](#续延),非对称协程类似一次[有界续延](#续延)。 * 对称协程可通过非对称协程补充操作实现。 * **注释** 一个这种设计的例子参见 [\[WG21 P0913R0\]](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0913r0.html) 。 * 类似地,有界续延可[通过添加显式的界限实现无界续延](#续延)。 * 但是,这种功能相似不表示一一对应。 * **注释** 参见以下关于出让操作使用续延实现的讨论。 * 在核心语言支持*存储(store)* 即[可修改](#实体的不可变性)的[一等状态](#一等状态)的[副作用](#基本语义概念)的前提下,非对称协程和对称协程在可表达性上等价。 * 有界续延可不依赖其它副作用表达状态,但[无界续延无法表达](#一等状态)。 * 一等续延和协程在一定条件下可互相实现。 * 对称协程可实现一次续延。 * **注释** 另见[这里](http://www.inf.puc-rio.br/~roberto/docs/MCC15-04.pdf),但这个实现没有检查续延调用内部可能的非预期重入,且不满足 \[Hi90] 中的 [PTC 要求](#尾调用和-ptc)。 * 因此,作为一等对象时,协程可作为一等续延的替代实现方式。 * 非对称协程的出让操作可通过无界续延实现。 * 虽然可实现出让操作的[续延捕获](#续延的捕获和调用)并非[续延调用](#续延的捕获和调用),但续延调用对控制的转移不必然蕴含区分调用和调用者。 * 事实上,使用 `call/cc` 捕获续延创建的是无界续延。 * 有界续延可实现无界续延,因此出让操作也可使用有界续延实现。 * **注释** 另见[这里](https://cs.indiana.edu/~sabry/papers/yield.pdf)。   典型的设计中,函数表达式默认创建例程,而协程使用特设的语法标记过程得到。特设的关键字(如 `yield` )提供语法,对应非对称协程中的出让操作。 **注释**   关于过程的参数和过程调用之间的计算顺序,参见[求值策略](#求值策略)。 #### λ 抽象   λ 抽象(lambda abstraction) 是 λ 演算中的基本构成之一,提供匿名函数。 **注释** λ 抽象的语法包含的形式是典型的操作符。   在原始的无类型 λ 演算中,λ 抽象[不支持蕴含副作用](#求值性质),子表达式求值顺序任取而不改变[范式](#范式)的存在性和值。   在使用[热情求值](#λ-求值策略)的语言中,λ 抽象创建的过程是应用[合并子](#合并子)。 #### vau 抽象   Vau 抽象(vau abstraction) 是 vau 演算 (\[Shu10]) 中的基本构成之一。   Vau 抽象创建的过程是操作[合并子](#合并子)。 **注释** 使用 vau 抽象可实现引入 λ 抽象的操作符,如 \[RnRK] 提供的 `$vau` 操作合并子。 ### 函数合并   具有操作符和操作数的项的组合可被特定的方式进行规约。这种组合是*函数合并(function combination)* ,包含: * 具有至少一个约定位置的[子项](#基本语义概念) `E1` 的[复合表达式](#复合表达式) `E` ,当且仅当 `E1` 是被求值作为[操作符](#规范化中间表示)的函数时,`E` 是*函数合并表达式(function combination expression)* 。 * 其它具有操作符和操作数的项是非表达式形式的函数合并。以下操作符和操作数记作 `E1` 和 `E2` 。   以下规则中,非表达式形式的函数合并也可被视为表达式求值。   求值函数合并时,子项 `E1` 总是被求值。   除 `E1` 外表达式的剩余子项 `E2` 是[操作数](#规范化中间表示),在 E 被求值时以操作数决定的值等效*替换(substitute)* 函数的[形式参数](#函数内部的变量)。   替换形式参数的值是*实际参数(actual argument, argument)* 。   函数合并的求值是[值替换规则](#求值规约)的非平凡形式。   若替换操作数 `E2` 在合并中被求值,函数合并 E 是函数应用表达式,简称*函数应用(function application)* 。   若操作符是 λ 抽象,`E2` 视为一个整体,则函数应用替换规则对应 λ 演算的 β-规约(beta-reduction) 规则。   其它函数合并使用的替换规则由派生实现指定。   派生实现应指定函数合并[规约](#基本语义概念)的结果是[规范形式](#范式),它对应的值是函数合并的[求值结果](#基本语义概念)替换被求值的表达式的实体,称为*函数值(function value)* 。   函数应用匹配实际参数和对应的引入形式参数的构造。匹配可能失败。确定匹配参数成功的条件是等价关系,称为参数匹配一致性,由参数匹配的等价关系指定。   匹配成功的每个实际参数和被匹配的目标(可能是形式参数)具有一对一或多对一的对应关系。   伴随参数匹配,实现可引入其它必要的操作(如为匹配分配资源和确定上述对应关系)。这些操作可具有和确定参数对应关系的匹配之间[非决定性有序](#规约顺序)的副作用。   仅当上述必要操作及所有实际参数的匹配成功,替换 `E1` 决定的某个关联表达式中和形式参数结构一致的子表达式为实际参数。替换参数的结构一致性是等价关系。   表达式相等蕴含参数匹配一致性和替换结构一致性。实现可分别定义其它规则扩充这些等价关系的外延。   替换参数的值蕴含对实际参数的计算的依赖,即参数若被求值,其[值计算](#基本语义概念)[先序](#规约顺序)函数应用的求值;但其它求值顺序没有保证。 **注释**   一般地,根据 `E1` 的值,操作数或操作数的值计算的结果被作为实际参数。   [过程](#过程)及其调用在其操作语义的[元语言](../Terminology.zh-CN.md#程序设计语言)中通常表达为函数及函数合并。   若过程的结果被忽略,则通常表达为[单元类型](#类型)的值。   此外,一些语言中忽略过程的结果是[空类型](#类型),以检查错误的使用。NPL 不要求语言具有[静态类型](#类型系统和类型机制)规则,也不要求这些检查。 #### 函数调用   求值函数合并包含子表达式的求值:总是求值操作符,并可能求值操作数。若这些求值都没有退出,则发生函数*调用(call)* ,函数是*被调函数(called function)* 。   若被调函数存在形式参数,函数调用首先以操作数的直接子表达式作为实际参数,匹配实际参数和形式参数。   若实际参数匹配的目标可指定一个变量,则伴随参数匹配的操作包括以特定规则[绑定](#函数内部的变量)的形式参数。   绑定的实际参数和对应的形式参数作为不同的实体时,作为伴随参数匹配的必要操作的一部分,发生*参数传递(parameter passing)* 。参数传递使形式参数具有作为实际参数值的副本。参数传递可能使和实际参数相关的资源被复制或转移。   实现在函数合并的求值中应提供函数调用的支持。   函数调用确定副作用的边界:保证参数表达式在函数应用被求值之前被求值。   在控制[返回](#过程)时,函数调用内部确定的值最终替换被求值的函数合并而作为函数值,即为*返回值(return value)* 。   若函数是[过程](#过程),对应的函数调用是*过程调用(procedure call)* 。   若一个函数的调用仍待返回,则该函数调用是*活动的(active)* 。   调用总是不蕴含非[纯求值](#求值性质)的函数是*纯函数(pure function)* 。   函数调用的中蕴含的求值对应的[规约步骤](#规约规则和求值)的集合是它的*动态范围(dynamic extent)* 。   函数中被[捕获](#函数内部的变量)的实体的引用和求值函数中的计算创建的对象的引用构成函数计算的结果时,引用可能*逃逸(escape)* ,即在调用的动态范围以外可访问。   派生实现可能约定附加的名义特征区分其它情形相同的调用,称为*调用约定(calling convention)* 。 **注释**   典型实现的函数指称过程,函数调用是过程调用。   一般地,被调用的函数及函数调用的作用的等价性通常不能被确定。   一个重要的子类是[不能确定具体表示](#规范化中间表示)的情形,参见[合并子](#合并子)。其它函数一般也有类似限制。   关于函数调用中的求值,另见函数调用的[终止保证](#终止保证)。   和 \[RnRS] 不同,动态范围仅对求值定义,而不是关于环境中的绑定显示[计算作用](../Terminology.zh-CN.md#程序设计语言)的属性。这种属性事实上对象的[生存期](#基本语义概念),仅对对象而非更一般的实体有效。   [续延](#续延)可用其动态范围表示。   本文档的动态范围的概念定义和 \[RnRK] §7.1 的定义兼容,但不依赖其对续延的描述,也适用[抽象机语义](#实现行为),是 \[RnRK] 的一般化。   \[Racket] 使用求值的规约步骤在表达式上定义动态范围。NPL 不在表达式上采用类似的定义,因为: * 类似 \[RnRK] ,NPL 强调支持对象语言中的[显式求值风格](#其它设计和实现参考)及表达式求值前后的不同。 * 类似 \[RnRK] ,进一步地,NPL 派生语言(如 NPLA1 )可明确支持在对象语言中指定[求值环境](#求值环境)而改变求值的上下文,表达式不能被预期通常以[上下文](#上下文相关求值)无关的方式被求值。   调用约定可提升实现细节,为[互操作](../Terminology.zh-CN.md#规范)提供接口保证,避免非预期的不兼容实现的混合。 #### 合并子   除非另行指定,NPL 假定函数合并满足以下典型情形,即函数合并的[操作符](#规范化中间表示)求值为以下类型的*合并子(combiner)* 之一: * 对操作数的直接[操作](#规范化中间表示)(而不要求对操作数求值)的合并子是*操作合并子(operative combiner)* ,简称*操作子(operative)* 。 * 进行函数应用的合并子是*应用合并子(applicative combiner)* ,简称*应用子(applicative)* 。 * 由派生实现定义的*扩展合并子(extended combiner)* 。   合并子的函数应用(依赖对操作数进行至少一次求值)是*合并子应用(combiner application)* 。   合并子应用使用[应用序](#求值策略)。   应用子总是对应一个*底层(underlying)* 合并子,可通过底层合并子上的一元的*包装(wrap)* 操作得到;其逆操作为*解包装(unwrap)* 。   解包装结果不是扩展合并子的合并子称为*真合并子(proper combiner)* 。   合并子上可以定义若干等价关系,这些等价关系蕴含关于函数应用替换的基本形式:   若对任意上下文,替换一个应用中的合并子为另一个不改变函数应用替换的结果,则这两个合并子等价(对应 λ 演算的 β-等价)。 **注释**   合并子被[调用](#函数调用)时通常[返回](#过程)且仅返回一次。 **注释** 详见[续延的捕获和调用](#续延的捕获和调用)。   由于程序可能引入未知具体表示的合并子(如从其它模块链接),以上等价可能无法判定,不要求实现提供。   因为本设计不依赖 λ 抽象的[内部表示](../Terminology.zh-CN.md#程序设计语言)(特别是支持[惰性求值](#λ-求值策略)为目的的),不依赖 η-变换的[可用性](#可用性),也不要求支持更强的 βη-等价。   派生实现可按需定义较弱的[等价谓词](#实体的等价性),保证其判定结果蕴含上述等价关系的结果。 #### 续延的捕获和调用   语言可提供作为[一等实体](#基本语义概念)的[续延](#续延)即*一等续延(first-class continuation)* 。   续延的*捕获(capture)* [具现](../Terminology.zh-CN.md#程序设计语言)[当前续延](#续延)为对象语言中可操作的一等续延。   类似[过程](#过程),续延可被[一次或多次调用](#过程),称为*续延调用(continuation call)* 。   续延调用接受一个[实际参数](#函数合并)作为传递给后继[规约步骤](#规约规则和求值)使用的[值](#基本语义概念)。除非另行指定,续延参数被[按值传递](#求值策略)。被调用的续延可[访问](#基本语义概念)参数并执行其蕴含的其余规约步骤。   和接受实际参数对应,续延可被假定关联一个等效的[应用子](#合并子),具有一个[形式参数](#函数内部的变量),这个应用子的[底层合并子](#合并子)被[调用](#函数调用)时[非正常地](#程序的控制执行条件)传递它的[操作数](#规范化中间表示)给关联的续延。   对象语言可支持符合[函数类型要求](#函数)的一等续延作为函数。作为一等续延的函数可直接作为[合并子](#合并子)构成[函数合并](#函数合并)进行[函数调用](#函数调用),而实现续延调用。   除非派生实现另行指定,NPL 的一等续延不是函数。   函数应用(如[合并子调用](#函数调用))可隐含([非一等对象](#一等实体和一等对象)的)续延调用。   续延调用的其它的具体形式由派生实现定义。   除非在捕获的续延上存在特定的[控制作用](#基本语义概念),合并子被调用时以当前续延[返回](#函数调用)且仅返回一次。   类似[函数应用表达式](#函数合并),*续延应用(continuation application)* 表达式是求值时蕴含[续延调用](#续延的捕获和调用)的表达式。 **原理**   在 Scheme 中,一等续延即过程。   在限制[元语言](../Terminology.zh-CN.md#程序设计语言)的函数不蕴含控制作用时,类似 Scheme 等支持的无界[续延](#续延)不是函数。一个理由不能以常规方式为无界续延指定是函数[类型](#类型)。参见[这里](https://okmij.org/ftp/continuations/undelimited.html#introduction)的介绍。   在 Kernel 和其它一些语言中,续延不是过程,而具有不同的[名义类型](#类型等价性)。这种不同于 Scheme 的设计是有意的。   NPL 一等续延不限制是否和函数类型同一,因此无界续延仍可被视为函数(或更确切地,即程序入口作为边界的有界续延)。   类似 \[RnRK] 的设计,因为一等续延的调用可引起和更常见的过程调用显著不同的控制作用,续延调用有必要和过程调用在对象语言的语法上显式区分以满足[易预测性](#易预测性),因此一等续延一般不是函数。   续延关联的等效应用子的原理同 \[RnRK] §7 和 §7.2.5(应用子 `continuation->applicative`)的原理,但略有不同: * 作为一等对象的续延和续延的实际参数是否求值无关,因此不是合并子,[求值算法](#求值规约)不需要支持续延作为函数合并被求值;但续延可通过特定的操作转换为应用子。 * 续延和操作子在被调用时都接受一个实际参数对象。 * 对前者,对象典型地表示计算的结果,即已被求值。 * 对后者,对象是操作数。典型地,操作子作为应用子的底层合并子,操作数已被作为应用子的实际参数被求值算法求值。 * 类似 \[RnRK] 而和 \[RnRS] 不同,因为[函数合并](#函数合并)可接受[非真列表](#广义列表)作为参数,非列表的操作数可以和非列表的续延实际参数直接对应。 * 因为函数合并的这种性质,续延关联的应用子的应用和续延应用存在直接的一一对应关系。 * 但是,为避免和[简单性](#简单性)冲突,\[RnRK] 的*选择器(selector)* 支持在此未被要求。 **注释**   类似过程,续延及其调用在其操作语义的元语言中能表示为元语言的[函数应用](#函数合并),通常表达为[函数](#函数)及[函数合并](#函数合并)。   续延捕获在语法上类似函数对变量的[捕获](#函数内部的变量)。被捕获的实体通常以引用保存。被捕获的实体通常是隐式的,即不在对象语言程序中出现。   在支持一等续延且捕获的续延可被复制的语言中,实现需要考虑活动记录的复制,参见 \[Hi90] 。   关于控制作用,另见续延调用[对程序控制的改变](#程序的控制执行条件)。 #### 活动记录   [活动的](#函数调用)合并子分配的对象称为*活动记录(activation record)* 。   [函数调用](#函数调用)以活动记录引用涉及的变量。每个调用的活动记录中可保存多个变量。活动记录可能因此持有状态,即便不一定可被函数调用外的操作直接修改。   嵌套的函数调用具有多次分配的活动记录。为强调其中的对应关系,每一个调用关联其中的一个*帧(frame)* 。   在确定一次分配的一个活动记录对应一次函数调用的实现中,一个活动记录和一个活动记录的帧同义。   活动记录的集合可能构成特定的数据结构。例如限制只支持嵌套的子例程调用(而不支持一般的[续延调用](#续延的捕获和调用))时,具有后入先出(LIFO, last-in-first-out) 的栈的结构。 ### λ 求值策略   在[变量](#名称表达式)绑定值后,兼容 λ 演算规约语义(特别地,β-规约)的表达式的[具体求值](#基本语义概念)根据是否传递操作数对使用[按需传递](#求值策略)的情形分为三类: * (完全)*惰性求值(lazy evaluation)* * 部分惰性求值 * *热情求值(eager evaluation)*   其中,惰性求值总是使用按需传递,热情求值总是不使用按需传递,部分惰性求值不总是使用或不适用按需传递。   在保证不存在非[纯求值](#求值性质)时这些求值的[计算作用](../Terminology.zh-CN.md#程序设计语言)没有实质差异。存在非纯求值时,使用的 λ 求值策略由派生实现定义。   [非严格求值](#严格性)严格蕴含惰性求值。两者经常但不总是一致,例如,实现可能[并行地](../Terminology.zh-CN.md#计算机科学)热情求值,并舍弃部分结果以实现非严格求值。   热情求值蕴含[严格求值](#严格性)。两者也经常但不总是一致,例如,实现可能使用应用序严格求值。但因为非严格的热情求值缺乏性能等可局部优化的实用动机,这种不一致的情况通常不作为附加的语言特性提供(而仅为简化实现默认作为全局策略使用)。 **注释**   由于实现可能确定特定表达式的作用对约定必须保持的[程序行为](#状态和行为)没有影响而可能省略求值,按[抽象机](#实现行为)语义的严格求值在实际实现中通常是不必要的。   惰性求值可通过中间值[延迟求值](#严格性)实现。 ## 上下文   *上下文(context)* 是表达式关联的状态的特定集合。 **注释** 这里不是[自指概念](../Terminology.zh-CN.md#自指)。   一个上下文是*显式的(explicit)* ,当且仅当它可以通过[名称表达式](#名称表达式)访问。   一个上下文是*隐式的(implicit)* ,当且仅当它不是显式的。   隐式的上下文通常是[管理状态](#状态和行为)。   确定上下文的状态或对可变上下文的[修改](#基本语义概念)是对上下文的[访问](#基本语义概念)。   规约规则中,以未指定子项参数化的项是一个上下文。   本节以外其它关于上下文的具体规则由派生实现定义。 **注释**   参数化的子项可在([元语言](../Terminology.zh-CN.md#程序设计语言)的)语法上被表示为一个*洞(hole)* ,详见[上下文相关求值](#上下文相关求值)中的语法 `[]` 。   [过程实体](#过程)能影响[函数表达式](#表达式)关联的上下文,参见[函数和函数应用的求值环境](#函数和函数应用的求值环境)。 ### 求值环境   *求值环境(evaluation environment)* 是在求值时可访问的隐式上下文,提供可通过[名称解析](#名称解析)访问的变量的[绑定](#基本语义概念)。   不和[实现环境](#略称)相混淆的情况下,求值环境简称(变量或对应的局部绑定所在的)为*环境(environment)* 。   具有[可见名称](#可见名称)的绑定是*可见的(visible)* 。   环境*包含(contain)* 若干个*局部绑定(local binding)* ,即不通过其它环境即保证可见的*被绑定实体(bound entity)* 。   环境*展示(exhibit)* 可见的绑定。   一个环境是*空环境(empty environment)* ,当且仅当其中包含的局部绑定集合是空集。 **注释**   按绑定的定义,求值环境的局部绑定集合即变量的名称和通过声明引入的被变量表示的实体构成的映射。   可见绑定可能被通过名称解析成功访问变量。   包含和展示的定义同 \[RnRK] 。除此之外,[环境对象具有直接包含的绑定的所有权](#环境对象)。 #### 实现环境提供的求值环境   实现环境可能在实现以外提供附加的求值环境作为任务通信的机制,如环境变量。   除非派生实现另行指定,语言支持的求值环境和这些机制蕴含的求值环境的交集为空。语言可以库的形式提供 [API](../Terminology.zh-CN.md#程序设计语言) 另行支持。 #### 函数和函数应用的求值环境   在典型的对象语言中 [λ 抽象](#λ-抽象)中指定的替换构造具有*局部作用域(local scoping)* ,其中可访问 λ 抽象外部词法意义上*包含的(enclosing)* 求值环境的变量,对应求值环境为*局部环境(local environment)* 。   在基于*词法作用域(lexical scoping)* 的对象语言中,引入 λ 抽象对应的语言构造支持[捕获](#函数内部的变量)引入函数时所在的作用域的环境,称为*静态环境(static environment)* 。   相对地,*动态作用域(dynamic scoping)* 根据求值时的状态指定指称。   [Vau 抽象](#vau-抽象)进一步支持在局部环境中提供访问[函数应用](#函数合并)时的求值环境,即*动态环境(dynamic environment)* 的机制。   除非另行指定,按*词法闭包(lexical closure)* 规则捕获,即只根据词法作用域确定捕获的指称;若需要支持依赖求值状态动态确定指称时,使用派生实现提供的对求值环境的操作,而不依赖动态作用域。   作为[过程](#过程)的实现,词法闭包规则捕获实体创建*闭包(closure)* 。   除非另行指定,NPL 只存在一种作用域,即所有作用域都使用相同的[名称解析](#名称解析)和捕获规则。 **注释**   历史上,闭包首先在 [SECD 抽象机](https://academic.oup.com/comjnl/article-pdf/6/4/308/1067901/6-4-308.pdf)中引入。术语闭包来自 λ 演算的[闭项](#项重写系统)。 ### 互操作上下文   用于[互操作](../Terminology.zh-CN.md#规范)的和求值关联的隐式上下文是*互操作上下文(interoperation context)* 。   除非派生实现另行指定,语言不提供访问互操作上下文的公开接口。 **注释**   一个典型的实例:由 [ISA](#补充领域定义)约定的通用架构寄存器的状态,可能需要在[函数调用](#函数调用)或任务切换过程中保存和重置。 ## 类型   *类型(type)* 是上下文中和特定的实体直接关联或间接关联的元素,满足[某个执行阶段的不变量约束](#阶段不变量约束)。   *类型规则(type rule)* 是和类型相关的对象语言的语义规则。   实体关联的类型可能被显式地指定,或通过隐式的限定规则推断确定。符合指定和限定要求的类型可有任意多个。   实体的类型是被显式指定的实体关联的类型。实体具有实体的类型以及通过其它规则限定的类型。实体是类型的*实例(instance)* 。   类型可用集合[表示](#表示)。集合的元素是具有其表示的类型的实体。   表示类型的集合为空时,表示类型没有实例,是*空类型(empty type)* 。   推论:由集合的形式表达,空类型是唯一的。   表示类型的集合只有一个元素时,类型只有一个不可区分的实例,这样的类型是*单元类型(unit type)* 。   和表达式直接关联的类型满足起始阶段不变量约束,称为*静态类型(static type)* 。   和表达式的[值](#基本语义概念)关联的类型满足[运行阶段](#实现的执行阶段)的不变量约束,称为*动态类型(dynamic type)* 。   其它可能存在类型或实现执行阶段的扩展由派生实现定义。   除非另行指定,对象的类型是[对象的值](#基本语义概念)的类型。   NPL 对象类型和[存储的值](#表示)的类型之间的关联未指定。   类型在描述类型规则的[元语言](../Terminology.zh-CN.md#程序设计语言)中可作为对象。   生成对象的元语言函数是*类型构造器(type constructor)* 。类型构造器的参数是类型,的函数值是组合这些参数得到的*复合类型(compound type)* 。 ### 类型系统和类型机制   称为类型的具体实体和之间的关联由派生实现的*类型系统(type system)* 规则指定。   默认类型系统不附加约束,所有表达式或关联的项都*没有指定类型(untyped)* ,为退化的*平凡类型系统(trivial type system)* 或*单一类型系统(unityped system)* ,实质上是动态类型。   对类型系统的分类中,类型也指确定类型的过程称为*类型机制(typing discipline)* ,其中确定类型的过程称为*定型(typing)* 。   在静态类型之后阶段确定的类型机制是*动态定型(dynamic typing)* 。   除非另行指定,被确定的静态类型的阶段是翻译时阶段;被确定的动态类型的阶段是翻译时之后,即[运行时](#实现的执行阶段)。   语言可提供[定型规则(typing rule) (en-US)](https://en.wikipedia.org/wiki/Typing_rule) ,指定[项](#基本语义概念)作为实体在特定的[上下文](#上下文)(称为*类型环境(typing environment)* )中的类型。项是类型在这个上下文中的*居留(inhabitant)* 。   类型环境确定*类型指派(type assignment)* ,即项和类型的之间的*定型关系(typing relation)* 。定型确定的这种定型关系的[实例](../Terminology.zh-CN.md#程序设计语言)即*定型判断(typing judgement)* 。   不违反类型系统规则下的[良定义的](../Terminology.zh-CN.md#规范)程序构造是*良型的(well-typed)* 。   根据是否要求项首先都是良型的再指派语义,带有类型的形式系统可具有[*内在(intrinsic)* 和*外在(extrinsic)* 的解释](https://en.wikipedia.org/wiki/Simply_typed_lambda_calculus#Intrinsic_vs._extrinsic_interpretations)。   除非另行指定,NPL 使用外在的解释。 **原理**   默认使用外在解释的理由是: * 类型的外在解释允许在一个没有指定具体类型系统设计的单一类型系统为基础扩展不同的类型系统,能满足语言自身[可扩展](#变化的自由)的需要。 * 扩展通用目的语言特性的顺序应是从简单到复杂的,而不是相反,因为并不存在已知的万能语言可供裁剪。 * 这也符合历史顺序:无类型 λ 演算被扩展到不同的有类型 λ 演算,而不是相反;因为有类型 λ 演算的规则明显较无类型 λ 演算多且复杂。 * 从无类型 λ 演算可以扩展到的一些特性更丰富其它系统,如 [λμ 演算 (en-US)](https://en.wikipedia.org/wiki/Lambda-mu_calculus) 和 vau 演算,首先都是无类型的,并不存在可用的内在解释。 * 为了描述类型规则,外在解释最终需要在整个系统中引入和对象语言不同的[元语言](../Terminology.zh-CN.md#程序设计语言),而增加复杂性。 * 即便存在强调可扩展的对象语言(如 [MLPolyR](https://arxiv.org/abs/0910.2654)),至少语言规范中定义的元语言没有被证明可以和被描述类型规则的对象语言合并。 * 即便能证明可以合并,这种方式也显著地大大增加了设计的复杂性,违反[避免不必要付出的代价](#避免不必要付出的代价)。 * 根本上,这种方式损害对象语言设计的[光滑性](#统一性),很可能大大削弱对象语言的[可用性](#可用性)。 * 没有确切的充分依据证明引入类型系统带来的性质是通过非类型论的直接扩展演绎系统的方式不能实现或者其实现有现实困难的。 * 因此先验地要求类型的存在缺乏必要性。即便可实现需求,在通用目的上通常是舍近求远的过度设计。 * 即便引入类型的方式有现成的工程实践而可以提升工程效率,也可能是[过早的优化](#避免不成熟的优化)。 * 更何况现实并没有证据表明存在这样的成功实践。 * 跳出先验地引入类型的做法,使用先验的内在解释而排除不够清晰明确的含义(meaning) 的语法的方式,在历史上存在更显著的失败。 * \[Chu41] §18 试图排除原始的 λ 演算(称为 λ-K-转换,在 \[Bare84]\[Shu10] 中称为 λ*K* 演算)中无法取得[范式](#范式)的项(以使之更适用于符号逻辑的目的):限制 λ 抽象中的约束变量是第二子项的自由变量。 * 非正式地,这在语法上要求每个[函数体](#函数)中的每个[变量](#基本语义概念)必须是某个唯一的函数的[形式参数](#函数内部的变量),且这个函数的函数体是语法上包含这个变量的表达式,即语言在语法上禁止出现(在声明以外)未使用的形式参数。 * 限制的 λ 演算在现代(如 \[Bare84]\[Shu10] )称为 λ*I* 演算,不支持表达 [K 组合子](https://zh.wikipedia.org/zh-cn/%E7%BB%84%E5%90%88%E5%AD%90%E9%80%BB%E8%BE%91)。 * \[Bare84] §2.2 指出 λ*I* 演算具有的一些问题,如: * 对应的理论 **λ*I*** 翻译到[组合子逻辑](https://zh.wikipedia.org/zh-cn/%E7%BB%84%E5%90%88%E5%AD%90%E9%80%BB%E8%BE%91)的理论 **CL** 时,项能取得[范式](#范式)的性质不被保持。 * 范式的概念过于侧重语法,所以在[模型](../Terminology.zh-CN.md#计算机科学)中不确定含义。 * 试图识别编码[偏函数(partial function) (en-US)](https://en.wikipedia.org/wiki/Partial_function) 需要的“未定义”的项是不可能的。 * λ*I* 演算定义的偏函数的组合对应的项不一定是 λ*I* 演算定义的被组合的偏函数的项的组合。 * \[Bare84] §2.2 指出,这些问题都来自 \[Chu41] 选择用无法取得范式的项编码“未定义”的概念。 * 定义可解性(solvability) ,以不可解代替不能取得范式编码“未定义”可解决这个问题。 * 项的可解性定义为存在有限的项序列使前者在后者顺序应用得到 [I 组合子](https://zh.wikipedia.org/zh-cn/%E7%BB%84%E5%90%88%E5%AD%90%E9%80%BB%E8%BE%91)(即 λ*x*.*x* )。 * 在 λ*I* 演算中,不可解等价不能取得范式。而在 λ 演算中,不可解等价不能取得 [HNF](#规范化中间表示) 。 * λ 演算没有 λ*I* 演算的上述问题。 * λ*I* 表述的 Church–Turing 论题仅限于[全函数](#终止保证),而 λ 函数表述的论题能扩展到一般形式的偏函数。 * 即便不考虑上述整体性质,尽管计算上 λ*I* 演算是 [Turing 完备](#完整性)的,它不能编码[常量](#基本语义概念)函数。 * 因为也不包含[管理规约](#管理规约)规则,它实际上无法编码[可观察性质的项](#状态和行为),除非平凡地指定所有项都是可观察的。 * 以上问题一定程度上揭示了去除似乎冗余但实际在语义上可能非平凡的语法构造是[不成熟的简化](#避免不成熟的优化),损害系统的[可用性](#可用性)。 * 要求(可被类型检查的)类型系统直接排除不能取得范式的项,在这个意义上比 λ*I* 演算对去除特定的项的组合更彻底。 * 即便类型系统能引入其它语义,这以引入不能被对象语言表达的规则为代价,通常需要元语言。 * 相比之下,同样是引入对象语言表达式无法表达的语义,管理规约是对象语言规则能直接蕴含的,相对具有更小的(工作量和避免兼容问题上的)代价。 * **注释** 内在解释又被称为 Church 风格的。 * 哲学意义上,内在解释或本体论上的(ontological) 解释,相比外在解释或语义上的(semantical) 解释需要更强的假设。 * 本体论上的逻辑,如 [Frege-Church 本体论 (en-US)](https://en.wikipedia.org/wiki/Frege%E2%80%93Church_ontology),可能解决一些悖论。 * 但根本上,没有充分动机指出,不涉及[演绎规则](#演绎规则)的悖论必须在通用语言内部直接提供规则消除,而不能通过其它方式(例如,由[用户程序](#程序实现)补充前提)解决。 * 指定管理规约可以编码非平凡的表达语言规则外的语义的项可以对这样的前提建模并在语言中适当编码表达。 * 编码表达这种方式是 NPL 强调 [N(name)](#词法规则) 和其它实体分离的主要理由。 * 本体论假设要求名称以外附加实体以使假设生效。一般地,这些假设以不同语言的陈述作为断言实现。这些陈述涉及特称对象时,在完备性上是可疑的,且容易和[开放世界假定](#开放性)冲突。 * 约定不涉及的语言规则的本体论假设在这些意义上也可被认为在效用上的[不成熟的优化](#避免不成熟的优化)。 **注释**   在元语言的意义上,类型系统包含语法和对应的语义,但在对象语言中,定型规则和其它推理规则(如[类型检查](#类型检查)规则)作为语言规则是语义规则,和语法相对独立。   实体的类型可被指定为未指定类型,以明确类型的存在性,但不明确具体的类型的构造和表示。   形式地,在类型系统中,类型环境和项作为前提,通过定型规则(typing rule) 得到定型判断。定型规则在逻辑上可以是公理或定理。   在数理逻辑中,使用结构主义数学方法,集合可以作为描述类型规则的理论(句子集合)的模型,和理论支持描述的类型一一对应。 ### 类型等价性   通过显式指定标识(如名称)的方式定义类型的方法是*名义类型(nominal typing)* ,否则是*结构化类型(structrual typing)* 。   除非另行指定,不同的名义类型不蕴含等价关系。结构化类型之间的等价关系由实现定义。   类型的相等关系是一种类型之间的等价关系。两个类型相等,当且仅当它们的实例作为元素的两个集合对应相等。   除非另行指定,相等的类型不在语言中区分,且元语言(描述对象语言的规则)中类型作为实体的[同一性](#基本语义概念)即类型相等性。   推论:除非另行指定,不同的类型不等价。   除非另行指定,对象语言使用类型相等性实现类型等价性。 **原理**   对象语言中的类型实质上是类型的一种间接的表示,作为实体仍然可以具有不同的同一性。   这避免程序可能需要枚举类型的外延(即精确实现出表示它的集合)才能确保确切表示出这个类型这样的计算上不可行的困难。   因为可支持的表示的[类型全集](#类型全集)不同,类型相等是相对的,依赖类型系统的具体实现。一个类型系统可能支持无法在另一个类型系统中精确表示的类型。 **注释**   本节的主要例外参见[公共子类型](#类型序)。 ### 类型标注   根据是否需要特定的文法元素指定和项关联的类型即*类型标注(type annotation)* ,对确定类型的机制可进行分类。   类型系统可使用*显式类型(explicit typing)* ,即在[定型](#类型系统和类型机制)时要求类型标注。   不使用类型标注的方式是*隐式类型(implicit typing)* 。   在引入实体(特别地,如[变量](#基本语义概念))时指定实体的显式类型标注称为*清单类型(manifest typing)* 。   不使用清单类型而使隐式引入的实体(如值)关联具体类型的机制称为*潜在类型(latent typing)* 。   清单类型是显式类型的实例;除此之外,显式类型还包括*铸型(casting)* ,即显式指定表达式求值的结果应具有的类型。   潜在类型是隐式类型的实例;除此之外,隐式类型还包括*类型推断(type interferece)* ,即通过隐含的上下文信息判断表达式关联的类型。   类型推断的逆过程是*类型擦除(type erasure)* 。类型擦除支持使一个[良型](#类型系统和类型机制)的程序中的已被定型的实体表示擦除前按类型规则不允许表示的其它实体。   若类型机制可保证在某个[执行阶段](#实现的执行阶段)内有确定[强规范化性质](#范式)的算法确定类型,则类型机制在该阶段是[静态定型](#类型系统和类型机制)。 **注释** 强规范化性质的算法保证终止。   语言可能个别指定引入这些类型相关的规则,在保持逻辑相容的前提下可混合使用。   显式类型可编码接口的要求,即*类型签名(type signature)* 。   类型签名通常直接指定[名义类型](#类型等价性),但同时也可允许非特定的满足结构类型约束的类型。这些类型和类型签名*兼容(compatible)* 。 **原理**   历史上,表达式的类型和变量的类型在[简单类型 λ 演算](#λ-完备语义和对应语法)中同时被引入。后者修饰 λ 抽象中的[自由变量](#基本语义概念),而前者限定剩余的所有项。   即便从[项重写系统](#基本语义概念)中两者是形式上统一的,在实际语用中具有很不同的差异。这集中体现在后者是[名义的](#类型等价性),除非附加其它不同的语法设施,并不具有结构化推导的性质,原则上只适合描述接口;而前者能兼容[结构化类型](#类型等价性),同时适合描述接口及其实现。   作为接口的名义类型在作为自由变量以外的上下文中重新复用为不关心其类型(并消除依赖这些信息的[其它机制](#类型检查))的其它程序构造(一般意义上的表达式),通常需要类型擦除等更复杂的机制和支持的类型系统规则,以消去不再预期和其它类型系统规则交互的类型。   和 \[RnRK] 类似,NPL 不要求使用清单类型,以避免一些一般意义上的全局设计缺陷。这些缺陷包括: * 过于积极地(非预期地)排除危险但对程序有用的使用,而违反[易预测性](#易预测性)。 * 因为移除类型标注需要上述的复杂机制和类型系统规则,具体的清单类型阻碍派生语言定义其它不容易冲突的类型标注规则而使语言具有更好的[可扩展性](#变化的自由)。 * 因为名义类型的相关规则更容易直接拒绝一些和类型规则不兼容的程序构造而难以简单地变通,往往对程序构造的组合具有更多直接的[可表达性](#完整性)限制而破坏通用计算意义上的[正确性](#正确性)。 * **注释** 例如,许多类型系统不允许表达 [Y 组合子](https://zh.wikipedia.org/zh-cn/%E4%B8%8D%E5%8A%A8%E7%82%B9%E7%BB%84%E5%90%88%E5%AD%90#Y%E7%BB%84%E5%90%88%E5%AD%90)的构造[良型](#类型系统和类型机制)。   若有必要,派生语言仍可限定使用清单类型。一般仍然建议仅在局部引入而避免全局复杂性和因此带来的限制。 **注释**   类型签名来自数理逻辑术语。 ### 类型检查   *类型检查(typechecking)* 解答程序是否满足类型规则的判定性问题。   使用[翻译时](#实现的执行阶段)的[语义分析](#实现的执行阶段)或[运行时](#实现的执行阶段)的类型检查分别为静态类型检查和动态类型检查。   静态类型检查规则是[可诊断语义规则](#翻译时正确性规则)。   语言可能个别指定引入类型检查相关的规则,在保持逻辑相容的前提下可混合使用。   类型检查失败引起的[错误](#错误)称为*类型错误(type error)* 。 **注释**   注意静态类型检查和[静态定型](#类型系统和类型机制)以及动态类型检查和[动态定型](#类型系统和类型机制)的区别。类型检查和类型机制是不同的规则,不必然包含蕴含关系。   类型检查的一个典型的使用场景是[类型签名的兼容性](#类型标注)校验。 ### 类型全集   *类型全集(type universe)* 是语言规则中允许表达的类型的总称。 **注释** 表达类型的规则构成的模型的语言是语言规则的子集。   NPL 避免限定类型全集。派生语言可指定不同的规则。   除非派生实现另行指定,程序的用户不能依赖语言规则的限定枚举类型全集中的所有类型。 **原理**   类型全集是[论域](#开放性)的实例。避免限定类型全集符合[开放世界假定](#开放性)。 #### 类型谓词   判断值是否满足类型*居留(inhabitant)* 的[谓词](#基本语义概念)是*类型谓词(type predicate)* 。 **注释**   和 \[RnRK] 的基本类型谓词不同,类型谓词定义为只接受一个参数。 ### 类型序   类型之间可具有序关系。   被[定型](#类型系统和类型机制)的类型的实体可完全地满足其它类型的约束。前者具有后者的*子类型(subtype)* 。   *子类型(subtyping)* 关系是一种[预序(preorder) 关系](https://zh.wikipedia.org/zh-cn/%E9%A2%84%E5%BA%8F%E5%85%B3%E7%B3%BB),即自反的、反对称的二元关系。   相等的类型符合子类型关系,是*平凡的(trivial)* 。排除平凡的子类型关系是严格子类型关系。   严格子类型是严格预序关系,即反自反、反对称的二元关系。   子类型和严格子类型对应的逆关系是*超类型(super typing)* 和*严格超类型(strict supertyping)* 关系。   多个类型可具有公共的(严格)超类型。这些类型同为一个类型的子类型而[等价](#类型等价性)。   除非另行指定,在程序的行为不依赖其中特定的个别不相等的类型而具有差异时,具有相等超类型的等价的子类型视为相同的类型。   [复合类型](#类型)中其中一部分的类型替换为其子类型,得到的结果和原复合类型可能有如下[变化(variance)](https://zh.wikipedia.org/zh-cn/%E5%8D%8F%E5%8F%98%E4%B8%8E%E9%80%86%E5%8F%98#%E5%BD%A2%E5%BC%8F%E5%AE%9A%E4%B9%89) 的对应关系之一: * *协变(covariant)* :类型序被保持,即结果类型是原复合类型的子类型。 * *逆变(contravariant)* :类型序的逆被保持,即结果类型是原复合类型的超类型。 * *不变(invariant)* :不保持类型序,即结果类型和原复合类型之间没有确定的子类型关系。   同时存在以下派生归类: * *互变(bivariant)* :同时协变和逆变。 * *可变(variant)* :至少协变或逆变之一。   对接受参数类型得到结果类型的函数[类型构造器](#类型) → ,以下关系是确定的: * 参数类型对函数类型逆变。 * 结果类型对函数类型协变。   把[LSP](#封装)要求子类型经替换前后保持性质的谓词视为类型构造器,则 LSP 要求的性质是协变的。 **注释**   关于 → 的变化关系的陈述通常直接被作为类型系统中的[定型规则](#类型系统和类型机制)表达的公理,以和 → 既有的定型规则兼容。   一些非普遍的局部类型序的构造器,如数组的下标 `[]` ,也可对参数有确定的可变关系。   在 LSP 的[原始论文](https://www.cs.cmu.edu/~wing/publications/LiskovWing94.pdf)提供了两个满足 LSP(文中称为子类型要求(subtype requirement) )的在[过程](#过程)的[签名](#类型标注)定义子类型的方法,兼容以上传统的函数类型构造器的子类型变化关系,是 → 上的上述关系的扩展:这些定义还支持表达过程的具体前置条件(precondition) 和其中引发的[异常](#异常)。   对一般的谓词,LSP 的行为多态(behavioral polymorphism) 是不可判定的。因此,一般的 LSP 无法被[类型检查](#类型检查)。在[类型系统](#类型系统和类型机制)中应用 LSP 需依赖具体能表达性质的谓词,如使用的类型构造器。 #### 类型边界元素   一个类型系统可指定唯一的[*底类型(bottom type)* (en-US)](https://en.wikipedia.org/wiki/Bottom_type) 作为其它任何不同类型的严格子类型,记作⊥。若类型全集包含空类型,则底类型是[*空类型(empty type)* (en-US)](https://en.wikipedia.org/wiki/Empty_type) 。   一个类型系统可指定唯一的[*顶类型(top type)* (en-US)](https://en.wikipedia.org/wiki/Top_type) 作为其它任何不同类型的严格超类型,记作⊤。这种类型即*通用类型(universal type)* 。   NPL 支持空类型作为底类型,但不要求在对象语言中支持其表示。   NPL 避免要求唯一的顶类型的存在以符合[开放世界假设](#开放性)。   派生语言可指定不同的规则。 **原理**   以空类型作为子类型在类型序的推理上是自然的。   就非特定的类型全集,通用类型的的构造和表示不唯一,因此不能直接断言其存在。   否则,假定存在这种类型,则断言不存在其超类型,这可能和其它语义规则冲突。   即使在名义上定义具体的超类型(如 Java 的 [`java.lang.Object`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html)),也面临不能向上扩展(得到比 `Object` 更基本的类型)的问题,违反[最小接口原则](#最小接口原则)和[通用性](#其它推论和比较)。   具体的顶类型在断言当前类型系统不存在公共超类型可能仍然有实用意义;此时,顶类型即[一等实体](#基本语义概念)构成的类型,而不需要定义具体[名义类型](#类型等价性)。 ### 多态类型   特定的类型系统支持[类型签名](#类型标注)能对应多种不同的[兼容](#类型标注)类型。这样的类型是*多态的(polymorphic)* 。   一般地,类型上的*多态(polymorphism)* 有: * *特设(ad-hoc)* 多态:仅对项上局部的项上的类型作用使之满足上下文兼容要求的多态: * [函数](#函数合并)*重载(overload)* :同一个[名称](../Terminology.zh-CN.md#程序设计语言)对应的不同的函数实体,允许按[实际参数](#函数合并)的类型选择调用不同的函数。 * *强制(coercion)* :求值时使值向某个上下文要求的类型的隐式转换。 * *参数(parameteric)* 多态:接口签名指定以具体类型作为值的变量,组合为函数或者其它接口对应实体的类型。 * [子类型](#类型序)多态:接口签名编码接受子类型关系作为兼容类型。 * *行(row)* 多态:对组成具有名称和实体对构成的元素作为*成员(member)* 的实体,兼容限定部分成员的类型。   *多型(polytipic)* 的接口在同一个接口签名上以结构化类型的[隐式类型](#类型标注)构造支持不同的类型而支持多态。 **注释**   重载在一些语言中自动地对函数对应的具体[可调用实体](#过程)适用。   行多态以[结构化类型](#类型等价性)约束取代通常通过[名义类型](#类型等价性)指定的子类型关系。 ### 类型种类   *种类(kind)* 是[静态类型系统](#类型系统和类型机制)的语法表示中具有特定类型模式(pattern) 的分类。   一定意义上,种类是类型系统的元语言中一种元静态类型。   一般地,实体类型的种类记作 `*` 。   除非另行指定,作为项的函数应具有函数类型,即符合类型种类为 `* → *` 的结果的类型,如为[简单类型 λ 演算](#λ-完备语义和对应语法)兼容的函数类型实例。   其中,`→` 是函数类型的类型构造器。   种类作为元语言中的[类型多态](#多态类型),实现*种类多态(kind polymorphism)* :接口签名接受类型的编码中对应位置具有不同种类的类型。 **注释**   在实现中,种类也被作为[互操作](#实现的执行阶段)的归类,如视为[函数调用](#函数调用)的[调用约定](https://www.microsoft.com/en-us/research/uploads/prod/2020/03/kacc.pdf)。   但这不足以涵盖一般的形式定义;特别地,[调用](#过程)是仅仅关于[过程](#过程)这类实体的互操作,而种类适合一般实体的静态类型。例如,在不考虑进一步地实现时,[多变(levity) 多态](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/11/levity-pldi17.pdf)的类型不需要限定过程(函数)。   类型系统中的种类也可扩展到特定的计算作用的*作用系统(effect system)* 上以描述[作用的种类](#作用使用原则),此处从略。 ### 一等类型   [一等对象](#实体语义)的类型是*一等类型(first-class type)* 。   非一等类型的[居留](#类型系统和类型机制)可能不在对象语言中可表达,即对象语言中无法构造这些类型的值。   非一等类型仅用于构造其它类型(可能是一等类型)和类型检查等依赖类型的推理。 **注释**   一个典型的非一等类型的例子是 \[ISO C] 和 \[ISO C++] 等语言支持的类型 `void` 。   在语义的角度上,`void` 可视为依赖[翻译阶段](#实现的执行阶段)把[求值](#基本语义概念)时得到的对应 `void` 居留的表示替换为表示语义错误的单元类型,并在翻译结束前拒绝接受带有这种居留的程序,而这种居留在对象语言中始终不可取得。   若不限制翻译阶段,可通过在[传递](#求值策略)时始终限制[正常控制](#程序的控制执行条件)的值实现类似的效果,例如不考虑类型消除时 \[ISO C++] 中在复制或转移构造函数始终抛出异常的类类型。 ## 程序的控制执行条件   程序的执行可被[控制作用](#基本语义概念)影响。蕴含这些影响的条件即*执行条件(execution condition)* 。   程序的[控制状态](#基本语义概念)决定[求值](#基本语义概念)使用的[续延](#续延)。 **注释** 这和[过程的调用](#过程)类似。   更一般地,规约规则指定语言的实现决定程序行为时使用的(对程序不保证可见的)续延,这种在实现中对应的控制状态称为控制执行条件。   和控制状态不同,控制执行条件描述语言提供的不同控制机制的分类,而不被作为语言可编程的特性提供。   除非另行指定,仅由[求值算法](#求值规约)中蕴含的规约规则决定的执行条件是*正常(normal)* 的。   [合并子调用以当前续延返回](#续延的捕获和调用)是正常执行的。 **注释** 这是正常控制执行条件的一个主要实例。   改变程序的正常的控制要求存在控制作用,此时,控制执行条件是*非正常(abnormal)* 的。   除非另行指定,隐含在求值算法中蕴含的规约规则确定的[函数应用](#函数合并)外的[续延调用](#续延的捕获和调用)是非正常的。 **注释** 这是非正常控制执行条件的一个主要实例。   具有规约语义的语言总是支持正常控制条件。NPL 中,非正常的控制条件的支持是可选的。 ### 异常   由派生实现定义的非正常的控制条件是*异常(exceptional)* 条件。   *异常(excpetion)* 是通过*抛出(throw)* 实体(称为异常实体)同时表达满足异常条件的控制作用的语言构造。   语言的实现或用户通过特定操作(如求值一个表达式)指定程序满足异常条件,使程序的控制进入[异常执行状态](#正确性),允许程序具有正常条件下可分辨不同行为。   程序通过*捕获(catch)* 并*处理(handle)* 被抛出的实体,程序可满足不同的恢复正常执行的条件。   进入违反[翻译时正确性规则](#翻译时正确性规则)的异常执行状态时,由语言实现提供的异常执行机制实现行为。 **注释** 这些行为至少蕴含满足翻译时正确性规则要求的[诊断](../Terminology.zh-CN.md#程序设计语言)。   进入其它异常执行状态的异常条件包括所有[运行时](#实现的执行阶段)异常条件和直接引起程序异常的用户操作。   这些异常条件的具体行为和正常条件下的不同由派生实现指定的运行时状态或直接引起异常(改变程序的控制)或语言构造的语义决定。此时,由实现定义使用的异常执行机制。 **注释** 其它异常条件的异常执行机制可能和上述相同或不同。   派生语言实现可指定以下规则: * 符合以上约定的判断改变(进入和退出)异常执行状态的执行机制。 * 包括抛出和捕获的语言构造和其它可选的引起改变异常条件的上下文。   若派生实现不指定以上要求的执行机制和上下文,则不支持异常。   除非派生实现另行指定,异常的控制作用总是*被同步(synchronized)* 的,即: * 在初始化异常实体时,保证存在与异常条件关联且可确定单一的[执行线程](#并发实现)的状态作为引起控制状态改变即引发异常的来源。 * 异常条件的满足不依赖未和引发异常状态同步的程序中的其它的执行状态(包括其它未同步的线程的状态)。 * 确认满足异常条件和进入异常执行状态之间,上述执行线程内程序仅在引发异常的线程上的程序允许存在[计算作用](../Terminology.zh-CN.md#程序设计语言)(这保证不被引起可观察行为改变的其它线程的操作中断)。   除非派生实现另行指定,未捕获的异常总是确定性地(deterministically) 持续引发异常的执行线程中引起控制的转移: * 若捕获操作有效的上下文,控制转移捕获构造处理对应异常的*异常处理器(exception handler)* 。 * 否则,若在活动函数调用中,则单向地从[当前活动的函数](#活动记录)向其[主调函数](#过程)转移控制,使后者活动。 * 否则,若没有找到剩余的活动函数调用,则程序异常终止。   除非派生实现另行指定,上述转移活动函数若成功(包括异常在活动的主调函数嵌套的特定语言构造中被捕获),先前不再活动的活动记录中的资源在控制成功转移后应立即被释放。   典型的设计中,求值规则使的正常状态的函数调用要求的活动记录分配和释放满足 FIFO(Last-In First-Out ,后入先出)的顺序,构成了*栈(stack)* ,活动记录是*栈帧(stack frame)* 。   除非派生实现另行指定,活动函数的转移释放资源,应保证按和创建被其所有的实体的顺序的相反顺序一致的形式释放。这种释放活动记录占用资源的机制称为*栈展开(stack unwinding)* 。 ### 终止保证   特定的求值具有(确定性地)*终止(termination)* 保证,当且仅当预期求值总是在有限计算步骤内可描述的[计算作用](../Terminology.zh-CN.md#程序设计语言)。   具有终止保证的求值总是取得值或通过非正常控制的计算作用退出求值。   不具有终止保证的求值可能不终止,此时它具有取得值以外的计算作用;这种计算作用是[副作用](#基本语义概念)。   若一个函数的调用总是具有终止保证,则此函数是*终止函数(terminating function)* 。   若一个函数的调用总是取得值,则此函数是*全函数(total function)* 。 **注释** 全函数总是终止函数。 # NPLA   当前维护的主要派生语言为 **NPLA** ,是 NPL 的[抽象语言实现](../Terminology.zh-CN.md#规范)和[派生实现](#略称)。   NPLA 的参照实现 [NPLA1](#npla1-核心语言) 是具体语言实现,约定特定于当前参照实现的附加规则和实现。   作为原型设计,NPLA 重视可扩展性。   作为 NPL 的派生实现,NPLA [对象语言](../Terminology.zh-CN.md#程序设计语言)的设计遵循 NPL [符合性规则](#规范模型),并满足如下要求或附加限制。 **注释**   [NPLA1](#npla1-核心语言) 是 NPLA 的一个派生实现。 ## NPLA 领域语义支持 * 位(bit) :表示二进制存储的最小单位,具有 0 和 1 两种状态。 * 字节(byte) :[基本字符集](#字符集和字符串)中一个字符需要的最少的存储空间,是若干位的有序集合。 * 八元组(octet) :8 个位的有序集合。 ## NPLA 整体约定 ### NPLA 实现环境   NPLA 使用[宿主语言](#实现的执行阶段)为 \[ISO C++11](及其之后的向前兼容的版本)的简单实现模型 [NPL-EMA](#嵌入宿主语言实现) 。   以下要求和[宿主环境](#嵌入宿主语言实现)一致: * 一[字节](#npla-领域语义支持)占用的位(至少占用 8 个二进制位)。 * 作为事件顺序的[在先发生和在后发生](#规约顺序)和宿主语言中的定义一致。 * **注释** 为[互操作](#npla-互操作支持),一般应避免和之后的(受实现支持的)\[ISO C++] 版本冲突。   NPLA 实体的[内部表示](#表示)是宿主语言中可表达的数据结构。   NPLA 实体的[外部表示](#表示)是宿主语言中可通过输入/输出操作处理的数据。   除非另行指定,NPLA 使用宿主语言提供的异常作为[异常执行机制](#异常)。   除非另行指定,程序不使用使宿主语言*区域指定的行为(locale-specific behavior)* 改变的特性。 **原理**   默认避免改变区域指定行为简化设计约定。 **注释**   关于类似的对宿主语言程序的要求,另见 YSLib 项目文档 [`doc/LanguageConvention.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/LanguageConvention.txt) 。 ### 附加功能   NPLA 支持*数值(numerical value)* ,但不要求支持具体的数值计算。   NPLA 实现为派生实现提供[数值类型](#数值类型)和相关的操作的基本支持。   除非另行指定,若派生实现支持数值计算,其实现兼容 [NPLA 数学功能](#npla-数学功能)的实现。 ### NPLA 词法和语法   词法分析可接受多字节文本编码的字符串形式的源代码,但不假设其编码中除 0(空字符 NUL )以外的具体代码点被编码的数值,不[转换编码](#实现的执行阶段)。   使用可选的语法预处理和 [NPL-GA 语法](#简单文法约定)。   [字符集](#字符集和字符串)的约定同[宿主环境](#嵌入宿主语言实现)。 ### NPLA 标识符   [NPL 标识符](#基本词法构造)外的以下[词素](#基本词法构造)也是 NPLA 标识符: * 在构成 NPL 标识符的词素中插入有限个非 [NPL 分隔符](#基本词法构造)的字符且不构成 [NPLA 扩展字面量](#npla-扩展字面量)的词素。 * 全由 `+` 或 `-` 构成的词素。 ### NPLA 扩展字面量   NPLA [扩展字面量](#字面量)包括: * 以 `#` 、`+` 或 `-` 起始的但不全是 `+` 或 `-` 构成的、长度大于 1 的词素。 * 十进制数字字符起始的词素(当被支持时)。   全由十进制数字字符的词素表示十进制[数值](#附加功能)。派生实现可定义其它作为数值的词素。这些词素作为字面量时,是*数值字面量(numerical literal)* 。 ### NPLA 名称和字面量求值   名称仅被实现为和[字符串](#字符集和字符串)的[值](#基本语义概念)的一个真子集一一对应的表示(参见[类型映射](#类型映射))。   除非派生实现另行指定,只有[代码字面量](#字面量)不是[自求值表达式](#范式),其余字面量都求值为[右值](#值类别)。   代码字面量求值时解释为名称。   数据字面量是自求值的字符串的[外部表示](#表示)。   数值字面量是自求值的数值的外部表示。   存在不保证先求值的[子表达式](#表达式)的[语法形式](#语法形式)是*特殊形式(special form)* 。   特定的名称是*保留名称(reserved name)* 。   除非另行指定,在[源代码](../Terminology.zh-CN.md#程序设计语言)中使用保留名称作为实体的名称的程序[行为未定义](#npla-未定义行为)。 ### NPLA 求值的表示   [规范形式](#范式)是特定[类型](#类型)的 \[ISO C++] 对象。   [名称解析](#名称解析)失败可被忽略而不[终止](#范式)实现演绎;保证名称表达式求值的[强规范化](#范式)。   不要求提供[命名空间](#命名空间)实现的可变实体。   不保证求值都是[纯求值](#求值性质);非[特殊形式](#npla-名称和字面量求值)使用[热情求值](#λ-求值策略);其它情形使用热情求值或[惰性求值](#λ-求值策略)的方式由具体特殊形式约定。   对象语言的[函数](#函数)默认为[过程](#过程),过程默认实现为[子例程](#过程调用的计算顺序)。过程指定的计算结果和函数表达式[最终求值结果](#范式)的[关联](#过程)是[过程调用](#函数调用)的[结果](#基本语义概念)的恒等映射。 **注释** 即过程调用的结果总是同函数值。   除非另行指定,实现[函数](#函数)的宿主数据结构[生存期要求](#npla-基础存储模型和对象模型)默认同[宿主语言](#实现的执行阶段)。   除非另行指定,[按值传递](#求值策略)支持[复制初始化](#复制初始化和直接初始化)对象的[一等作用](#一等作用)。 **原理**   NPLA 函数不支持类似 \[ISO C++] 的类型退化(decay) 。作为动态类型语言,需要被转换的值在[操作](#规范化中间表示)内部实现,不需要在[返回值](#函数调用)上另行附加转换。   按值传递的复制初始化和宿主语言的对应语义类似。 ### NPLA 类型系统   NPLA 使用[隐式类型](#类型标注)而非[显式类型](#类型标注)。   NPLA 使用[潜在类型](#类型标注):[值](#基本语义概念)具有类型;不指定[动态类型](#类型)以外的[类型](#类型)。   [显式类型(如清单类型)](#类型标注)的机制可由派生实现指定可选地引入。[用户程序](#程序实现)也可能添加类型标注和不同的[类型机制](#类型系统和类型机制)的支持。   除非派生实现另行指定,引入的[静态类型](#类型系统和类型机制)应和[动态类型](#类型系统和类型机制)一一对应。   NPLA 使用和[宿主语言](#实现的执行阶段)相容的[动态类型检查](#类型检查)。除非派生实现另行指定或[类型映射](#类型映射)的需要,使用的类型检查规则和宿主语言一致。   宿主语言对象的值描述状态,且宿主语言要求的对 `volatile` 左值的操作属于[可观察行为](#状态和行为)。 ## NPLA 互操作支持   NPLA 的[宿主语言](#实现的执行阶段)应能提供 NPLA 及派生实现的[本机实现](#实现的执行阶段)。   NPLA 的派生实现提供特定的和宿主语言的[互操作](../Terminology.zh-CN.md#规范)支持,可其中和 NPLA 提供的关于互操作的具体行为不同的部分应由实现定义。 **注释** 对派生实现,NPLA 约定的具体默认互操作特性是可选的。但是,一般的约定如[开放类型系统](#类型映射)仍被要求。   NPLA 和派生实现可约定互操作的具体实现的要求,以确保实现的状态可预测。   本机实现可以具有 C++ 的实现兼容的二进制接口的函数提供,这些函数称为*本机函数(native function)* 。   本机实现可直接支持本机函数在实现中被调用。若被支持,具体接口由派生实现指定。   本机函数作为函数的实现,其调用的求值可具有和非本机的函数一致的[作用](../Terminology.zh-CN.md#程序设计语言),但不需要具有可被对象语言表达的[函数体](#函数)。   为确保函数求值的作用可能保持一致,本机函数应符合和本机函数调用时使用的规约一致的方式使用,即在宿主语言的意义上至少符合以下规约[调用约定](#函数调用): * 被调用时的子项被作为以 [WHNF](#规范化中间表示) 形式表示的被调用的表达式使用。 * 调用后具有项被重写为必要的值以表示[函数调用](#函数调用)的[返回值](#函数调用)。   本机函数的返回值应能表达任意的非本机函数调用的返回值,即通过求值函数调用中函数体的非本机函数的[求值结果](#基本语义概念)。 **原理**   [实体的内部表示和外部表示满足实现环境的要求](#npla-实现环境)允许在宿主语言程序中直接实现关于[表示](#表示)的操作,简化了互操作机制的设计和实现。 **注释**   宿主语言自身的调用约定(通常和实现的 [ISA](#补充领域定义) 相关)作为 C++ 实现自身的 [ABI](../Terminology.zh-CN.md#规范) ,在此是中立的,没有提供特设的支持的要求。   另见 [NPLA 基础存储和对象模型](#npla-基础存储模型和对象模型)。 ### 类型映射   *类型映射(type mapping)* 指定对象语言和宿主语言之间的[实体类型](#实体类型)之间的关系,是前者中的[类型](#类型)到后者中的类型的映射。   作为类型映射目标的宿主语言类型或其[子类型](#类型序)称为*宿主类型(hosted type)* 。   作为宿主语言类型的宿主类型是典型的。其它宿主类型是非典型的。   具有特定[动态类型](#类型)的对象语言的[值](#基本语义概念)在宿主语言具有宿主类型,以宿主语言的值表示,称为*宿主值(hosted value)* 。   在[互操作](#npla-互操作支持)的意义上,宿主值在作为对象语言的值的[表示](#表示)的[项](#基本语义概念)中以*宿主对象(hosted object)* 的形式被保存并可在宿主语言中访问。   对象语言的值被对象语言的实体类型表示蕴含它被映射的宿主类型表示,反之亦然。   类型映射可以是非空的多对一、一对多或一一映射。   若类型映射是一一映射,其类型等价性同宿主语言的语义规则;否则,由类型的语义规则约定。   因需提供与作为宿主语言的 \[ISO C++] 的互操作支持,所以明确约定实现中部分实体类型对应的 C++ 类型: * 用于条件判断的单一值的宿主类型是 `bool` 。 * [字符串](#字符集和字符串)的宿主类型都是 `string` 类型。 * 和字符串的子集一一对应的[词素](#基本词法构造)的宿主类型是能映射到 `string` 的另一种类型。 **注释** `string` 是占位符,不要求是和 [\[ISO C++\] 的 `std::basic_string`](https://eel.is/c++draft/string.classes#basic.string) 相关的类型。但一般地,`string` 类型应具有和 `std::string` 相近的操作以便实现对象语言语义及支持互操作。   推论:字符串和词素可直接比较相等性或排序。   [NPLA 数值](#附加功能)在对象语言中具有[数值类型](#数值类型),具体类型映射未指定,但在 NPLA 数学功能提供可选实现。派生实现可显式扩充或替换定义其它数值类型的类型映射。   其它宿主类型[由实现定义](../Terminology.zh-CN.md#规范)。具体宿主类型参见以下各节和对象语言类型对应的描述。   宿主类型在对应的 C++ [API](../Terminology.zh-CN.md#程序设计语言) 中可能以类型别名的形式引入。 **原理**   [类型系统](#类型系统和类型机制)是[开放](#类型全集)的,可能提供不被对象语言支持的宿主语言类型和值。   但符合已指定的类型的实体需能被视为同种类型的实体使用,即子类型。 **注释**   非典型的宿主类型可以是特定的宿主类型的值的子集,即便这样的类型不被宿主语言的类型系统直接表示。   不被对象语言支持的值的一个例子是实现使用的*中间值(thunked value)* 。   关于中间值、`string` 类型的具体要求、NPLA 数学功能的规格说明和由实现定义的命名空间,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 ## NPLA 未定义行为   一般地,NPLA 规则不排除[未定义行为](../Terminology.zh-CN.md#程序设计语言)。其中,[宿主语言](#实现的执行阶段)的未定义行为是非特定体系结构或其它 \[ISO C++] 意义上不可预测或不可移植的行为。   除非派生实现另行指定,NPLA 约定仅有具有以下情形的程序引入未定义行为: * [互操作](#npla-互操作支持)中引起宿主语言的未定义行为或不满足[约定的要求](#npla-互操作支持)而可能引入派生实现定义的未定义行为。 * **注释** 例如,[没有被支持的并发访问](#npla-并发访问)引起宿主语言的未定义行为。 * [本机实现](#npla-互操作支持)无法提供资源而引起宿主语言的未定义行为(如宿主语言的实现无法提供宿主语言函数调用的自动对象隐式使用的资源)。 * 违反[资源所有权语义](#所有权抽象)约束的操作,包括但不限于: * 违反[内存安全](#内存安全)的操作。 * **注释** 例如,违反按[项对象和关联对象所有权](#项对象和关联对象所有权)的推论不能确保满足[生存期要求](#npla-基础存储模型和对象模型)的操作。 * 除非另行指定,构造任意的循环引用。 * 使用特定的词法构造。   除非派生实现另行指定,NPLA 约定: * 若程序的执行蕴含宿主语言中不保证排除未定义行为的操作,执行可包含宿主语言的未定义行为。 * 否则,非互操作引入的[管理规约](#管理规约)可能存在未定义行为,当且仅当它是[求值规约](#求值规约)的一部分且求值规约可能存在未定义行为。 **原理**   满足[错误条件](#错误)的程序可能[引起错误](#错误),也可引起未定义行为而不要求引起错误。这允许减少实现的复杂性。   对宿主语言的未定义行为的单独处理允许描述互操作。   程序的执行允许宿主语言的未定义行为,同时允许形式上不可靠,但仍可通过宿主的外部环境提供附加保证的实现,而保留可实现性: * 典型地,宿主语言不保证调用的活动记录总是可用。 * 例如,\[ISO C++] 指定程序在自动对象无法分配时具有未定义行为。 * 这种情形形式上无法排除,但不影响实用(否则,任意 \[ISO C++] 程序都是不可移植的)。 * 实现仍应保守使用资源,以尽可能地避免引起宿主语言的未定义行为。 * 通过宿主的外部提供附加保证的实现类似保证为[完整性](#完整性)的前提下通过加入附加的限制来使设计符合要求。   对管理规约的约定同时蕴含对 NPLA 实现的要求。这保证未定义行为不会被任意地在对象语言以外被引入。 **注释**   为简化互操作实现,部分 NPLA 未定义行为可能在实现中被检查以预防(尽可能避免)宿主语言的未定义行为,但这种检查不保证完全覆盖所有引起未定义行为的条件,不应预期其行为可移植。   关于构造循环引用可能引起的问题,另见[内存泄漏](#资源泄漏)。 ### 常规宿主资源分配要求   一般地,本机实现要求资源分配失败时,引起(可能派生)`std::bad_alloc` 或另行指定的宿主异常而非宿主语言的未定义行为;但因为宿主语言缺乏保证,可能并非所有宿主语言实现都能保证实现这项特性。   实际的实现中非极端条件下(如宿主调用栈接近不可用)通常可支持实现这些行为。   宿主语言实现支持时,具有可预期的失败(而 NPLA 或宿主语言的非未定义行为)的 NPLA 实现的要求称为常规宿主资源分配要求。 ### 嵌套调用安全   宿主语言的 [API](../Terminology.zh-CN.md#程序设计语言) 提供*嵌套调用安全(nested call safety)* ,当且仅当:   若调用没有宿主语言无法分配资源的未定义行为,则同时避免因宿主语言的嵌套调用[深度](#过程)过大时引起的这样的未定义行为。   嵌套调用安全应包括支持可能通过对象语言构造的输入使对应宿主语言的操作中的嵌套调用不保证的情形。   对象语言的实现可假定限制避免无限创建[活动记录](#活动记录)即满足嵌套调用安全的要求。 **原理**   嵌套调用安全允许不限制嵌套深度的可靠的调用,如递归调用。   宿主语言实现在宿主语言的[尾上下文](#上下文相关求值)可能支持[宿主 TCO](#tco-实现策略概述) 而使递归调用满足嵌套调用安全,但这并不是语言提供的保证,不应在可移植的实现中依赖。   \[ISO C++] 并没有明确指定关于深度的限制,嵌套调用可能因资源耗尽而引起未定义行为。   严格来说,这种未指定深度是可移植性上的缺陷,因为任意小的深度的调用(甚至深度为 1 的非嵌套调用)都可引起未定义行为而不需要遵循任何 \[ISO C++] 的要求,却仍然满足实现的[符合性](../Terminology.zh-CN.md#非自指)。   \[ISO C] 也有相同的问题。   实际实现中,具体深度限制依赖实现。在宿主语言缺乏保证的状况下,添加附加假定对可实现性是必要的。 **注释**   对应宿主语言的操作中的嵌套调用不保证的情形的主要例子是保证[宿主语言中立](#宿主语言中立)。   非嵌套调用安全的情形在过程嵌套调用深度过大时,可因为宿主语言的存储资源消耗导致的宿主语言实现的未定义行为,典型地包括实现中的*栈溢出(stack overflow)* 。   不限深度的重入不一定引起无限的活动记录的创建:[尾调用](#尾调用和-ptc)应能保证嵌套调用安全。 ### NPLA 并发访问   当前所有 NPLA 实现中都没有显式的[并发](../Terminology.zh-CN.md#计算机科学)访问控制,但可通过互操作引入。 **注释**   一般地,为避免并发访问引起的宿主语言的未定义行为,需要通过本机实现在外部使用不同的资源实例或附加适当的同步。   另见[并发访问安全](#并发访问安全)。 ## NPLA 一等对象类型   除[类型映射](#类型映射),NPLA 约定能作为一等对象的类型支持的抽象的类型,作为实现的最小要求的一部分。   以下章节扩充 NPLA 的其它类型,这些类型中的一部分可能作为一等对象。   基于[开放类型系统](#类型映射),派生实现可定义其它类型,不论是否被[互操作](#npla-互操作支持)支持。 **原理**   这些类型在[求值算法](#求值规约)等规则的描述中适用。 ### 有序对   两个不同对象可作为*元素(element)* 构成*有序对(ordered pair, pair)* 。   有序对的元素是[子对象](#子对象)。   当且仅当若有序对的两个元素不同,交换元素得到的有序对和原有序对不同。 **注释**   一些编程语言中,构造有序对的操作称为 `cons` ,有序对又称为 cons 对。 ### 广义列表   *列表(list)* 一种[类型](#类型),它的对象可能具有[子对象](#子对象)。   *空列表(empty list)* 是不含有子对象的列表。其它列表是*非空(nonempty)* 列表。   每个非空列表是一个[有序对](#有序对)对象,满足: * 有序对对象的第一个元素是列表的元素。 * 若有序对对象的第二个元素是有序对,则这个有序对对象的第一个元素是列表的元素;否则,最后一个不是有序对对象的子对象是列表的元素。 **注释** 推论:同一个列表的元素不是另一个元素的子对象;不同元素之间不具有所有权,生存期不相交。   从非空列表对象中取得元素*分解(decompose)* 列表对象。若经有限次分解,不再可取得列表对象的元素,则列表对象被完全分解。   完全分解的列表的最后一个元素之外的其它元素是列表的*前缀(prefix)* 元素。   对象具有前缀元素,当且仅当对象是列表且具有前缀元素。   *真列表(proper list)* 是空列表,或能经完全分解得到最后元素是空列表的列表。其它列表是*非真列表(improper list)* 。 **注释** 推论:非真列表是非空列表。   *广义列表(generalized list)* 是真列表或非真列表。   广义列表的元素是[一等对象](#基本语义概念)。广义列表对元素具有所有权。   广义列表是完全分解的元素的*序列(sequence)* 。   作为广义列表的非真列表是*无环的(acyclic)* ,不包含*环(cycle)* 。 **注释** 同一般的 NPL 约定,NPLA 对象不支持[自引用和循环数据结构](#自引用数据结构和循环引用)。   除非另行指定,以下列表指真列表。   *子有序对(subpair)* 是一个有序对完全分解的序列中的元素的真子集构成的子对象。   *子列表(sublist)* 是一个列表中的元素的真子集构成的列表子对象。 **注释**   无环非真列表和真列表类似,可通过 `cons` 逐次构造。   非列表的有序对的元素可能具有自引用,而不是广义列表的元素,因此不是广义列表。NPLA 的一等对象不支持这种情形。 ### 符号   [*符号(symbol)*](../Terminology.zh-CN.md#程序设计语言) 是未被求值的非[字面量](#字面量)[记号](#基本词法构造)的类型。   符号值可构成[名称表达式](#λ-完备语义和对应语法)。 ## 存储和对象模型   NPLA 使用统一的模型对存储和[对象](#基本语义概念)进行抽象,并提供关于存储、对象和作为对象的[表示](#表示)的[项](#基本语义概念)以及[子项](#基本语义概念)的若干保证。   对象语言的存储被视为资源进行管理,称为*存储资源(memory resource)* 。 **原理**   语言中[默认不引入非一等对象](#一等实体和一等对象)。因此,存储和对象模型作用到所有实体,有助于保持[简单性](#简单性)。 **注释**   一等对象的使用可能受到其它规则的限制,不总是能同时通过对象语言的构造创建和访问。   NPL [允许派生实现引入实体的规则](#一等实体和一等对象)不受限制。 ### NPLA 基础存储模型和对象模型   因需提供[宿主语言](#实现的执行阶段)[互操作支持](#npla-互操作支持),除不支持*静态(static)* 存储和没有提供支持的存储操作外,NPLA 的基础存储模型和对象模型和 \[ISO C++11] 相同。   当前不支持的存储操作包括*分配函数(allocation function)* 取得的存储和*线程局部(thread-local)* 存储。   NPLA 还允许类似对象具有未指定的存储或不需要存储的实体,以使[一等实体](#实体语义)可涵盖宿主语言在功能上等价的非对象类型(如 C++ 的引用)。这些实体若被支持,其存储实现和互操作接口由派生实现定义。   NPLA 中不是[一等对象](#基本语义概念)的[一等实体](#一等实体和一等对象)仅由派生实现定义。   保证存储性质的差异不被依赖时,不区分一等实体和一等对象的实现方式。   在此情况下对象都是*固定(pinned)* 的,即对象在*存储期(storage duration)* 内具有宿主语言意义上的确定不变的地址。派生实现可约定扩展作为例外。   推论:若一等实体不是一等对象,存储可能和一等对象的存储方式不同。派生实现可在必要时约定与其它一等实体存储的差异。   对象的[生存期](#基本语义概念)是存储期的子集。创建对象基于已确保可访问的存储;销毁对象结束后释放存储。   NPLA [支持特定的非一等对象](#存储和对象模型)作为[引用值](#引用值)的[被引用对象](#一等引用)。 **注释** 和宿主语言类似。   作为一等对象相同方式传递的一等实体都视为一等对象。仅当不依赖一等对象的性质时,实现以[非一等对象](#一等实体和一等对象)的方式实现一等实体的操作。 **原理**   [实体的内部表示满足实现环境的要求](#npla-实现环境)决定和 NPLA 和宿主语言之间共享一些基本的假定。 ### 间接值   特定的[值](#基本语义概念)是*间接值(indirect value)* 。   间接值可以*关联(associated)* 一个对象。通过间接值可以间接[访问](#基本语义概念)这个对象。   间接值可能是[一等对象](#基本语义概念)或[非一等对象](#一等实体和一等对象)。   非一等对象的间接值由实现定义,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。   派生实现可以定义其它间接值,称为 NPLA 扩展间接值。   一个间接值*有效(valid)* ,当且仅当存在关联的对象且访问对象不引起[未定义行为](../Terminology.zh-CN.md#程序设计语言)。   其它间接值是*无效(invalid)* 的。   除非另行指定,通过无效的间接值试图间接访问关联的对象不满足[内存安全](#内存安全)而引起未定义行为。   有效的引用值可能被*无效化(invalidate)* 而不再有效。   派生实现可指定能使间接值无效化的操作。   因关联的对象[存储期](#npla-基础存储模型和对象模型)结束而被无效化的间接值是*悬空(dangling)* 的。 **原理**   间接值可用于代替非间接值,避免求值时改变[环境](#环境对象)所有的非[临时对象](#临时对象)的[所有权](#求值和对象所有权)。   间接值可实现和 \[ISO C++] 引用类型的表达式类似的行为。   间接访问默认没有对象的[生存期](#基本语义概念)检查,因此不是安全的。这可能被具体的间接值的规则改变。   限制具体的操作能避免或减少在可能访问间接值的操作随意引入具有潜在未定义行为风险。 **注释**   作为一等对象的间接值可能允许复制或转移关联的对象以恢复对应的非间接值作为一等对象直接访问。   在[使用约定](#间接值使用约定)后,本节以下约定要求被 NPLA 实现支持作为一等对象的间接值。非一等对象的间接值由实现定义。派生实现可以定义其它的 NPLA 扩展间接值。 #### 间接值使用约定   间接值生存期规则:被规约对象中间接值的生存期被引用的环境中的对象的生存期的子集。   不满足间接值生存期规则的情形,除非提供派生实现定义的其它保证,不保证[内存安全](#内存安全)。   以含间接值的项替代不含间接值的项,称为*引入(introduce)* 间接值。   包含间接值的项可被不含引用值的项替代,称为*消除(eliminate)* 间接值。   在特定的适当情形下实现应复制或转移间接值关联的对象以保证[满足生存期要求](#项对象和关联对象所有权),包括: * [局部间接值安全保证](#局部间接值安全保证)描述的操作,包括[返回值转换](#返回值转换)。 * 为[互操作](#npla-互操作支持)目的,实现定义的其它情形。 * 派生实现可能定义的其它情形。   除非另行指定[引起错误](#错误),若不能满足上述适当情形条件,则[行为未定义](#npla-未定义行为)。   派生实现可基于本节约定其它规则。 **原理**   为保证间接访问关联对象的内存安全,约定间接值生存期规则。   参见局部间接值安全保证和返回值转换。 **注释**   如需直接[替换项](#项的子对象)表示的值,需消除间接值。否则,没有必要提前对项进行操作以提前移除间接值。   关于实现定义和派生实现定义的其它情形,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。   另见被求值的[被规约项中的对象的所有权](#项对象和关联对象所有权)。 #### 环境间接值   [环境引用](#环境引用)间接访问[环境对象](#环境对象)。 #### 引用间接值   *项引用(term reference)* 作为间接值引用一个项,访问这个以这个项作为表示的[被引用对象](#一等引用)作为关联对象。   项引用具有[标签](#对象属性)。 ### 求值和对象所有权   被求值的表达式的[内部表示](#表示)中的对象具有 NPLA 对象的[所有权](#所有权抽象)。   这些内部表示包括[环境对象](#环境对象)或[被求值的表达式中的项](#npla-表达式语义)的情形。   对象是表示它的[被规约项](#求值规约)的*项对象(term object)* 。   [NPLA 临时对象](#临时对象)的存储未指定,但[部分临时对象被项所有](#项对象和关联对象所有权)。   求值结束而不被使用的项的资源在求值终止时被释放,包括被项独占所有权的这些临时对象。   求值终止包括可被实现确定的[异常](#异常)退出。   对名义上被项所有的临时对象,必要时实现可分配内部存储转移项(包括在环境中分配),以满足附加要求(如[生存期附加约定](#生存期附加约定))。   对象的所有权随可随对象被转移,参见[对象的复制和转移](#对象的复制和转移)。   [求值结果](#基本语义概念)可以是: * 作为[值计算](#基本语义概念)的结果的[一等对象](#基本语义概念),称为*结果对象(result object)* 。 * 传递异常的状态的实体。 * 派生实现可定义的其它实体。 **注释** 按[求值规约](#求值规约),其它的求值结果的存在未指定,若存在则可能需要其它处理,可能依赖和处理一等对象的值不同的语义规则。   [函数调用](#函数调用)时以[活动记录](#活动记录)保持[被引用对象](#一等引用)的所有权。活动记录及其帧的具体结构、维护方式和生存期由派生实现定义。   除非另行指定,NPLA 只有一种[作用域](#函数和函数应用的求值环境),这种作用域中的名称由[环境](#npla-环境)提供。   除非另行指定,NPLA 的活动记录不需要和[宿主语言](#实现的执行阶段)的结构保证直接对应关系。 **原理**   因为宿主语言函数调用实现(典型地,*调用栈(call stack)* 及其中的栈帧)不提供可移植的[互操作](#npla-互操作支持),不要求实现提供活动记录之间的映射关系。 **注释**   临时对象的存储未指定、异常退出和所有权转移类似宿主语言。   结果对象和 \[ISO C++17](由提案 \[WG21 P0135R1] 引入)中的概念对应。   另见[环境对象](#环境对象)和[环境引用](#环境引用)对其中的对象的所有权。 #### 项对象和关联对象所有权   仅在[泛左值](#值类别)中允许引入可能访问关联对象的间接值。   推论:泛左值的项对象和它作为间接值可关联的对象(若存在)不是临时对象,被环境所有。   通常纯右值作为其它项的子项而被独占所有权,求值时可能通过[临时对象实质化转换](#值类别转换)标识创建的[临时对象](#临时对象)。   [表示临时对象的项](#临时对象的表示)被纯右值所有,也间接被其它项所有。   特定的纯右值可能被[环境](#npla-环境)所有,但应只通过复制等方式访问其值而不依赖所有权关系。   关于实现中项的[宿主类型](#类型映射)和构成以及纯右值被环境所有的例子,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   基于[间接值](#间接值)的性质,为保证[内存安全](#内存安全),避免非预期地超出存储期的间接值访问,限制引入间接值的表达式的[值类别](#值类别)。   因[临时对象可能具有和一等对象不同的表示](#临时对象的表示),在此特设规则约定。 ### 并发访问安全   蕴含按[抽象机语义](#实现行为)不等价副作用的并发的访问是*冲突的(conflict)* 。   不共享相同的[控制状态](#基本语义概念)的[无序](#规约顺序)的[规约事件](#规约顺序)是*潜在并发的(potentially concurrent)* 。   若程序包含[蕴含](#作用使用原则)冲突的作用的潜在并发的求值,且这些求值之间没有附加的*数据竞争避免(data race avoidence)* 保证,程序的执行包含*数据竞争(data race)* ,不满足并发访问的内存安全。其中,以下机制数据竞争避免保证: * 所有潜在并发的求值都是宿主实现提供的*原子操作(atomic operation)* 时,避免数据竞争。 * 派生实现另行指定的数据竞争避免机制。   并发访问相关的概念和 \[ISO C++11] 相容。 ### 内存安全   (非并发)*内存安全(memory safety)* 是存储资源避免特定类型不可预测错误使用的性质。   基本的内存安全保证蕴含非[并发访问](#npla-并发访问)时不引入[未定义行为](#npla-未定义行为)。这至少满足: * 对存储的[访问](#基本语义概念)总是在提供存储的对象的存储期内,除非有其它另行指定的机制(如[宿主环境](#嵌入宿主语言实现)的[互操作](#npla-互操作支持))保证存储的访问不违反其它语义规则。 * 宿主环境中不访问未被初始化的值。 **注释** 实现仍可能因其它规则引起未定义行为;特别地,这包括[本机实现](#npla-互操作支持)无法提供资源的未定义行为。   派生实现可能扩展内存安全,提供语言规则避免非预期的内存访问错误,提供更一般的高级*安全(security)* 保证。 **注释** 例如,*保密性(secrecy)* 和*完整性(integrity)* 。   除非另行指定,派生实现不提供扩展的内存安全保证。   不满足[并发访问安全](#并发访问安全)的访问是非内存安全的。 **原理**   关于内存安全含义的讨论,另见[这里](https://arxiv.org/abs/1705.07354)。 **注释**   用户代码应注意避免违反内存安全的访问,包括非并发的,以及并发访问的内存冲突。 #### 非内存安全操作   非内存安全操作是不保证内存安全的操作,在对象语言中即可能引起违反内存安全。   这些操作违反内存安全时,引起 [NPLA 未定义行为](#npla-未定义行为),且可能未被实现检查而同时引起[宿主语言](#实现的执行阶段)的未定义行为。   对象语言中的非内存安全特性可能直接调用这些操作。NPLA 外依赖此类操作的其它操作也具有类似的性质。 **注释**   派生实现或[用户程序](#程序实现)可能使用补充检查等方式避免未定义行为。 #### NPLA 对象语言内存安全保证   NPLA 中,确定地引入具有[非内存安全操作](#非内存安全操作)的对象的操作应仅只包括引入特定的[间接值](#间接值)或其它派生实现指定类型的值的操作: * 调用引入不保证内存安全的间接值的 NPLA [API](../Terminology.zh-CN.md#程序设计语言) 。 * 调用 NPLA 中其它取[对象](#基本语义概念)的[内部表示](#表示)的值的间接值使之被[修改](#基本语义概念)的 API 。   排除非内存安全操作以及非内存安全的本机实现,NPLA 实现的对象语言提供基本内存安全保证。 #### NPLA 内存安全保证   满足 [NPLA 对象语言内存安全保证](#npla-对象语言内存安全保证)同时排除引起宿主语言未定义行为的非内存安全的操作,NPLA 实现提供基本内存安全保证。 **注释** 宿主语言未定义行为的非内存安全的操作如超出[生存期](#基本语义概念)的[访问](#基本语义概念)。   除非通过接口约束另行指定,使用 NPLA 实现的派生实现应提供相同的保证。 **注释** 例如,添加[断言检查](#运行时内存安全检查)可能改变[实现行为](#实现行为)。 #### 运行时内存安全检查   [运行时](#实现的执行阶段)检查可能帮助排查内存安全的[实现行为](#实现行为)。这包括蕴含运行时检查的接口约束(失败时抛出异常或断言)。   此外,实现可能提供可选的运行时检查。这些可选的检查帮助排查未定义行为,而不应被程序实现依赖。 #### 局部间接值安全保证   访问间接值涉及维护[内存安全保证](#内存安全)时,可能需要[提升项](#项的子对象)[消除间接值](#间接值使用约定),以移除允许[非内存安全访问](#非内存安全操作)的间接值。 **原理**   使用[删除策略](#资源回收策略)实现[过程调用](#函数调用)时,其中分配的*局部(local)* 资源随包含资源引用的[引用](#一等引用)返回可能[逃逸](#函数调用)。一般的间接值也有类似的逃逸问题。   若其[关联](#间接值)的对象(如[项引用](#引用间接值)关联的[被引用对象](#一等引用))在调用后不再存在,则间接值不再有效,构成[悬空间接值](#间接值)。若这些间接值被[调用者](#过程)获取(如被作为[返回值](#函数调用)传递),继续[访问](#基本语义概念)这个间接值关联的对象非内存安全。   为维护内存安全保证,这些情形应被避免,如通过: * 通过分析调用处的代码证明确保不存在这样的内存不安全访问。 * 通过间接值的消除移除这些间接值使这种悬空间接值在调用者中自始不存在。   替代消除间接值的方式包括通过*逃逸分析(escape analysis)* 替换间接值,这也能减少间接值的访问而提供更优化的实现。例如,通过对[环境中被绑定对象的使用](#环境引用)进行逃逸分析提供优化实现。   但是,这不在 NPLA 中被要求,因为: * 逃逸分析需要完整的所有权信息,这需要附加的开销,否则不总是可行(例如涉及跨多个过程的调用)。 * 对删除策略,逃逸分析也没有提供不可替代的优化。 ### 资源泄漏   *资源泄漏(resource leak)* 是不能预期地(决定性地)访问之前被分配的资源的情形。   *内存泄漏(memory leak)* 是存储资源的泄漏。   强内存泄漏状态是指存在存储无法通过任何途径访问的状态。若存在存储不被任意对象或其它另行指定的代替对象的实体(如[宿主环境](#嵌入宿主语言实现))所有权的传递闭包包含,即所有权依赖*不可达(unreachable)* ,则存在强内存泄漏。   弱内存泄漏是除了强内存泄漏以外的内存泄漏,和具体预期相关。 **原理**   一般意义下,\[Cl98] 中定义的任一空间复杂度类都可以作为形式的预期。因为内存作为存储资源被空间复杂度类度量,满足某个空间复杂度类的无*空间泄漏(space leak)* 蕴含对应的无内存泄漏。   弱内存泄漏的预期的可实现性和实现细节相关,因此 NPLA 不指定具体预期。 #### 资源回收策略   [单一作用域](#求值和对象所有权)内的资源回收有*删除(deletion)* 和*保留(retention)* 的策略。   NPLA 不限定具体使用的回收策略,但要求应支持: * [释放一等对象时允许具有副作用](#一等状态)。 * 确保副作用作用时机的确定性,即除[用户程序](#程序实现)指定外,不在[抽象机语义](#实现行为)中延迟副作用的起始。   为简化语义规则同时避免限制特定的可用资源(如系统中剩余的内存)的变化被派生实现抽象为副作用,除非派生实现指定,不对内存使用保留策略,不使内存超出对象[生存期](#基本语义概念)。   NPLA 要求实现完全避免除用户程序显式管理资源的资源泄漏以外的强内存泄漏。   除非另行指定,NPLA 释放资源的作用顺序未指定。NPLA 不依赖释放的[作用](#基本语义概念)的顺序。   派生实现可以要求使用不同的规则: * 指定释放资源的顺序。 * 可选地支持非确定的释放资源的副作用。   NPLA释放可能具有的副作用顺序的存储资源和其它资源共享更普遍的所有权抽象资源的[所有权语义](#所有权抽象)上的操作:   使用删除策略时,[活动的过程调用](#函数调用)对其中分配的资源具有所有权。   注意多个对象构成的系统中,仅存在[平等的所有权](#所有权抽象)时的[循环引用](#自引用数据结构和循环引用)造成强内存泄漏:除非即从循环引用的对象中区分出具有不同类所有权的对象子集实现所有权正规化,总是存在无法被释放资源的对象。   NPLA 不要求实现 GC 等机制避免这类循环引用。   关于循环引用避免,另见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   NPLA 不要求实现 [GC](#所有权抽象)。   未指定的资源释放的作用顺序使其中可能具有的副作用影响的可观察行为成为[未指定行为](../Terminology.zh-CN.md#程序设计语言)。   除非派生实现要求使用不同的规则支持非确定的资源的副作用,NPLA 的实现不依赖不保证确定性释放资源的副作用顺序的*追踪(tracing)* GC 。这使追踪 GC 可能被可选地添加(opted-in) 到实现支持特性中。这允许自动资源管理机制中一定程度的[变化的自由](#变化的自由)。\[ISO C++11] 起直至 \[ISO C++20] ,C++ 语言规则支持类似的策略。   资源释放副作用的确定性要求和作用顺序未指定的规则不影响实现使用基于引用计数的 GC 策略。这允许实现以简单的方式以用户程序不直接可见的方式引入共享资源,在避免资源泄漏的意义上兼顾[正确性](#正确性)和[简单性](#简单性)。但为[避免单一所有者](#所有权抽象),此时在对象语言应提供特性使用户程序可以创建隔离共享者的资源实体。   基于非预期的循环引用不可避免地造成实现开销而违反[避免不必要付出的代价](#避免不必要付出的代价)(即使这种开销可能并不总是可观察)NPLA 不要求实现 GC 和对一般对象区分强弱引用等机制避免循环引用。此时,程序应自行避免所有权意义上的[循环引用](#自引用数据结构和循环引用)以避免资源泄漏。   由于 GC 通常基于具有特定操作的单一资源所有权的所有者的对象池的这一实现特例,不依赖共享所有者的 GC 的设计一般也更容易满足[统一性](#统一性)、[最小接口原则](#最小接口原则)和[关注点分离原则](#关注点分离原则)。   以上规则允许程序中: * 不依赖释放可能具有的副作用顺序的资源。 * 使存储资源和其它资源共享基于更普遍的[所有权抽象](#所有权抽象)的资源所有权语义的操作的[作用](#一等作用),以一致的方式实现资源管理。   关于不同的资源回收策略(其中一部分可能引起存储空间资源泄漏)的讨论,详见 \[Cl98] 。   使用所有权抽象活动记录的资源能更好地满足资源管理机制和具体操作的[可复用性](#可复用性)和[作用使用原则](#作用使用原则)的要求。 #### 资源回收安全性   派生实现可补充定义规则在资源回收的作用上提供更强的安全保证。 **原理**   内存泄漏是和[内存安全](#内存安全)不同的另一类非预期的问题,表明语言设计、实现或程序存在缺陷。   即便不违反内存安全保证,涉及弱化空间复杂度类预期的内存泄漏仍可损害程序的[可用性](#可用性)而引起安全(security) 问题。   内存泄漏和违反内存安全同属违反特定的存储访问不变量的*错误条件(error condition)* ,但因为不论在语言还是程序的设计和实现中,避免的机制相当不同,在此被区分对待。   即便不扩展规则提供更强的内存安全保证,仅在资源回收的作用上避免错误条件也是有意义的。   存在其它语言使用类似的区分内存泄漏和[非内存安全](#非内存安全操作)的设计,如 \[Rust](详见[相关文档](https://doc.rust-lang.org/book/second-edition/ch15-06-reference-cycles.html))。 ### 子对象   对象的[子实体](#实体数据结构)是对象时,子实体是对象的*子对象(subobject)* 。   除非另行指定,子对象及其性质同宿主语言的约定:在宿主语言的表示中表现为子对象的对象语言中的对象,也是对象语言的子对象。   对象语言的其它具有子对象的情形由派生实现定义。   对象对它的子对象具有平凡的[所有权](#所有权抽象)。   对象的子对象的[生存期](#基本语义概念)不[先序](#规约顺序)对象的生存期起始,对象的子对象的生存期结束不[后序](#规约顺序)对象的生存期结束。   对象的子对象的生存期起始后序对象的生存期起始,对象的子对象的生存期结束先序对象的生存期结束。   除非另行指定,同一个的对象不同子对象的存储期起始、存储期结束、生存期起始、生存期结束之间分别无序。   对象对其存储期和生存期的其它约束和宿主语言相同。   对象可通过[子对象引用](#子对象引用)关联和与其生存期相关或无关的其它对象。   通过子对象访问的[被引用对象](#一等引用)上的[副作用](#状态和行为)是否蕴含对象上的副作用未指定。   关于内部对象,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   子对象不一定支持[可修改](#实体的不可变性)的[一等状态](#一等状态)。修改子对象可能导致或不导致对象或先前通过相同方式取得的子对象的[改变](#实体的不可变性)。   \[ISO C++] 通过类型定义具有的隐含的对象布局共享同类对象的[内部表示](#表示)。与之不同,为简化[非一等对象](#一等实体和一等对象)表示的项上的操作,子对象之间不一定共享表示。   特别地,通过[子对象引用项](#引用值的表示)访问的对象的子对象之间不一定具有[同一性](#基本语义概念)。   关于具体表示,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **注释**   作为支持子对象作为内部对象的逻辑前提,NPLA 不支持[循环引用](#自引用数据结构和循环引用)。   \[ISO C++] 允许 `const` 成员提供不支持修改的状态。[NPLA 不要求类似的类型系统支持](#npla-类型系统),没有类似的设计。 #### 项的子对象   作为对象的子项是[项对象](#求值和对象所有权)的子对象。   因为子项可以递归地具有子项,项对象作为[数据结构](#基本语义概念)构成*树(tree)* 。项对象是树的节点,即*项节点(term node)* 。   项节点具有如下互斥的基本分类: * *枝节点(branch node)* 或*非叶节点(non-leaf node)* :具有子节点的节点。 * *叶节点(leaf node)* :不具有子节点的节点。   除子项外,项具有*值数据成员(value data member)* 作为其子对象。   表示项对象的[被规约项](#求值规约)的值数据成员提供时间复杂度为 O(1) 的操作判断: * 项对象是否为[有序对](#广义列表)。 * 项对象是否为[真列表](#广义列表)。 * 非有序对的项对象具有的[宿主类型](#类型映射)。   值数据成员可能具有空值。   值数据成员和子项可构成[对象的内部表示](#表示): * *列表节点(list node)* 是值数据成员为空的节点,表示真列表。 * *空节点(empty node)* 同时是叶节点和列表节点,表示[空列表](#广义列表)。 * 实现可定义其它的节点作为其它的内部表示。   若项存在其它子对象,作为对象内部表示的具体规则由实现定义。   满足以下条件的替换变换替代项或其子对象,称为项的*提升(lifting)* :被提升的项(源)是提升后得到的项(目标)的一个直接、间接子项或项的子对象变换得到的项。   提升可能包含附加检查,检查失败时可能[引起错误](#错误)而不实际进行提升。   除非另行指定,提升项[修改](#基本语义概念)被替换的对象。 **原理**   项的子对象确定的表示可能被具体的[互操作](#npla-互操作支持)依赖。   项的提升可以视为作为语法变换的消去 [λ 抽象](#λ-完备语义和对应语法)的 [lambda 提升 (en-US)](https://en.wikipedia.org/wiki/Lambda_lifting) 的一般化,但此处和 λ 抽象没有直接关联。   项的提升的变换可以是恒等变换,即直接以子对象作为替换的来源。其它变换如创建[间接值](#间接值)和取间接值关联的对象,对应的提升[引入和消除间接值](#间接值使用约定)。   项的提升的检查可包括为满足接口行为的[语义检查](#实现的执行阶段)和实现为预防[宿主语言的未定义行为](#npla-未定义行为)的附加检查。   被提升的项往往被[转移](#项的转移),因此一般地,需要在宿主语言中[可修改](#实体的不可变性)。若被提升的项表示对象语言的值,一般也需要在对象语言中可修改。 ### 对象属性   除以上性质外,对象可关联其它元数据以指定对象的[属性](#基本语义概念)。   和属性对应的可组成对象的表示的非一等实体统称为*标签(tag)* 。   对象具有的标签决定以下[正交](#正交性)的性质: * 唯一(unique) 引用:指定[对象的值](#基本语义概念)关联到自身以外的不被其它对象[别名](#对象别名)的对象。 * 以唯一引用关联的对象进行[复制初始化](#复制初始化和直接初始化)时,不需要保留关联的对象的值。 * 不可修改(nonmodifying) :指定对象的值保持不变。 * 若操作需要修改此对象,则[引起错误](#错误)。 * 临时(temporary) 对象:指定对象的值被临时使用。   唯一引用和不可修改是[引用值](#引用值)的属性。对象语言中,引用值以外的对象是否具有这些属性未指定。为[互操作](#npla-互操作支持)目的可能具有实现定义的更强的假设。派生实现也可定义更强的假设。   临时对象属性类似唯一引用,但限定的可以是对象自身而非关联的其它对象,即引用值自身和[被引用对象](#一等引用)可以分别具有临时对象属性。但除了引用值属性外,临时对象属性仅限在[临时对象](#临时对象)上出现。 **注释**   不可修改的对象类似 \[ISO C++] 的 `const` 类型的对象。\[ISO C++] 的非类和非数组类型的对象不具有 `const` 修饰。   对象的标签不在大多数对象中可见。另见[引用值的属性](#引用值的属性)。 ## NPLA 环境   [求值环境](#求值环境)维护[名称](#名称)和作用域。   *变量名(variable name)* 即[变量](#基本语义概念)的[名称](#名称)。   除非另行指定,环境维护的名称都是变量名。   NPLA 的求值环境可以是: * *一等环境(first-class environment)* ,即作为对象语言中的[一等对象](#一等实体和一等对象)的环境。 * 作为 NPLA [非一等对象](#一等实体和一等对象)的*环境记录(environment record)* 。   环境可引用若干个关联的其它环境为*父环境(parent environment)* ,用于[重定向](#名称解析)。   除非派生实现另行指定: * 环境可引用有限多个父环境,其数量的上限未指定。 * 父环境在创建时指定,作为[实体](#基本语义概念),之后[不可变](#实体的不可变性)。 **原理**   如[需求](#需求概述)中指出的,本设计[避免命名空间隔离](https://github.com/FrankHB/pl-docs/blob/master/en-US/calling-for-language-features.md#namespace-separation),因此只有一种被环境支持且被[求值算法](#求值规约)统一处理的名称。   若派生实现需要,可修改环境的内部表示和求值算法的[名称解析](#名称解析)步骤以对不同的名称添加支持。相反,在已有多种名称的设计中还原为一种设计是更复杂和不可行的。因此,在本设计中不预设多种名称。 ### 环境对象   环境作为可保持[可变状态](../Terminology.zh-CN.md#非自指)的对象,是*环境对象(environment object)* 。   环境对象包含变量名到表示[被绑定实体](#求值环境)的映射,称为*名称绑定映射(name binding map)* ,实现[变量绑定](#基本语义概念)。   被绑定实体是对象时,称为*被绑定对象(bound object)* 。NPLA 环境对象中的被绑定实体包含一等对象,因此被绑定实体总是被绑定对象。   环境对象对其中的[名称绑定映射](#环境对象)具有独占的[所有权](#所有权抽象)。名称绑定映射对其中的对象可具有独占或共享的所有权。因此,环境对象可对包括被绑定实体的名称绑定映射中的对象具有独占或共享的所有权。   环境记录之间共享所有权,以[环境引用](#环境引用)访问。   环境对象是[名称解析](#名称解析)时查找名称的[目标](#名称解析)。   父环境可共享环境记录。通过共享环境记录实现重定向的环境表示是*链接的(linked)* 而非*平坦的(flat)* 。 **原理**   仅在可证明符合语义要求等价时,使用平坦的环境表示。   对支持一等对象语义的设计,因为明确要求区分[同一性](#基本语义概念),对象的[存储](#表示)不能被任意地复制。   一般地,仅在可证明父环境对应的环境记录在对象语言和实现内部都不被共享访问(不具有[共享引用](#共享引用)且不被[别名](#对象别名)),且不存在任意派生实现定义的对释放顺序引起的可观察行为差异时,才能唯一具有这个父环境的环境为平坦的表示而[保持语义不变](#状态和行为)。 **注释**   变量名通过以和字符串一一对应的值表示,没有直接的值的限制,可能为空串。   若环境记录直接持有[被引用对象](#一等引用),则这些对象是环境记录的[子对象](#子对象)。 ### 环境引用   环境引用是对象语言中访问环境记录的[一等对象](#一等实体和一等对象)。 **注释** 环境引用不是[引用值](#引用值)。后者关联的被引用对象是一等对象。   环境引用共享环境对象的[所有权](#所有权抽象)。   根据所有权管理机制的不同,环境引用包括[环境强引用](#一般引用数据结构的一般实现)和[环境弱引用](#一般引用数据结构的一般实现)。   [环境强引用](#环境引用)可能共享环境对象的所有权,对[环境对象的名称绑定映射](#环境对象)持有的项具有间接的所有权。   作为[间接值](#间接值),环境引用可被复制或转移。   复制或转移环境引用不引起被引用的环境对象被复制。因此,[按值传递](#求值策略)环境引用不引起其中所有的对象被复制。另见[引用](#一等引用)。 **原理**   区分环境对象和环境引用在纯函数式语言不是必要的,因为不需要关心[环境中的子对象](#环境对象)的复制影响[可观察行为](#状态和行为)。   否则,为支持影响可观察行为的环境的[修改](#基本语义概念),非环境记录的环境引用是必要的。   环境引用也是一种较简单且一般普遍高效的父环境的实现表示,可直接实现[链接的](#环境对象)环境而不需要证明和实现特设的其它内部表示能和[抽象机](#实现行为)意义上链接的环境保持语义等价。   [续延捕获](#续延的捕获和调用)若复制续延,可能引起关联的环境的复制,影响可观察行为并引起不必要的实现开销。为此,区分环境引用是必要的。   以环境引用作为一等对象使访问[被引用对象](#一等引用)等环境记录的子对象时需要间接访问,在环境实际不需要被复制的大部分其它场景引起开销。这种开销是可接受的,因为: * 考虑到一等环境的普遍性,有必要有效支持对象语言中创建环境临时对象(而不仅仅是环境对象的[引用值](#引用值))的使用使之避免复制。 * 实现可能提供附加的证明以在优化的[翻译](#实现的执行阶段)过程中替换环境引用为环境记录或其它不需要间接访问的中间表示,以消除这些开销。   不论这样的证明是否存在,环境强引用和弱引用仍在对象语言中区分,以明确接口上的[所有权语义](#所有权抽象)。   引入环境弱引用作为一般的引用机制,且仅在必要时使用环境强引用,以避免过于容易[引入循环引用引起强内存泄漏](#资源回收策略),符合[适用性](#适用性)。 ### 当前环境   NPLA 对象语言中,表达式的求值隐含对应一个环境对象作为[求值算法](#求值规约)需要的[上下文](#上下文相关求值)输入,称为*当前环境(current environment)* 。 ## NPLA 表达式语义   本节约定对象语言中的表达式相关的语义规则,特别是[求值规则](#求值规约)。   [列表表达式](#列表表达式)作为[一等对象](#基本语义概念)是[列表](#广义列表)。 ### 值类别   [表达式](#表达式)归类为具有以下基本的*值类别(value category)* 之一: * *泛左值(glvalue)* :求值用于决定被表示的对象的[同一性](#基本语义概念)的表达式。 * *纯右值(prvalue)* :求值不用于决定对象同一性(而仅用于[初始化](#初始化)[临时对象](#临时对象)或计算对象中[存储的值](#表示))的表达式。   一个泛左值可能被标记为*消亡值(xvalue)* ,以提供基于不同的所有权的行为。   纯右值蕴含对象在[可观察行为](#状态和行为)的意义上不被共享,类似不被别名的引用的[被引用对象](#一等引用)不被[共享](#对象别名)。   *左值(lvalue)* 是除了消亡值外的泛左值。   *右值(rvalue)* 是消亡值或纯右值。   基本的值类别、消亡值、左值和右值都是值类别。   求值涉及表达式的值类别仅在必要时约定。   表达式的值类别是上下文相关的,相同表达式构造在不同的上下文可能具有不同的值类别。   NPLA 表达式允许在源语言语法之外的形式被间接构造,这些表达式同样具有值类别。   [求值规约](#求值规约)可能重写一个表达式为具有不同值类别的为被规约项。即便不能被对象语言表达,只要不和其它语义规则冲突,它们在此被视为其它形式的表达式的表示,即[项对象](#求值和对象所有权)也对应地具有值类别。   一般地,NPLA 的表达式不限定从[源代码](#表示)的[翻译](#实现的执行阶段)确定,且一个表达式的求值结果不排除继续构成表达式而被求值,因此表达式的值也普遍具有值类别。   除非另行指定,若一个 NPLA 表达式没有指定未被求值,则其值类别是其[求值结果](#基本语义概念)的值类别。 **原理**   值类别根据是否只关心表达式关联的(对象的或非对象的)值,在需要对象时提供区分[两类一等实体](#一等实体和一等对象)的机制,同时避免在仅需要表达式关联的值时引入不必要的其它对象。 **注释**   对象语言表达式的值类别和 \[ISO C++17](由提案 \[WG21 P0135R1] 引入的特性)类似。   值类别在 \[ISO C++] 中实质上是一种[静态类型](#类型系统和类型机制)系统。在 NPLA 中以更灵活的可在[运行时](#实现的执行阶段)访问的元数据代替,仍能体现类似的上下文相关性。   除了标记消亡值,附加其它元数据也允许区分不同的所有权行为。   NPLA 值类别和 \[ISO C++] 也有显著的不同,体现在如下扩展:源语言语法外的[被规约项](#求值规约)的项对象视为 NPLA 表达式,也具有值类别。   因此,作为[求值结果](#基本语义概念)的表达式的值也普遍具有值类别。若存在[结果对象](#求值和对象所有权),可直接[通过其类型确定](#引用值的子类型)。   作为静态语言,\[ISO C++] 缺乏允许在运行时确定的[求值](#基本语义概念)特性,这些不同不在 \[ISO C++] 中可用,可以被视为保守的扩展。 #### 类型系统和值类别   NPLA 中,值类别作为[实体类型](#实体类型),被作为一种内建的[类型系统](#类型系统和类型机制)。 **注释**   这和 \[ISO C++] 不同。\[ISO C++] 的“类型”的定义排除值类别,尽管值类别具有类型论意义上所有可作为类型讨论的对象的性质。   另见[引用类型](#引用值)。 ### 初始化   对象被创建后可通过*初始化(initialization)* 决定其值,并可能存在其它[作用](#基本语义概念)。被决定的值是*初始值(initial value)* 。   决定初始化这些作用的表达式是初始化的*初值符(initializer)* 。   初值符的求值可能有[副作用](#基本语义概念),其求值结果指定特定被初始化的对象的初始值。   初始化包括[被绑定对象](#环境对象)的初始化和作为函数值的[返回值](#函数调用)对象的初始化。   初始化被绑定对象可能以[修改操作](#实体的不可变性)的形式体现,此时修改绑定具有副作用。若这样的副作用存在,每个被初始化的值[后序](#规约顺序)于对应初始的计算。 **注释**   初值符的求值的副作用不属于初始化,其求值结果和对象的初始值不一定相同。   和宿主语言不同,初始化不是独立的依赖特定语法上下文的概念,但此处语义上的作用类似。   对象的初始化一般可蕴含[子对象](#子对象)的初始化。 #### 复制初始化和直接初始化   初始化包括*直接初始化(direct initialization)* 和*复制初始化(copy initialization)* 。   函数可能接受[引用值](#引用值)参数和返回值,是对函数的形式参数或函数值的复制初始化;其它初始化是直接初始化。   复制初始化形式参数和函数值时,函数参数或返回值作为初值符。 **注释**   区分两者和宿主语言类似。 #### 函数参数和函数值传递   部分函数可保证[被初始化的对象副本](#实体的副本)中的值和初值符的值及元数据一致。   这样的参数或返回值的初始化的求值称为*转发(forwarding)* 。   转发也包括只部分保留上述部分元数据的情形。   在允许保留元数据不变的上下文,转发在[本机实现](#npla-互操作支持)中可直接通过[转移项](#项的转移)实现。   转发保持引入这些初始化的表达式(通常是被求值取得函数值的函数表达式)时,其求值结果(函数值)的[值类别](#值类别)和初值符保持一致。 **注释**   这里的元数据的一个例子是[引用值的属性](#引用值的属性)。   转发类似宿主语言的*完美转发(perfect forwarding)* 。   另见[函数值传递](#求值策略)。 #### 对象的复制和转移   可使用初值符为参数进行复制或转移操作以[复制初始化](#复制初始化和直接初始化)对象,创建[对象的副本](#实体的副本)。 **注释** 这类似宿主语言中的类类型的值。其它情形另见[复制消除](#复制消除)。   对象的复制和转移不改变被转移后的[类型](#类型)。   对象的复制和转移对应蕴含其子对象被复制和转移。在[互操作](#npla-互操作支持)的意义上,若项[具有子对象的独占所有权](#求值和对象所有权),这些子对象的复制构造函数和转移构造函数被对应调用。特别地,这里的子对象包括[宿主值](#类型映射)。   可使用转移操作时,不对作为对象的[表示](#表示)的[项](#基本语义概念)进行复制,因此不要求其中的子对象可复制,而避免[引起错误](#错误)。 **注释** 这类似 \[ISO C++11] 起选择类的转移构造函数代替复制构造函数。   和 \[ISO C++11] 起不同,上述可使用转移操作的条件和语法上下文无关:引起选择转移操作的条件由对初值符的谓词而非类似宿主语言的构造函数判断(详见[默认值类别转换约定](#默认值类别转换约定))。 **注释** 同宿主语言。   除非另行指定,需要创建实体的副本时: * 若对象满足[可转移条件](#对象的可转移条件),则转移而不是复制。 * 其它情形实体被复制。 **注释** 一个主要的实例是[按值的副本传递](#求值策略)。 #### 项的转移   一定条件下,作为对象的[表示](#表示)的[项](#基本语义概念)可被整体转移,而避免其中包含的对象的初始化在对象语言中具有可见的[作用](#基本语义概念)。   在[互操作](#npla-互操作支持)的意义上,因作为对象的表示的项的转移,项及其子对象的转移构造函数会被调用,但项的[值数据成员](#项的子对象)中的[宿主类型](#类型映射)的转移构造函数不会被调用。 **注释** 这一般要求实现使用某种[类型擦除](#类型标注)使子对象类型的转移构造函数的调用不蕴含宿主类型的转移构造函数的调用。   项的转移是[析构性转移](#实体的副本)。   一般地,当对象需要被转移且没有约定[转移后要求类型不变](#对象的复制和转移)时,项的整体转移可代替[对象的转移](#对象的复制和转移),避免初始化新的[宿主对象](#类型映射),称为宿主对象转移消除。 **注释** 若需调用[宿主类型](#类型映射)的转移构造函数,需明确避免在代替对象的转移的上下文中进行操作。派生实现可提供这些操作。   [返回值转换上下文](#返回值转换上下文)的转移蕴含宿主对象转移消除。   若被[复制消除](#复制消除)的对象来自不同的项,则复制消除蕴含宿主对象转移消除。这包括所有对象转移的返回值转换上下文的情形。 ### 引用值   在对象语言中,*引用值(reference value)* 是作为[引用](#一等引用)的[值](#基本语义概念),可保存在一等对象中。这样的一等对象是*引用对象(reference object)* 。   引用值和引用对象的值具有*引用类型(reference type)* 。   在特定上下文中,引用和其它[一等对象](#一等实体和一等对象)的值的相同具有不同的语义,主要体现在引用值被按值直接初始化传递和[按引用传递](#求值策略)时。 **注释** 差异和 \[ISO C++] 中使用对象类型和引用类型作为参数类似。   NPLA 引用值总是假定和[被引用对象](#一等引用)关联。 **注释** 和宿主类型类似,引用类型没有空值。   仅当以下情形中,NPLA 引用值的被引用对象是非[一等对象](#一等引用): * 作为[临时对象](#临时对象)的[被绑定对象](#环境对象)。 **原理**   由于[左值](#值类别)的[项对象](#求值和对象所有权)[被环境所有](#项对象和关联对象所有权),为允许[规约求值](#求值规约)其中的[被绑定对象](#环境对象),需要不被环境所有的(其它不同的)[被规约项](#求值规约)作为[表示](#基本语义概念)的[项对象](#求值和对象所有权)作为中间值。   这种中间值通过间接引用作为[一等对象](#基本语义概念)使用,也是[一种间接值](#引用间接值),即引用值。 #### 子对象引用   特定的引用值是*子对象引用(subobject reference)* ,其被引用对象是被另一个对象所有的、作为这个对象的[子对象](#子对象)的一等对象。   子对象引用对特定操作可表现和其它一等对象不同的行为。   以下引用是子对象引用: * *子有序对引用(subpair reference)* 是[子有序对](#广义列表)作为被引用对象的引用。 * *子列表引用(sublist reference)* 是[子列表](#广义列表)作为被引用对象的引用。   语言可能引入其它的子对象引用。 #### 引用值的有效性   [作为](#引用间接值)一种[间接值](#间接值),引用值有效当且仅当访问被引用对象不引起未定义行为。   以下约定要求被 NPLA 实现支持的有效的引用值总是无条件地允许访问对象。   有效的引用值应通过特定的构造方式引入,包括: * 在对象语言通过被引用对象初始化引用值。 * [互操作](#npla-互操作支持)引入的保证不引起未定义行为的引用值。 **注释**   一些对象语言的操作可能引起引用值无效。例如,[改变](#实体的不可变性)被引用对象可以使已被初始化的有效的引用值成为*悬空引用(dangling reference)* 。 #### 多重引用   被引用对象也可以是引用值。   被引用对象不是引用值的引用值是*完全折叠(fully collapsed)* 的。   除非另行指定,*未折叠的(uncollapsed)* 引用值指未完全折叠的引用值。 **注释**   这和宿主语言不同。 #### 引用值的属性   引用值可以具有和作为引用值[表示](#基本语义概念)的[项](#基本语义概念)保存的[属性](#对象属性)相互独立的属性,保存其作为一等对象的状态。   属性不可分割:一个引用值明确具有或者不具有一种属性。   和[对象属性](#对象属性)对应,NPLA 指定的引用属性可以是: * 唯一引用。 * 不可修改引用。 * [临时对象](#临时对象)引用。   引用值属性指定通过引用对[被引用对象](#一等引用)的访问假定允许具有的性质,即便被引用对象自身没有具有这些属性。   特定的操作使用引用值作为操作数,根据不同的属性决定行为,包括在违反属性引入的假定时[引起错误](#错误)。   在本节要求以外,除非派生实现另行指定,违反这些假定不引起 [NPLA 未定义行为](#npla-未定义行为)。   具体的引用属性满足以下语义规则: * 唯一引用允许通过引用值访问被引用对象时,对象可被假定不被其它引用而仅通过这个途径访问,即便实际存在其它途径的引用时可能引起不同的行为;在假定的基础上程序具有何种可能的行为是未指定的。 * 唯一引用可被假定不被共享,被引用对象不被[别名](#对象别名)。 * 通过不可修改引用的左值的对象访问不包含[修改](#基本语义概念)。否则,若没有引起错误,程序[行为未定义](#npla-未定义行为);但除非另行指定,不引起[宿主语言的未定义行为](#npla-未定义行为)。 * 具有临时对象引用属性的引用值是临时对象的引用值,其被引用对象是临时对象。 **原理** 宿主语言的互操作不被总是要求保证对象语言程序的可移植性,但不应引起实现自身的行为无法预测。   对引用值的操作*传播(propagate)* 特定的引用属性,当且仅当:   若操作数是具有特定引用属性的引用值,且结果是引用值时,结果具有和操作数相同的特定属性。 **注释**   引用值属性和对象属性相互独立,类似 \[ISO C] 和 \[ISO C++] 在指针和引用等复合类型的 `const` 等限定独立于指向的对象或被引用对象上的类型不同。通过 `const` 等属性可以在指针或引用类型上单独限制类型,而不影响对应的被间接访问的对象。   唯一引用蕴含的假定类似 \[ISO C] 约定的 `restrict` 关键字,但程序违反假定的约束时不引起未定义行为。   和 \[ISO C++] 核心语言(但不是 [\[res.on.arguments\]](https://eel.is/c++draft/res.on.arguments) 中的标准库绑定到右值引用实际参数的约定)的右值引用类似,唯一引用不总是表示被引用对象不被共享。   接受唯一引用的操作可能只假定被引用对象的[子对象](#子对象)不被共享,也可能完全不进行假定,这依赖具体操作的语义。若需要和具体操作无关的无条件非共享假定,使用[纯右值](#值类别)而非作为左值的唯一引用。   和宿主语言的 `const` 限定类型类似,不可修改引用仅针对特定左值的访问;通过共享的其它未被限定的引用仍可修改对象。   违反不可修改引用引入的假定的错误可能通过[类型检查](#类型检查)或其它方式引起。   临时对象引用类似 \[ISO C++] 的*转发引用(forwarding reference)* 中保留在表达式声明中的类型信息。   因为 NPLA 不支持声明元数据,这些信息保存在对象的[表示](#表示)中,且在初始化时被引用值保存;也因此[这些元数据可跟随一等对象传递](#绑定临时对象属性)。对临时对象,[绑定操作](#绑定操作)可确保[元数据被添加](#绑定临时对象属性)。   这也和宿主语言不同。在宿主语言中: * 无论是标记消亡值的右值引用类型还是标记是否可转发的引用的转发引用推断的类型信息(左值引用或右值引用)都是静态的。 * 并且,转发的类型信息只在函数模板的局部有效,而不存在对应的跨过程传递机制。 #### 引用值的消除   作为[间接值](#间接值),引用值可被[消除](#间接值使用约定),即被其[(可能多重)引用](#多重引用)关联的被引用对象替代。   [未折叠的引用值](#多重引用)消除一次引用值,结果仍是引用值。   消除[完全折叠的引用值](#多重引用)的结果总是[右值](#值类别)。   推论:因为引用值不[循环引用](#自引用数据结构和循环引用)自身,除非引用值[已完全折叠](#多重引用),继续消除引用值得到的值和引用值是不同的值。 **原理**   特定的引用值消除可蕴含对不可修改的[传播](#引用值的属性)的要求。这和 \[ISO C++] 初始化引用时遵循的 `const` 安全性,属于类型安全性的一种。   但是,消除引用不一定总是预期这种性质,特别当折叠不被预期时。   例如,\[ISO C++] 内建指针的不同级 `const` 不会被隐式转换直接折叠合并。消除间接的指针值不是隐式的(而依赖内建一元 `*` 操作符),这是因为指针作为类型构造器自身的类型安全需要;是否消除 `const` 限定符仍然需要基于其它理由考虑。   而当被引用对象实现子对象时,修饰被指向的类型的 `const` 不会自动传播到子对象的类型中,此时可有 `std::experimental::propagate_const` 可选引入这种性质。   对具有非间接访问的子对象的类型,这相当于 \[ISO C++] 的 `mutable` 修饰符,可实现[内部可变性](#实体的不可变性)。而允许子对象以外直接不传播不可变性,是一种结构性的平凡的扩展:这允许把被引用对象直接视为一种子对象的实现,而非要求引入新的[名义类型](#类型等价性)。   在 NPLA 这样没有要求显式类型编码是否可变的语言中,首先要求总是具有不可修改的传播性质会显著增加规则形式上的复杂性。若具体操作需要传播不可修改性,仍可进一步约定。 **注释**   典型地,消除引用值包括: * 当[引用值提升转换](#值类别转换)的操作数是引用值时,消除被提升的引用值。 * 当[引用折叠](#引用折叠)的操作数是引用值时,消除被折叠的引用值。   和引用折叠不同,引用值提升转换不满足对不可修改引用属性的传播性质。 #### 引用折叠   和 \[ISO C++] 类似,引用值在 NPLA 中默认不被继续引用,使用引用初始化引用会引用到[被引用对象](#一等引用)上,即*引用折叠(reference collapse)* 。   引用值被折叠后结果和原引用值不同,当且仅当原引用值是[未折叠的引用值](#多重引用)。   和 \[ISO C++] 不同,NPLA 不限制派生实现利用未折叠的引用值。 **注释** 特定的操作可能区分未折叠的引用值。   引用折叠的结果是不可修改引用,若引用值和作为引用值的被引用对象之一是不可修改引用。   引用折叠的结果满足不可修改[引用属性的传播性质](#引用值的属性)。推论: * 引用折叠的结果是[唯一引用](#引用值的属性),当且仅当引用值和作为引用值的被引用对象都是唯一引用。 * 引用折叠的结果是[临时对象引用](#引用值的属性),当且仅当被引用对象是临时对象引用。 **原理**   内部表示可支持间接的引用,以允许在对象语言中实现[一等引用](#一等引用)。   引用折叠对不可修改的传播性质的要求和 \[ISO C++] 的引用折叠对 `const` 限定符的处理类似。   引用折叠对唯一引用的要求和 \[ISO C++] 的右值引用仅通过被折叠的引用都是右值引用类型折叠类似。注意 \[ISO C++] 右值引用推断仅用于推断转发引用(forwarding reference) 参数,而非直接声明特定的右值引用类型。   和唯一引用不同,临时对象相对唯一引用更接近 \[ISO C++] 的声明的右值引用类型信息(而非推断值类别时使用的消亡值表达式的右值引用类型),一般不预期被折叠。 **注释**   未折叠的引用值被折叠时,用于初始化的被引用对象可能仍然是未折叠的引用值。 #### 对象的可转移条件   根据项是否具有特定元数据的引用值可判断使用复制代替[对象转移的条件](#对象的复制和转移)。   对象的*可转移(movable)* 条件的判断基于首先基于[值的类型](#表达式的类型): * 非引用值(纯右值)总是可转移的。 * 否则,对象是引用值。可转移由[引用值的属性](#引用值的属性)决定:当引用值是唯一引用且非不可修改,引用值是可转移引用,对应的被引用对象是可转移的。 #### 引用值的表示   作为引用值的[表示](#表示),*引用项(reference term)* 是包含[项引用](#引用间接值)的[项](#基本语义概念)。   引用项中的项引用对象引用一个(其它的)项,即*被引用项(referenced term)* ,用于在必要时引入可被引用的一个项而不在 TermNode 中直接储存这个项的值。   被引用项表示引用项作为引用值对应的[被引用对象](#一等引用)。   引用项在作为[项对象](#求值和对象所有权)外,[保存标签](#引用间接值)作为[引用值的属性](#引用值的属性)的[表示](#表示)。   [临时对象](#临时对象)可作为引用值的被引用对象。   与此不同,非临时对象的引用值可作为一等对象而总是需要区分作为不同对象的[同一性](#基本语义概念)。   带有[临时对象属性](#引用值的属性)的引用值可在特定的操作中被视为和临时对象引用近似的引用值。   子对象引用的表示是*子对象引用项(subojbect reference term)* ,和本节中的其它引用类型的表示兼容,但不完全相同。   关于引用项的构成,另见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   因为[临时对象不是一等对象](#临时对象),临时对象的引用值可代替关联的被引用对象使之作为一等对象被访问。   为在对象语言中区分引用值和非引用值的[一等对象](#基本语义概念)是必要的,引用项这样的特设表示是必要的。   非引用项的表示则是针对临时对象的一种优化,因为使[被引用对象](#一等引用)总是在作为引用值的表示而: * 相比通常的间接值,避免间接[访问](#基本语义概念)关联的[被引用对象](#一等引用)的开销。 * 避免[悬空引用](#引用值的有效性)。   带有临时对象属性的引用值和临时对象的引用值不同,参见[绑定临时对象属性](#绑定临时对象属性)。 #### 引用值的子类型   根据表示和属性,引用类型具有如下[子类型](#类型序): * *左值引用(lvalue reference)* :以引用项表示的非[唯一引用](#引用值的属性)。 * *右值引用(rvalue reference)* :以引用项表示的唯一引用。   引用值是否作为[左值](#值类别)使用取决于上下文。除非另行指定,引用值都是左值。 **注释** 在要求右值的上下文发生[左值到右值转换](#默认值类别转换约定)。   引入不同的引用子类型后,NPLA 一等对象的值的类型和[值类别](#值类别)存在以下一一对应关系: * 若类型是左值引用,则对应的值类别是左值。 * 若类型是右值引用,则对应的值类别是消亡值。 * 否则,对应的值类别是右值。 **原理**   左值引用和左值引用与宿主语言中的对象类型的左值引用与右值引用分别类似。 **注释**   在要求右值的上下文,作为左值的引用值发生[左值到右值转换](#默认值类别转换约定)。 #### 不安全引用值   特定的引用值是*不安全引用值(unsafe reference value)* ,可能和常规的其它引用值具有不同的内部表示。   若实现支持不安全引用值,和其它引用值的行为不同由实现定义。   派生实现可能添加更多对不安全引用值的假设。 **原理**   不安全引用值可能放弃常规的引用具有元数据而能被更高效地访问。 ### 值类别转换   具有特定值类别的表达式可转换为不同值类别的表达式: * 除非另行指定,泛左值总是允许作为纯右值使用。从泛左值取对应右值的操作称为*左值到右值转换(lvalue-to-rvalue conversion)* 。 * 从纯右值[初始化](#初始化)可被对象语言作为[一等对象](#基本语义概念)使用的[临时对象](#临时对象)的引用值作为[消亡值](#值类别),称为*临时对象实质化转换(temporary materialization conversion)* 。   左值到右值转换没有[副作用](#一等状态)。临时对象实质化转换没有副作用,当且仅当其中初始化临时对象时没有副作用。   临时对象实质化转换中,纯右值被*实质化(materialized)* 。   在求值子表达式时,按表达式具有的语义,必要时(如按[相关规则](#类型系统和值类别)判断上下文的值类别)进行值类别转换。   NPLA 还提供可能使结果具有不同的值类别的*引用值提升转换(reference value lifting conversion)* 。以下规则确定引用值提升转换的结果: * 若操作数是引用值,则结果是操作数的[被引用对象](#一等引用)。 * 否则,结果是操作数。   引用值提升转换蕴含引用[提升](#项的子对象),即使用被引用对象替换操作数。 **原理**   为支持[引用值](#引用值)作为一等对象(特别是[未折叠的引用值](#多重引用)),NPLA 提供比左值到右值转换更精细的引用值提升转换。   值类别转换在特定求值中适用,因此不影响构造性的规则。   特别地,列表左值(列表的引用值)不能代替列表,因此以空列表的引用作为最后一个元素的嵌套有序对是[非真列表](#广义列表)。这和 \[R7RS] 约定空列表总是同一对象不同。   这种设计使语言规则更容易在局部一致,同时显著减少实现(对象内部表示)的复杂性,并有助于提升实现性能的可预测性。 **注释**   不同值类别表达式的转换和宿主语言中的部分标准转换类似。   根据引用值的性质,易知左值到右值转换的规约是引用值提升转换的规约的传递闭包,即: * 若操作数是[已完全折叠的引用值](#引用折叠),则引用值提升转换等价左值到右值转换。 * 否则,有限次的引用值提升转换等价左值到右值转换。   引用值提升转换不[传播引用值的属性](#引用值的属性),参见[引用值的消除](#引用值的消除)。   引用值提升转换不传播不可修改属性,类似 \[ISO C++] 非引用值的转换在结果中不保留源操作数中的 `const` 类型。   临时对象实质化可实现为空操作,因为项在先前(如[返回值转换](#返回值转换)蕴含的[引用值提升转换](#值类别转换)对[引用项](#引用值的表示)的[提升操作](#值类别转换)的实现中)已被创建。   互操作可能引入不以项表达的右值而需要首先创建项。 #### 默认值类别转换约定   除非另行指定: * 作为[操作符](#规范化中间表示)的[表达式](#表达式)没有值类别转换。 * 作为[操作数](#规范化中间表示)的表达式仅在必要时进行一次左值到右值转换。 **原理**   类似宿主语言规则,并非所有上下文都需要转换。类似地,宿主语言的操作符(括[函数调用](#函数调用)的第一个子表达式)可直接使用左值而不需要转换。但和宿主语言不同,因为[多重引用](#多重引用),不确定次数的连续的转换结果不同。因此除了上下文要求,有必要约定默认仅转换一次,而非确保转换结果到右值。   必要时,具体[操作](#规范化中间表示)仍可指定不同的规则。   值类别和左值到右值转换在一些上下文的行为类似箱和自动拆箱,约定存在默认转换并不利于维护[简单性](#简单性): * 特别地,和宿主语言不同,函数不包含充分的信息(参数类型)推断是否接受左值操作数,因此在不提供针对函数的*重载(overloading)* 一般机制的前提下,[本机实现](#npla-互操作支持)不能预知输入的操作数是否是左值,通常需分别支持左值和右值的操作数。 * 即便提供重载,仍然较单一的值类别更复杂。   但 NPLA 的设计中,值类别转换已被通过[正确](#正确性)反映需求的[存储和对象模型](#存储和对象模型)的设计隐含在项的内部性质中,因此不是可选的。   由[正确性的优先规则](#正确性),[完整性](#完整性)应先于简单性被满足。   而考虑[统一性](#统一性),对存储和对象模型的设计,用户自行的实现仍要求这些设施(尽管更困难)。   关于箱和自动装箱,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 #### 返回值转换   *返回值转换(return value conversion)* 是一次引用值提升转换和可选的一次临时对象实质化转换的顺序复合。   返回值转换用于在对象语言中确定函数调用的[返回值](#函数调用)可包含[函数体](#函数)的求值结果到返回值的转换。   引用值作为间接值,适用[局部间接值安全保证](#局部间接值安全保证)。在[返回值转换上下文](#返回值转换上下文)中确定函数返回值的[实质化转换上下文](#实质化转换上下文)的部分操作[消除引用值](#引用值的消除),即返回值转换,是这种情形的主要实例。   这可约束作为间接值的[完全折叠的引用值](#多重引用)不[逃逸](#函数调用)(因此[访问被引用对象的值可不超出指向对象的存储期](#局部间接值安全保证)),而保证只考虑项可能是引用值时的内存安全。   除非证明不需要[临时对象](#临时对象),返回值转换中初始化临时对象作为返回值的[项对象](#求值和对象所有权),否则临时对象被[复制消除](#复制消除)。是否存在复制消除是未指定行为。   不论是否存在返回值转换,返回值的项对象来自返回的[右值](#值类别)关联的[临时对象实质化转换](#值类别转换)。这可能在返回值转换蕴含的[项提升](#项的子对象)操作或之前的[求值规约](#求值规约)中蕴含。 **注释**   返回值转换不保证[未折叠的引用值](#多重引用)在消除引用值后的结果不逃逸。   为确保内存安全,程序仍需要保证被引用的对象的间接引用的对象生存期结束后,不能访问间接引用的对象。   其它间接值的内存安全需要另行保证。   是否需要返回值转换由实质化转换上下文中的被调用的函数而非上下文是否需要使用右值决定,无关被转换的表达式是否是左值,因此返回值转换不是[左值到右值转换](#值类别转换)。   当前未实现是否需要临时对象的证明。   另见[项对象和关联对象所有权](#项对象和关联对象所有权)和[局部间接值安全保证](#局部间接值安全保证)。 ### 临时对象   特定的 [NPLA 非一等对象](#npla-基础存储模型和对象模型)是*临时对象(temporary object)* 。   NPLA 允许(但不要求对象语言支持)一等对象构成的表达式通过特定的求值,在中间结果中蕴含这种非一等对象。 **注释** 这样的非一等对象不在源语言中可见,一般仅用于[互操作](#npla-互操作支持)。   临时对象的[子对象](#子对象)不是临时对象。   NPLA 对象语言在特定的上下文引入其它临时对象,包括: * [实质化转换上下文](#实质化转换上下文)。 * [返回值转换上下文](#返回值转换上下文)。 **原理**   为简化规约和互操作机制的设计,和 \[ISO C++17] 不同,引入临时对象不包括延迟初始化或异常对象的创建。   关于临时对象的子对象的规则,参见[绑定临时对象](#绑定临时对象)中的原理。 **注释**   关于临时对象的存储和所有权,参见[求值和对象所有权](#求值和对象所有权)。   关于临时对象的表示,参见[临时对象的表示](#临时对象的表示)。   关于避免特定相关对象的[初始化](#初始化)的要求,参见[复制消除](#复制消除)。   引入临时对象的一些上下文的和宿主语言类似。 #### 实质化转换上下文   可具有(但不保证具有)[临时对象实质化转换](#值类别转换)的上下文包括: * 使用纯右值初始化被绑定为[引用值](#引用值)的变量(如函数的引用类型的[形式参数](#函数内部的变量))。 * 求值[函数调用](#函数调用)以初始化[函数值](#函数合并)。 **注释**   一般地,被绑定为引用值的变量在[活动调用](#函数调用)关联的环境分配临时对象。此时,对象被调用表达式的项独占所有权,同时被绑定的环境独占资源所有权,并实现[复制消除](#复制消除)。   在不具有转换时,优化的实现可能消除函数调用(内联(inline) 展开)而不分配关联的环境,把临时对象分配到其它环境或者语言不保证可见的存储(如 CPU 寄存器)中,并同时实现复制消除。   临时对象实质化转换引入临时对象的规则和 \[ISO C++17] 不同: * 不论表达式是否作为子表达式使其值被使用(未使用的情形对应 \[ISO C++] 中的 discarded-value expression ),都允许存在临时对象。 * 要求复制消除而避免特定对象的[初始化](#初始化)。 #### 返回值转换上下文   [返回值转换](#返回值转换)可引入实质化的临时对象,其中[可能转移求值](#对象的可转移条件)的中间结果;否则,对象被复制。   此处被转移对象符合[求值和对象所有权](#求值和对象所有权)规则中的临时对象的定义,但除非另行指定,被转移的对象不在对象语言中可被访问。   仅在对象被复制且复制具有副作用时,返回值转换具有等价复制的副作用。 #### 复制消除   NPLA 要求特定上下文中的*复制消除(copy elision)* ,排除复制或转移操作且保证被消除操作的源和目的对象的[同一性](#一等对象的同一性)。   复制消除仅在以下转换上下文中被要求,即直接使用被转换的源表达式中的对象作为实质化的对象而不初始化新的临时对象: * [实质化转换上下文](#实质化转换上下文)。 * 引起对象转移至[函数值](#函数合并)的[返回值转换上下文](#返回值转换上下文)。   非[本机实现](#npla-互操作支持)函数的函数体内指定的返回值不属于上述的确定返回值的上下文,但也不要求被复制消除。   实现仍可根据[当前环境](#当前环境)来判断是否在允许消除对象复制的上下文中,而进行复制消除。   复制消除不在被初始化对象以外引入新的对象语言可见的对象。 **原理**   为维护语言规则的[简单性](#简单性)和使用这些规则的程序的行为的[易预测性](#易预测性),NPLA 的复制消除限于临时对象的消除。   在完成实质化转换前的不完整的[求值规约](#规约规则和求值)中的临时对象在逻辑上不需要作为[一等对象](#基本语义概念)存在,但纯右值作为对象[表示](#表示)中的[子项](#基本语义概念),随纯右值在宿主语言中作为对象存在,以允许互操作。   复制消除的目的 \[ISO C++17] 类似。同时,提供语言支持也允许更简单地实现 C++ 互操作性。   和 \[ISO C++17] 不同的一些要求可简化语言规则和实现,例如: * 不区分求值结果是否被作为[返回值](#函数调用)或求值是否为常量表达式。 * 非[本机实现](#npla-互操作支持)函数的规则不要求 `return` 语句中的特定的表达式,而不需要依赖特定上下文的语法性质。 * 同时,NPLA 不限制对象的类型(\[ISO C++17] 则要求特定的 C++ 类类型)。 **注释**   在实现中,被转换的源表达式中的对象是[待求值项](#求值规约)的[项对象](#求值和对象所有权)。   当前未实现按当前环境判断是否在允许消除对象复制的上下文中进行复制消除。 #### 生存期扩展   在使用[纯右值](#值类别)初始化[引用值](#引用值)时,*扩展(extend)* 源表达式的[项对象](#求值和对象所有权)的[生存期](#生存期附加约定)使之比其它规则决定的生存期延长。   这和初始化非引用值类似,但实现需区分是否初始化的是延长生存期的临时对象,以确保之后能区分引用值初始化时是否[按引用传递](#求值策略)。 #### 绑定临时对象属性   若[实质化转换上下文](#实质化转换上下文)支持[绑定临时对象](#绑定临时对象),按引用绑定(即绑定初始化使用[按引用传递](#求值策略))的[被绑定对象](#环境对象)是[临时对象](#临时对象)。   引入引用值的形式参数需要满足的要求由引入绑定的操作或派生实现指定。 **原理**   绑定临时对象时指定临时对象属性允许区分[通过引用绑定延长生存期的临时对象](#生存期扩展)和非引用绑定的对象。   一般地,表达式中的[纯右值](#值类别)(非引用值)被绑定为临时对象,即被绑定的对象在初始化后具有[临时对象属性](#对象属性)。   这对应宿主语言中的转发引用参数(如 `std::forward` )中的情形: * 若模板参数 `P` 对应转发引用函数参数 `P&&` ,其中 `P` 是对象或对象的右值引用类型,保留从实际参数*推导(deduce)* 得到的但不是实际参数类型的信息。 * 没有绑定临时对象属性的对象则同一般的非引用类型的对象类型参数(非转发引用)。   `P` 在宿主语言中通过值类别推断,但不表示值类别。   类似宿主语言,这种操作数表达式的值类别以外的形式是一种[类型推断](#类型标注)。因为推断规则和宿主语言的*类型推导(type deducing)* 相似,这种上下文可支持类似宿主语言的参数转发。但和宿主语言的 `std::forward` 不同,此处推断的右值除了是[消亡值](#值类别)外,也可以是[纯右值](#值类别)。   临时对象属性在绑定特定形式的参数时具有和 `P` 编码的附加信息类似的作用: * 不具有临时对象属性的引用值作为[表达式](#表达式),在初始化临时对象的引用时被视为[左值](#值类别)(不影响其余属性)。 * 其它表达式的推断结果是右值。   带有临时对象属性的引用值和临时对象的引用值不同。特别地,作为引用属性值的临时对象属性允许在[运行时](#实现的执行阶段)作为对象的元数据[访问](#基本语义概念)以及跟随对象被跨过程传递,这无法被宿主语言支持,因为 `P` 表示的[静态类型](#类型系统和类型机制)信息不在函数外可用,仅在模板的类型参数 `P` 中而不在运行时可访问的元数据中(事实上,也不在对象的动态类型中)保留。关于其应用,参见[进一步讨论](#绑定临时对象)。 **注释**   因为宿主语言的引用折叠,以上 `P` 和 `P&&` 一致。   被绑定的这些对象可作为临时对象引用关联的[被引用对象](#一等引用)。   另见[绑定操作](#绑定操作)。 #### 临时对象的表示   作为一等对象的临时对象和其它一等对象表示方式一致。   非一等对象临时对象包括: * 所有权可[被项独占](#项对象和关联对象所有权)而不能作为一等对象访问的对象。 * 具有[临时对象标签](#对象属性)的[项](#基本语义概念)作为内部表示的对象。   对临时标签对象决定的非一等对象,去除临时对象标签后,应具有一等对象表示。   在[项引用](#引用值的表示)以外的临时对象标签仅在[被绑定对象](#环境对象)上存在。   关于一等对象表示,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   至少在逻辑上,临时对象作为对象语言中不可见的对象和一等对象相同的宿主类型(即项)作为内部表示。因此,区分其内部表示并非通过宿主语言中的类型,而需通过运行时性质确定。   一些表示可能仅出现在临时对象中,而不是合法的一等对象表示。实现可据此进行一定运行时检查,以排除[互操作](#npla-互操作支持)或者错误实现中的误用。   对临时对象标签决定的非一等对象和一等对象表现之间的要求简化实现的一些操作,使[实质化](#值类别转换)不需依赖另行分配的资源。   临时对象的子对象不是临时对象,简化对临时对象的一些操作,也减少可能使临时对象标签扩散(到非预期的上下文影响一等对象表示)而误用。   关于被绑定对象的规则,参见[绑定临时对象](#绑定临时对象)中的原理。限制标签的使用范围以使之不和其它表示冲突。   关于实现中项的[宿主类型](#类型映射)和简化实现的例子,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **注释**   直接构成项的非一等对象可以是通过[源代码](../Terminology.zh-CN.md#程序设计语言)中的[外部表示](#表示)经[翻译](#实现的执行阶段)变换得到的具有内部表示的数据结构的非一等对象,参见上述实现中项的[宿主类型](#类型映射)。 ### 表达式的类型   NPLA 的[类型系统](#类型系统和类型机制)使用[隐式类型](#类型标注);默认使用[潜在类型](#类型标注),保证[表达式的值具有类型](#npla-整体约定)。   NPLA 表达式的[类型](#类型)是表达式[求值结果](#基本语义概念)的类型。   [空求值](#求值性质)的求值结果要求未求值的[合式的](#翻译时正确性规则)表达式应具有和[语法分析](#实现的执行阶段)的实现的输出兼容的类型。   实现对特定的上下文的表达式可使用[类型推断](#类型标注)。由此确定的类型类似宿主语言的表达式的类型。   表达式具有[值类别](#值类别)。值类别的指派规则作为[定型规则](#类型系统和类型机制)是[类型系统的一部分](#类型系统和值类别)。但除非另行指定,值类别和 NPLA 及派生语言规则中描述的表达式的类型[正交](#正交性)。   关于语法分析的实现和其中处理的类型,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **注释**   类型系统和 Scheme 及 Kernel 语言类似;除了表达式具有值类别这点和 Scheme 和 Kernel 不同而类似宿主语言。   表达式的类型和 \[R7RS] 的 expression type 无关,后者是[语法形式](#语法形式)的约定(在 \[R5RS] 和 \[R7RS] 中称为 form );因为存在[合并子](#合并子)作为[一等对象](#基本语义概念)的类型,不需要这种约定。   NPLA 中值类别和表达式的类型正交,这类似宿主语言。这简化了相关[类型规则](#类型)的描述。 ## 生存期附加约定   和宿主语言不同,NPLA 子表达式的求值顺序可被不同的函数(特别允许显式指定对特定[操作数](#规范化中间表示)求值的[操作子](#合并子))中的求值调整,不需要特别约定。   NPLA 不存在宿主语言意义上的[完全表达式](#严格性),但在按宿主语言规则判断生存期时,使用[本机实现](#npla-互操作支持)的[函数合并](#合并子)视同宿主语言的完全表达式,其本机函数调用不引起函数内创建的对象的生存期被延长。   [临时对象](#临时对象)的[生存期](#npla-基础存储模型和对象模型)同时约束隐含的隐式宿主函数调用(如复制构造)。   为保证求值表达式取得的临时对象的[内存安全](#内存安全),函数合并同时满足以下规则: * [操作符](#规范化中间表示)和未被求值的操作数的直接或间接子表达式关联的对象以及求值操作数的子表达式引入的临时对象的生存期结束的作用应不[后序](#规约顺序)于[活动调用](#函数调用)结束。 * 生存期起始和结束的顺序被确定(determined) 时,和对应所在的表达求值之间的[先序](#规约顺序)关系同构;否则,其顺序满足[非决定性有序](#规约顺序)关系。 **注释**   生存期的顺序约束确保引入临时对象时,其生存期不会任意地被[扩展](#生存期扩展)而超过[函数合并](#函数合并)的求值。   具体操作可在以上约束下指定被求值的操作数可能引入的临时对象的生存期。 ## 尾上下文约定   NPLA 表达式[求值规约](#求值规约)中最后一个规约步骤中的上下文是[尾上下文](#上下文相关求值)。   尾上下文在 NPLA 中可满足一些附加的性质。 ### 真尾规约   尾上下文涉及的存储在特定情况下满足调用消耗的空间有上界(即空间复杂度 O(1) )。   满足这种情况下的规约称为*真尾规约(proper tail reduction)* 。 ### 尾调用和 PTC   在尾上下文规约的[调用](#函数调用)是*尾调用(tail call)* 。   以[真尾规约](#真尾规约)的实现尾调用允许具有不限定数量的(unbounded) [活动调用](#函数调用),称为 PTC(proper tail call ,真尾调用)。   PTC 占用[活动记录](#活动记录)满足真尾规约的上界的要求。   当宿主语言提供函数调用支持 PTC 时,可直接使用宿主语言的 PTC 调用,否则,需要使用其它替代实现机制确保 PTC 。   非对象语言的调用的上下文中,若被调用时间接使用,也仍需要保证 PTC 。   PTC 确保仅有一个[活动](#函数调用)的调用。不满足 PTC 的情形下,语言没有提供用户访问非[活动记录帧](#活动记录)资源的手段,因此可以认为是资源泄漏。但为简化语义规则,NPLA [不要求避免相关的弱内存泄漏](#求值和对象所有权)。   NPLA 不添加保证活动记录帧中保存引用,销毁活动记录的帧可能影响环境中的变量生存期而改变语义。 **注释** NPL 不保证一般对象存在[引用](#一等引用)。   因此,除非依赖本节中以下的规则,NPLA 不保证提供 PTC 支持;实现更一般的 PTC 依赖派生实现定义的附加规则。   除非另行指定,NPLA 要求至少在被[求值算法](#求值规约)中蕴含的[函数应用](#函数合并)的求值支持 PTC 。   为满足 PTC ,在[生存期附加约定](#生存期附加约定)的基础上,尾上下文内可以确定并调整对象生存期结束时机: * 作为[临时对象](#临时对象)的[合并子](#合并子)及其参数可以[延长生存期](#生存期扩展)至多到尾上下文结束。 * 被证明不再需要之后引用的对象,或未被绑定到活动记录上的项中的对象,可以缩短生存期。 * 被延长生存期的对象生存期起始和结束的相对顺序保持不变。 * 被缩短生存期的不同对象生存期结束的相对顺序保持不变。   推论:被缩短生存期和延长生存期的对象的生存期结束的相对顺序保持不变。这由没有被调整生存期的对象与被调整生存期对象之间的生存期结束的[顺序关系](#尾上下文约定)的传递性保证。   延长临时对象生存期和宿主语言中允许扩展非完全表达式内的临时对象的效果类似,但条件不同。 **原理**   要求 PTC 主要用例是支持 [PTR](#ptr) 。相对 PTR ,PTC 更具有一般性,也适合对象语言[可观察行为](#状态和行为)以外的普遍性质。 **注释**   以上规则中被调整生存期的对象一般仅是参数和函数体内创建的对象。因此,不保证理论上允许的尾上下文的都满足 PTC 。一个例子是合并子中可以保存[动态环境](#函数和函数应用的求值环境),这个环境可能被递归的调用引用,而无法提前释放。   理论上 PTC 不要求延长生存期,仅要求特定情形下缩短生存期,且其它情形被释放的对象生存期不延长到尾上下文外。   允许延长生存期是[生存期附加约定](#生存期附加约定)的结果。 ### PTR   PTC 的活动记录性质也在一般的递归规约时体现,被称为 PTR(proper tail recursion ,真尾递归)。   和 PTC 不同,PTR 要求的递归规约不一定是对象语言中的调用,以 PTR 描述时仅强调递归,不考虑尾上下文的适用性。   通过特定的保持语义等价的变换,对象语言可要求尾上下文作用于函数调用以外的上下文中(例如非[函数合并](#函数合并)的语法上下文)使用真尾规约实现。   除非派生实现另行指定,NPLA 对象语言不指定使函数调用以外的上下文作为尾上下文的要求;函数调用以外的尾上下文规约的仅可能用于实现的元语言中的[管理规约](#管理规约);非管理规约的真尾规约都用于尾调用。   此时,PTR 等价被递归调用的 PTC 。但由于支持 PTC 在非递归规约情形时也影响语言实现的一般构造,所以描述要求时一般不以 PTR 代替 PTC 或真尾规约。   关于 PTR 在 Scheme 为基础的形式模型,参见 \[Cl98] 。   PTR 的一个更激进的实现优化方式是 evlis tail recursion ,参见以下文献和参考资料: * [Continuation-Based Program Transformation Strategies](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.83.8567&rep=rep1&type=pdf) * \[Cl98] * [Evlis tail recursion](https://www.akalin.com/evlis-tail-recursion)   因为 NPLA 使用[链接的环境](#环境对象),不支持实现其中更激进的 safe-for-space 保证。 **原理**   支持 PTR 使[重入](#过程)的函数调用保持较小的空间开销。这允许使用递归的函数调用代替尾上下文中特设的*循环(loop)* 和迭代语法实现等效的算法功能,满足[简单性](#简单性)和[通用性](#其它推论和比较)。   与[控制状态和支持一等状态的实现之间具有的偶然耦合](#一等状态)不同,使用支持 PTR 的递归函数调用代替循环的耦合可以是足够必要的:它排除了特设的循环语法的需要,同时也能满足实现自身的[简单性](#简单性),也因此可能更高效。   为支持[一等对象](#一等实体和一等对象),可被共享的环境一般不支持[平坦的表示](#环境对象)。   不具有 safe-for-space 保证时,实现对程序的闭包变换(closure conversion) 可能创建多余的[循环引用](#自引用数据结构和循环引用)且无法被运行时有效地分辨,而造成[资源泄漏](#资源泄漏)。通过明确支持这类保证的变换(如[这里描述的设计](http://flint.cs.yale.edu/flint/publications/escc.pdf) )可避免变换引起资源泄漏。   只要捕获自由变量的[静态环境](#函数和函数应用的求值环境)可被程序在创建函数时明确指定,safe-for-space 保证就不是必须的:避免在[语义规则约定](#生存期扩展)之外的生存期延长和资源泄漏是[用户程序](#程序实现)的责任;NPLA 程序可精确控制对象生存期,同时应当避免[循环引用](#npla-未定义行为)。   对 safe-for-space 保证的证明(如 \[Cl98] 和 [Closure Conversion Is Safe for Space](https://zoep.github.io/icfp2019.pdf) ),隐含要求和上述一等对象支持冲突的条件:环境中引用的对象总是可被复制的没有可观察行为的值。这实质上要求支持[共享引用](#共享引用)乃至可能要求一等对象都是[引用](#一等引用)。   因为使用链接的环境的要求,一般情形[不支持](#环境对象)对 safe-for-space 的变换。   即便允许类似的变换,这也仅保证不存在[不可达](#资源泄漏),仍然不保证资源被及时回收——[全局机制](#所有权抽象)可能具有不确定的延迟而造成的实时资源泄漏。 ### TCO   TCO(Tail Call Optimization ,尾调用优化)是在以尾上下文规约时,允许减少修改规约状态的优化。   一般地,TCO 可重新排列规约过程中的被语义允许调整的副作用和其它不影响可观察行为的[状态](#状态和行为)的调用,减小空间开销。   TCO 的这种性质可以在宿主语言不支持 [PTC](#尾调用和-ptc) 时用于实现[对象语言的 PTC](#宿主语言中立) 。 **注释**   关于 TCO 和 PTC 的差异,另见[这里](https://groups.google.com/d/msg/comp.lang.lisp/AezzhxTliME/2Zsq7HUn_ssJ)。 #### 宿主语言中立   C++ 不要求实现支持 PTC ,也不保证支持 TCO 。因此,对象语言的 PTC 要求显式的 TCO 实现。   为可移植地支持 TCO ,NPLA 不依赖宿主语言中不可移植的[互操作](#npla-互操作支持)的活动记录(通常是体系结构相关的栈)。 **注释**   尾调用可避免尾上下文中非[嵌套调用安全](#嵌套调用安全)的情形的[宿主语言实现的未定义行为](#嵌套调用安全),但不保证非尾上下文中具有类似的性质。 #### TCO 实现策略概述   TCO 包括以下形式: * 静态 TCO :实现时替换宿主语言中不保证满足 PTC 的构造为满足 PTC 的构造。 * 动态 TCO :运行时调整直接或间接表示对象语言构造的数据结构和状态,使状态占用的空间复杂度满足 PTC 要求。   静态 TCO 也适合非对象语言的调用的[上下文](#尾调用和-ptc)。   不依赖宿主语言特性的静态 TCO 包括以下形式: * 替换宿主语言实现中的不保证满足 PTC 的递归调用为满足 PTC 的结构(如循环结构),包括直接编码和自动的变换(transformation) ,称为宿主(host) TCO 。 * 替换不满足 PTC 的对象语言原语为满足 PTC 的表达形式,称为目标(target) TCO 。   不依赖宿主语言特性的动态 TCO 包括以下形式: * 通过合并不同[活动调用](#函数调用)中活动记录占用的冗余状态,减少宿主语言的活动调用同时占用的总空间,称为 TCM(Tail Call Merging ,尾调用合并)。 * 引入具有便于操作[控制作用](#基本语义概念)的构造,同时作为一些其它优化的基础,以消除部分活动记录状态的分配,称为 TCE(Tail Call Elimination ,尾调用消除)。 **注释**   本节的内容不影响对象语言的语义,但可能影响[互操作](#npla-互操作支持)的接口兼容性和实现质量。   关于对实现的具体影响,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 ## NPLA 数学功能   NPLA 数学功能(模块 NPLAMath )提供数学功能和相关支持。   关于 NPLA 数学功能的规格说明的其它部分,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 ### 数值类型   [NPLA 数值](#附加功能)是 NPLA 对数学意义上的*数(number)* 的建模。   被建模的数是 NPLA 数值的*真值(true value)* 。   NPLA 数值的集合到真值的集合的映射是满射;除此之外,也存在不被 NPLA 数值建模的数,这些数可能被 NPLAMath 未来的版本支持作为真值。   除非另行指定,NPLA 数值的行为由对应的真值的数学含义决定。   基于宿主语言的类型系统,NPLA 支持以下按数值范围从小到大排列的本机整数和浮点数作为[宿主类型](#类型映射): * `signed char` * `unsigned char` * `signed short` * `unsigned short` * `int` * `unsigned` * `long` * `unsigned long` * `long long` * `unsigned long long` * `float` * `double` * `long double`   文法表示:   支持的数值类型以 `` 表示,具有以下表示数值的[子类型](#类型序): * `` :复数。 * `` :实数。 * `` :有理数。 * `` :整数。   其子类型由数学定义蕴含,即以上类型中,后者依次是前者的子类型。   当前所有数值都是 `` ,因此暂时没有针对 `` 值是否属于 `` 和 `` 的类型检查。   和数学意义上的实数不同,`` 也包含以下可带符号(sign) 的*特殊值(special value)* : * 无限大值。 * [NaN(not a number)](https://zh.wikipedia.org/zh-cn/NaN) 值。   对应地,`` 也包含实部和/或虚部是上述特殊值的特殊值。 **注释** 当前所有复数都是实数,因此虚部总是 0 。   根据数值是否完全保留真值在数学上的唯一性即*精确性(exactness)* ,数值分为*精确数(exact number)* 和*不精确数(inexact number)* 。   精确数和对应的真值总是相等;不精确数和真值不严格相等。   有限的不精确数的偏离程度可通过实数描述,即(绝对)*误差(error)* 。精确数的误差恒等于 0 。   除非另行指定,特定不精确数的具体的误差是未指定的。   数值的绝对*精度(precision)* 是其内部表示蕴含的误差的上界的倒数。对确定使用进位制的表示,精度也指精确表示的数值位数。   数值的*任意精度(arbitrary precision)* 指除[实现环境的可用资源(一般即存储空间)限制](#资源可用性基本约定)外,不限制精度。 **注释** 为支持更多数学上有意义的真值,未来可能引入其它类型来表示任意精度的整数、有理数及数学意义上的扩展(如复数和四元数)。   数值的内部表示中能以实数描述的度量应至少具有整数数量级精度,即误差不大于 1 。   精确数和不精确数在数值上可能相等,而[类型不同](#类型等价性)。   宿主类型中的本机整数和浮点类型是数值类型的子类型,分别称为 fixnum 和 flonum 。这些类型在项的内部表示预期直接占据本机存储而不需要动态分配。   Fixnum 总是精确数;flonum 总是不精确数。 **注释** 当前实现中,所有数值是 fixnum 或 flonum 之一。两者分别是宿主的整数类型(排除字符和 `bool` 类型)以及浮点数类型。   Flonum 支持带符号的无限大值以及 NaN 值作为特殊值。其它值都是有限值。特殊值可能具有不唯一的内部表示,但和有限值的表示都不同。   Flonum 中可存在小的非零数,可能和其它数值不同的内部表示而更容易在计算中损失精度,即非规格化(denormalized) 数值。   整数值的数值具有整数类型。这包括所有的 fixnum ,以及 flonum 中是整数的数值。这不和宿主类型直接对应。 **注释** 一个 flonum 是整数,当且仅当它的值取整后结果和原值相等。这里的取整使用可使用任意的舍入。\[R7RS] 对不精确数有类似的定义(仅使用 round )。   对 fixnum ,`+0` 和 `-0` 是相等的数值。Flonum 不同符号的零值在值的表示中可以不同,但在数学意义上相等,表示同一个数。实现中的其它和具体表示无关的[等价谓词](#实体的等价性)是否表现这种不同是未指定的。 **注释**   NPLA 的不同的[名义](#类型等价性)数值类型的集合到宿主类型的集合的[类型映射](#类型映射)是满射,即本节指定的宿主类型总是关联至少一个能表示它的值的 NPLA 数值类型。   同时,NPLA 数值类型可以映射到其它类型,特别地,NPLA 整数的类型映射目标是宿主语言整数类型和包含整数值的非典型[宿主类型](#类型映射)的并。所以,NPLA 数值整体的类型映射不构成简单的一对多或多对多关系。   特殊值同 ISO/IEC 10967–1 (LIA–1) 定义,引用 \[IEC 60559] (IEEE-754) 的具体值,仅适用于浮点数。   无限大值在数学上属于*超实数(hyperreal number)* ,在浮点数实现中属于*扩展实数(extended real number)* 。   无限大值的符号在数学意义上是必要的,因此也被要求区分。   精确性、fixnum 和 flonum 等区分同 \[R6RS] ,但具体实现要求不尽相同。   NaN 不是数学意义上的数,表示特定的没有数学定义的计算结果。NaN 和任何数值比较总是不相等。   NPLA 的宿主语言支持的 NaN 值带有符号。不是所有实现都区分符号,如 \[ECMAScript] 。   整数精度外的数量级精度仅在确定使用的进位制底数时和绝对精度可比较,因此常用于描述特定实现的内部表示(例如,\[ISO C] 定义的浮点数精度即有效数字的位数)。但是,不比较具体大小时,有限的数量级精度和绝对精度性质可以一致,这种上下文可不区分两者。   大多数不精确数的浮点表示的高效实现使用底数 2 。   参照 \[IEC 60559] ,浮点格式的无限大值和有限数值是浮点数;NaN 不是浮点数。两者统称为*浮点数据(floating-point datum)* 。   宿主类型中实现为 \[IEC 60559] 的*非规格化(denormalized)* 浮点数是具有不唯一的内部表示的小的非零数值。使用 IEEE-754 2008 以来的定义,这些数是*非规格(subnormal)* 数。   和 \[RnRK] 不同,本文档没有指定可选的模块,也没有指定精确的 ±∞ 值。   和 \[RnRK] 不同,不精确数不指定边界和*主值(primary value)* ,NaN 值被显式提供而不是唯一的 `#undefined` 值,非规格数不作为 `#undefined` 。这同时不要求在任意的操作中检查 `#undefined` 值并[引起错误](#错误)。   尽管容易损失精度,区分不同的非规格数的数值仍然有意义。同时,也避免和 NaN 用以表示数学上未定义操作的结果(如 0 除以 0 )引起混淆。   数值相等和一般对象相等可使用不同的等价谓词。和一般对象比较不同,数值相等比较可对参数要求数值类型,否则引起错误。两者比较结果可能也不总是相同。如 Scheme 的 `=` 和 `eqv?` 以及 Kernel 的 `=?` 和 `equal?` 。 ### 数值操作约定   在对象语言中,数值操作是可使用数值作为算法输入的值的操作。NPLA1 提供本机 API 支持这些操作的实现。   数值操作数和非数值操作数分别是具有和不具有数值类型的操作数。   数值操作蕴含对应的数值计算,接受至少一个数值或非数值操作数,预期得到[计算结果](#过程)。 **注释** 非预期情形可[引起错误](#错误)。   其中,计算结果依赖影响计算结果的操作数,并依赖至少一个数值操作数。   除非另行指定: * 在数学上有意义的前提下,数值操作同时支持以上尽可能多的数值类型的操作数。 * 数值操作对预期的数值操作数进行[类型检查](#类型检查),失败时出错。 * 数值操作不区分数值操作数中对应的真值相等的精确数或不精确数。 * 可假定数值操作数和计算过程中不出现 [SNaN(signaling NaN) (en-US)](https://en.wikipedia.org/wiki/NaN#Signaling_NaN) 值。 * 若作为操作数的精确数决定计算结果在数学上未定义,则引起错误。 * 不精确数计算中的舍入方式未指定。 * 若计算结果是数值,则: * 若被计算结果依赖的任一操作数中具有 NaN 值,则依赖这个操作数的数值操作结果也是 NaN 值。 * 输出的类型的值域能表示操作结果;除操作的语义和本节的其它规则蕴含外,具体类型未指定。 * 若作为操作数的精确数决定的计算结果是不精确数表示的有限数值,则这个不精确数应是所有相同内部表示的数值中和结果的真值误差最小的数值。 * 对数学上封闭的计算,结果具有不超过所有数值操作数范围的数值类型。 * 除非不能在结果类型中表示计算结果的范围: * 若数值操作的所有数值操作数都是精确数,结果不是不精确数。 * 数值操作的实现不损失按数学定义得到的中间结果的[精度](#数值类型);结果的[误差](#数值类型)仅来自其依赖的数值操作数引入的累积误差。 * 若计算结果是不精确数,则: * 若计算结果是小于最小可唯一表示的 `` 值,则对应的数值操作结果是不精确数 0 。 * 计算结果中真值等于 0 的数值以及 NaN 值的符号是未指定的。 * 若计算结果中无限大数值不能通过数学上有意义的方式确定符号,则对应的数值操作结果是无限大值或 NaN 之一,具体选择未指定。 * 数值的宿主类型未指定。 **原理**   因为典型的高效实现实现依赖[外部环境](#略称)对浮点数的支持,设计策略以保持[互操作](#npla-互操作支持)的便利性相关。 * NPLAMath 实现不访问 SNaN 值,也不需要访问宿主语言的浮点环境,但不假设总是使用默认浮点环境。 * 这不阻止和使用 SNaN 的[本机实现](#npla-互操作支持)的程序链接和调用,这有助于保持互操作性。 * NPLAMath 实现不保证检查访问浮点环境的副作用是否存在。若互操作需要改变浮点环境,应避免破坏实现的假设。 * 不依赖零值的符号、NaN 的符号以及 SNaN 的处理和许多[宿主实现](#需求概述)的默认情形一致而能简化一般的实现,如: * [GCC](https://gcc.gnu.org/wiki/FloatingPointMath) 。 * [Microsoft VC++](https://docs.microsoft.com/cpp/build/reference/fp-specify-floating-point-behavior) 。 * 不要求使用 GCC 时启用 `-ffloat-store` 。Microsoft VC++ 默认的 `/fp:precise` 的类似语义也不被依赖。 **注释**   数值操作可能允许非数值的操作数,这些操作数也可被计算结果依赖。   因为 [flonum](#数值类型) 能表示所有实数数值范围,所以实数范围以内的操作不会引入操作数以外的其它 flonum 类型。   数值操作抛出异常的要求不一定在每一个实现数值操作的 [API](../Terminology.zh-CN.md#程序设计语言) 中蕴含,因为这些 API 不一定是数值操作的完整实现。   抛出异常和[宿主环境](#嵌入宿主语言实现)的异常和浮点异常没有直接关联。   若数值操作指定非数值计算结果(如[布尔值](#基本语义概念))或者不能表示的 NaN 值的计算结果,则即使依赖 NaN 数值操作数,也不是 NaN 值。   和 \[RnRK] 不同,数值操作不支持不同的全局模式。   特定情形下,精确数可能替换计算结果中的不精确数: * 当按数学定义能被精确表示时,计算结果可以是符合要求的任意一个类型的精确数。 * 否则,当实现能证明不精确数值足够小到不足以影响结果的表示,且存在真值相等的可用的精确数时,可使用这个精确数代替不精确数。 * 当前实现没有这类证明机制。   IEEE-754 使用*渐进下溢(gradual underflow)* ,使零值和相邻的非零浮点数的真值之差不会显著大于其它两个浮点数真值的差,而使小的非零浮点数之间的差不等于零。这体现了支持[非规格化数](#数值操作约定)的实际作用。但一般数值计算仍需要累积误差。   虽然可能影响结果,浮点数实现的内部状态(如舍入模式)的访问不被直接支持。   浮点数 0 和 0 之差的符号可能取决于舍入模式。数值操作一般不保证结果 0 的符号,但以依赖[表示的形式](#数值表示)仍可确定符号。   除满足必要的精度要求的前提外,互操作以外目的的数值的宿主类型的具体选择在维持计算正确性的意义上通常不重要,因此默认不要求指定。   对特殊值,因为 \[RnRS] 只要求 `.0` 后缀的特殊值字面量,需保持兼容时,程序可只使用这些形式的字面量。为此,实现可使用带有 `.0` 后缀特殊值的数值字面量的对应的宿主类型,以减少潜在的可移植问题。   关于派生实现支持的数值字面量,参见 [NPLA1 数值字面量](#npla1-数值字面量)。 ### 数值表示   支持解析的数值以字符串作为外部表示。作为字面量时,构成[数值字面量的词法](#npla-扩展字面量)。   数值的外部表示和内部表示应支持*往返(round-trip)* 转换,即转换的内部或者外部表示输出可被输入接受。   往返转换中,精确数转换保持任意(无限)精度;不精确数经有限次转换不继续损失精度。 **注释** 即便损失精度,也应总是满足结果至少不低于[整数精度](#数值类型)。   支持的外部表示和对应的含义具体包括: * 数值的外部表示中起始的一个 `+` 或 `-` 字符指定符号。 * 这可能是可选的。若符合规则的数值字面量没有指定符号,则隐含为 `+` 。 * **注释** 不精确数可能在内部表示支持不同符号的[零值](#数值类型)。 * 以下优先匹配较长的模式。 * 匹配[正则表达式](https://zh.wikipedia.org/zh-cn/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F) `(+|-)?[0-9]+` :十进制整数值。 * 不论符号,当前精确数数值字面量默认都具有[宿主类型](#类型映射) `int` ,除非其绝对值太大而无法被表示,使用其它类型代替。 * 除非精确数字面量的数值超过所有 [fixnum](#数值类型) 的可表示范围,都具有 fixnum 值。 * 除非精确数字面量的数值超过所有支持的精确数的可表示范围,都是精确数。 * **注释** 当前精确数的表示范围是 fixnum 中宿主类型的值域的并集,因此超过 fixnum 可表示范围的数值不是精确数。 * 匹配正则表达式 `(+|-)?[0-9]+\.[0-9]*` 或 `(+|-)?[0-9]+(\.[0-9]*)?(E|e|S|s|F|f|D|d|L|l)(+|-)?[0-9]+` :十进制不精确数数值。 * 不精确数数值字面量的解析使用未指定的浮点数舍入模式,其[误差](#数值类型)不大于最后一个在规格化范围内表示的十进制小数位为 1 时的绝对值的真值大小。 * 解析不精确数外部表示得到的真值和内部表示可具有误差。 * **注释** 误差和具体宿主语言支持相关,通常以任意可能符合宿主语言要求的舍入模式下的最大值计。 * 第一种形式是直接记法。 * 第二种形式是[科学记数法(scientific notation)](https://zh.wikipedia.org/zh-cn/%E7%A7%91%E5%AD%A6%E8%AE%B0%E6%95%B0%E6%B3%95) ,在指示指数的指数字母前后匹配的数字序列分别是有效数字(significand) 和指数(exponent) 。 * 指数字母表示作为 [flonum](#数值类型) 的不同精度: * `E` 或 `e` :默认精度。 * `S` 或 `s` :短(short) 精度。 * `F` 或 `f` :单精度(float) 。 * `D` 或 `D` :双精度(double) 。 * `L` 或 `l` :长精度(long) 。 * 同组的字母含义等价。以上精度中,默认精度不低于双精度,其它精度依次不低于之前的一个。 * 精度可影响内部存储的宿主类型。 * 若字面量指定的数值小于或大于使用的类型的数值表示范围,则值为对应类型具有相同符号的零值或[无限大值](#数值类型)。 * 匹配正则表达式 `(+|-)(inf|nan)\.(0|f|t)` :带符号的 flonum 特殊值。 * 其中,`inf` 指定无限大值,`nan` 指定 NaN 值。 * 后缀指定精度: * `0` :默认精度。 * `f` :单精度。 * `t` :扩展(extended) 精度。 * 以上精度中,默认精度不低于双精度,扩展精度不低于默认精度。   除非派生实现另行指定,以上要求外的数值的子类型和内部表示未指定。 **原理**   浮点数解析存在不同精度的算法。   若以二进制浮点数和经过舍入的十进制表示相互转换不损失精度为前提,宿主语言的 [`std::numeric_limits`](http://eel.is/c++draft/numeric.limits) 的 [`max_digits10`](http://eel.is/c++draft/numeric.limits.members#14) 位十进制数字足够表示。   (对 \[IEC 60559] 的二进制浮点数情形的证明参见[这里](https://www.itu.dk/~sestoft/bachelor/IEEE754_article.pdf)的 Theorem 15 。)   但是,对任意有效输入的结果误差都不大于 1 [ULP(unit in the last place)](https://zh.wikipedia.org/zh-cn/%E6%9C%80%E5%90%8E%E4%B8%80%E4%BD%8D%E4%B8%8A%E7%9A%84%E5%8D%95%E4%BD%8D%E5%80%BC) 的不经舍入的值完全精确值(full precision) 的精确解析算法,对实现的要求较高,且性能可能明显较低,故不作要求。   (对 \[IEC 60559] 的二进制浮点数的情形,需要数十倍的中间存储,参见[这里](https://stackoverflow.com/questions/554063/62542806)。)   和宿主语言的 `std::strtod` 不同,允许使用宿主语言中的任意浮点数舍入模式,而不要求不同浮点数舍入模式下的结果一致性。   浮点数精度的 `float` 和 `double` 在典型实现中的内部表示格式同 \[IEC 60559] 的二进制的单精度和双精度浮点数。 **注释**   串模式 `(+|-)` 表示带有可选前缀符号(仅限一个),影响值的数值。   同 klisp 而不同于 \[RnRS] 的字面量词法,小数点不能出现在词素中符号以外的第一个字符;但 klisp 的 `string->number` 没有这个限制。   同 \[RnRS] 而不同于 klisp(两者包括字面量词法和 `string->number` ),小数点允许出现在词素的结尾。   当前不精确数数值都具有宿主类型 `double` 。即便 `long double` 可能具有更大的数值范围,也不能通过解析数值表示直接取得。   类似地,\[Racket] 默认不启用 [extflonum](https://docs.racket-lang.org/reference/extflonums.html)。关于数值操作也类似,参见[数值操作约定](#数值操作约定)。   当前允许在[宿主值](#类型映射)不能完全存储不精确数的字面量数字时,解析十进制不精确数字面量存储的值可能和字面量的数值的真值之间具有超过 1ULP 的误差。这可能影响和精确数之间的比较。   当前实现使用四舍五入。   关于宿主语言中 [`std::strtod`](http://www.eel.is/c++draft/cstdlib.syn#lib:strtod)(同 \[ISO C] 标准库的 `strtod` )舍入要求的一些问题,参见: * [Incorrectly Rounded Conversions in GCC and GLIBC](https://www.exploringbinary.com/incorrectly-rounded-conversions-in-gcc-and-glibc/) * [Incorrect rounding in `strtod()`](https://www.sourceware.org/bugzilla/show_bug.cgi?id=3479)   数值字面量的词法同 \[RnRS] 的一个子集。   \[RnRS] 指出实现可能允许用户修改不同的默认精度。这指出精度不是固定的,但不是实现要求。   \[RnRK] 的有限数的对应子集接近 \[RnRS] 的设计,但没有明确指定字面量词法规则。其中对精度的表述略有不同: * 没有指定大写字母。 * 指定 `s` 、`f` 、`d` 和 `l` 的精度递增,没有显式允许不同的精度映射到相同的内部格式。 * 没有显式允许用户指定的默认精度。   但是,SINK 和 klisp 实际上都不符合前两点,而更符合 Scheme 的实现。\[RnRK] 在此可能不完善或表述有误。   当前 klisp 的实现不允许 `E` 和 `e` 之前没有小数点,且在存在 `E` 或 `e` 时省略之后的指数的任意部分,和 SINK 以及 \[RnRS] 都不同。本设计遵循后者。   当前 NPLAMath 精度对应的宿主类型指派如下: * 非特殊值: * `e` :同 `d` 。 * `s` :同 `f` 。 * `f` :`float` 。 * `d` :`double` 。 * `l` :`long double` 。 * 特殊值: * `0` :`double` 。 * `f` :`float` 。 * `t` :`long double` 。   指定特殊值的精度的[词素](#基本词法构造)语法兼容 \[Racket] 。 # NPLA1 核心语言   NPL 是独立设计的,但其派生语言和其它一些语言有类似之处;这些语言和 NPL 方言之间并不具有派生关系。但为简化描述,部分地引用这些现有[语言规范](../Terminology.zh-CN.md#非自指)中的描述,仅强调其中的不同。   NPLA1 符合 NPL 和 NPLA 的语言规则,其[实现环境](../Terminology.zh-CN.md#程序设计语言)还应提供本章起的其它程序接口。   互操作中的一些接口处理的值可约定[宿主类型](#类型映射)。但这些类型不一定在[对象语言](../Terminology.zh-CN.md#程序设计语言)层次上稳定,可能在之后的版本变化。稳定性由具体实现提供的附加规则(若存在)保证。   NPLA1 和 Kernel 语言(即 \[RnRK] )的特性设计(如 [vau](#vau-抽象)和作为[一等对象](#基本语义概念)的环境表达[过程抽象](#过程))有很多类似之处,因此许多概念是通用的;但从[设计哲学](#领域设计原则)到本章介绍的各个细节(如[默认求值规则](#对象语言求值算法))都存在深刻的差异。   部分名称指定的操作和 \[RnRS] 或 [klisp](http://klisp.org/docs/index.html) 指定的类似。   以下章节主要介绍和 Kernel 约定不同的设计。各节的通用约定不再在之后的各个接口单独说明。 ## NPLA1 对象语言约定   NPLA1 仅使用[宿主语言](#npla-实现环境)的[类型](#类型)和[值](#基本语义概念)作为在对象语言可表达的[状态](#基本语义概念)。   在 [NPLA](#npla) 的基础上,NPLA1 要求对象语言支持以一等对象作为表达式并被[求值](#基本语义概念)。   [类型等价性](#类型等价性)基于[类型映射](#类型映射)及其实现,由 \[ISO C++] 的语义规则定义。   值等价性由[宿主环境](#嵌入宿主语言实现)的 `==` 表达式的结果定义。   除非另行指定,所有类型的[外部表示](#表示)都是允许从作为内部表示的[项节点](#项的子对象)确定的同[宿主类型](#类型映射)的空字符结尾的字符串(即 \[ISO C++] 的 [NTCTS](https://eel.is/c++draft/defns.ntcts) )。   关于作为表达式的求值和类型映射的实现,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 ### 标识符附加规则   当前仅支持[标识符](#基本词法构造)作为[名称](#名称)。   部分名称是[保留名称](#npla-名称和字面量求值):含有 `$$` 的名称保留给宿主交互使用;含有 `__` 的名称保留给 NPLA1 实现。   在 [NPLA 规则](#npla-整体约定)的基础上,在[内部表示](#表示)中显式使用保留给实现的标识符的程序[行为未定义](#npla-未定义行为)。 **注释** 这包含在源代码以外的中间表示使用的情形,但不包含作为用户输入的数据。 ## NPLA1 互操作约定   基本规则参见 [NPLA 互操作支持](#npla-互操作支持)。   非 NPLA1 实现提供的类型的宿主 `==` 操作不要求支持[嵌套调用安全](#嵌套调用安全)。   作为 NPLA1 嵌套调用安全的约定的扩展,若存在 == 操作不支持嵌套调用安全的类型,具体类型由派生实现的定义。   对象语言操作和互操作不修改对象语言中已经可见的[一等环境](#npla-环境)的[父环境](#npla-环境)。 **原理**   NPLA1 中提供的类型仍需要支持嵌套调用安全,以满足嵌套调用安全的约定中的要求。   关于 NPLA1 嵌套调用安全的具体约定和其它实现原理,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。   避免修改已在对象语言可访问的一定对象的父环境符合同 \[RnRK] 的环境[封装性](#封装)对对象语言的要求。这允许实现假定仅在有限的上下文中父环境可修改,而减少优化实现的难度。 ## NPLA1 程序实现   本章指定 NPLA1 对象语言的[核心语言特性](#程序实现)。包含[库特性](#程序实现)的其它设计参见 [NPLA1 参照实现环境](#npla1-参照实现环境)。 **原理**   一般的语言能支持不同实现形式的库,包括[源程序](../Terminology.zh-CN.md#程序设计语言)和其它无法判断是否和特定源程序关联的翻译后的程序。   复用这些程序时,可能需要根据不同的形式而分别处理:源代码被读取和[求值](#基本语义概念)而加载,而其它格式的翻译形式可能直接映射存储后经特定的检查即被加载。   但是在可复用的意义上,这些不同的形式是一致的,都视为库。 **注释**   和 \[RnRK] 不同,库不限定其实现形式。\[RnRK] 指定的库实质上是可使用对象语言派生实现的库。   典型的静态语言不保证程序执行时能对源程序进行翻译,因此加载程序的限制通常更大,可能无法处理源程序形式的库而首先需要分离翻译为其它格式。NPL 一般不具有这个限制。   关于对象语言的[派生实现](#略称),参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 ### NPLA1 程序外部环境   基于 [NPLA 整体约定](#npla-整体约定),由 [NPL-EMA](#嵌入宿主语言实现) ,NPLA 的实现不假定存在多线程执行环境。   但是,宿主语言可支持多线程环境执行,[可引起宿主语言的未定义行为](#npla-并发访问)。   作为 NPLA 的派生,NPLA1 对象语言程序也具有相同的性质,除非另行指定需要和[外部环境](../Terminology.zh-CN.md#程序设计语言)交互的特定操作,不需要假定 NPLA1 引入存在多线程执行环境。 ### 附加元数据   NPLA1 实现可提供和[实现环境](../Terminology.zh-CN.md#规范)或具体 NPLA 对象关联的附加的资源,用于提供程序运行时可得到的附加信息,如源代码位置。   是否存在这些*附加元数据(extra metadata)* 和附加元数据的具体内容可影响特定的行为。 **注释** 如符合[诊断](#诊断)中要求的实现的具体行为。   这些影响是未指定的,但除 NPLA1 程序直接依赖具体数据而进行的操作外,不应影响程序的其它语义(例如,引起程序终止)。 ### NPLA1 扩展支持   本章中除[循环引用](#循环引用)的限制外,不支持的特性可能会在之后的实现中扩展并支持。 ### NPLA1 未定义行为   NPLA1 对象语言程序中的[未定义行为](../Terminology.zh-CN.md#程序设计语言)包括 [NPLA 未定义行为](#npla-未定义行为)和以下扩展 NPLA 未定义行为: * 特定情形下[访问被修改的环境中绑定的对象](#npla1-环境)。 * 特定情形下违反对环境的使用要求。 * **注释** 参见[环境的稳定性](#环境的稳定性)。 * 特定情形下违反对外部实现环境假定的使用要求。 * **注释** 参见[模块的初始化和加载](#模块的初始化和加载)。注意虽然可能通过环境实现,但这在接口的意义上独立于环境的稳定性。 * NPLA1 库约定的未定义行为。 * **注释** 有些未定义行为不需要被显式约定,例如不支持的并发访问[可引起宿主语言的未定义行为](#npla1-程序外部环境),属于 NPLA 未定义行为。   派生语言可约定其它未定义行为。 **原理**   扩展 NPLA 未定义行为可提供更严格的要求使实现更简化。   关于环境的一些未定义行为可视为违反[内存安全](#内存安全),而不需要单独实现。 ## 接口文法约定   为描述对象语言规则和程序接口,本节约定[文法](../Terminology.zh-CN.md#程序设计语言)形式。 **注释** 这仅用于描述接口,不依赖 NPL 语言的[基本文法](#基本文法)。   元语言文法: ```xbnf ::= < ::= > ::= ::= . ::= ? | ... ::= | | ::= * ::= ( ::= )
:= | ```   规约操作中[项](#基本语义概念)的约束通过具有同类[名称](#基本语义概念)(即 `` 元素)的前后以 `<` 和 `>` 作为边界的文法元素表示,即 `` 文法元素。   为区分同类约束的不同项,约束的名称后(在 `>` 之前)的可带有以 1 起始的正整数序数。除非另行指定,这些序数仅用于区分不同的同类约束项,无其它附加含义。   本节描述的项是被用于[求值](#基本语义概念)(参见[求值算法](#对象语言求值算法))的[项](#基本语义概念)或它们的直接文法组合。前者应能涵盖[原子表达式](#原子表达式)、其[求值结果](#基本语义概念)以及预期在对象语言中实现[对象语言求值算法](#对象语言求值算法)所需的 [NPLA1 用户程序](#程序实现)构造。   库可参照本节的方式约定通过项的文法,以支持仅在特定库使用的操作数。   除非另行指定,本节的对应要求同时适用于本节中和这些库中引入的项。 ### 元文法基本约定   描述接口的[元语言](../Terminology.zh-CN.md#程序设计语言)的文法具有以下含义: * 表达操作的文法是 `` ,表示匹配项的文法元素 `` 或 `` 的序列。 * 当 `` 不存在具有非空 `` 的 `` 时,序列是[真列表](#广义列表)。 * `.` 仅在序列的最后一个文法元素中出现,表示后继的项和可选的文法元素后缀是有序对的最后一个元素。 * 文法元素后缀的含义如下: * `...` :Kleene 星号,重复之前修饰的 `` 0 次或多次。 * `+` :重复之前修饰的 `` 1 次或多次。 * `?` :重复之前修饰的 `` 0 次或 1 次。 **注释** `...` 一般在结尾出现,表示元素构成列表。   和 \[RnRK] 不同,不使用 `.` 分隔有序对,不使用元素名称的复数表示列表。 ### 实体元素文法约定   指定具名的[函数](#函数)的文法中,第一项以[符号值](#符号)的形式在所在的环境中提供,指定求值结果指称为[合并子](#合并子)的函数的[名称](#基本语义概念);其后指定的文法中不同的元素对应合并子的[操作数](#规范化中间表示)或其被作为调用时的[形式参数树](#绑定操作)的子项。   除非另行指定,在操作数可能是左值时,仅当对应以 [`...` 或 `?` 形式](#元文法基本约定)中最后的一项(若存在)时,支持匹配作为[被引用对象](#一等引用)的有序对的非[前缀元素](#广义列表)不是空列表的[引用值](#引用值)的情形。   名义不同的约束可能蕴含相同的[检查](#错误检查)。   除非另行指定,应用子的操作数的约束也适用其[底层合并子](#合并子)。 **注释** 这意味着意味着按求值算法,被求值的应用子的[函数合并](#函数合并)对象不能是[非真列表](#广义列表)。特别地,`...` 形式的结尾序列一般被要求是真列表,除非是其底层操作子被求值且具有存在 `.` 前缀的最后一个文法元素。   文法形式上,使用本节约定指定[应用子的操作数](#求值得到的操作数)时,指定表达式形式的求值结果。 **注释** 这和 \[RnRK] 和 \[Shu10] 中的斜体标识符的标记不同,但含义(表示语义变量(semantic variable) )和效果实质相同。   操作数可能是左值或右值,按具体操作的需要,在必要时可被转换。   除可能具有的[子类型](#类型序)关系,本节约定的不同类型的操作数构成的集合之间不相交。一般规则参见[类型分类](#类型分类)。   根据是否可作为操作子中指定不被求值的函数参数,本节的操作数及其[子项](#基本语义概念)分为[未求值的操作数](#未求值的操作数)和[求值得到的操作数](#求值得到的操作数)。 **原理**   约束可用于区分特定的含义,但不直接指定和具体的[检查](#错误检查)对应,以便被实现优化,例如合并名义不同的检查。   文法形势的匹配应避免歧义。 **注释**   对非[前缀元素](#广义列表)的支持和[绑定匹配](#绑定匹配)规则对应。其中: * 仅[结尾序列](#绑定匹配)支持匹配以空列表以外的值的引用值作为非前缀元素的[有序对](#有序对)操作数左值的被引用对象中的非前缀元素[子对象](#子对象)。 * 非结尾序列的元素因计算前缀元素数而被要求在同一个对象的前缀元素中。 * 作为操作数被绑定时,若元素是引用值: * 它不被[消除](#引用值的消除)。 * 它的被引用对象中的元素(若存在)不被视为初始化[绑定构造](#绑定构造)中的其它[被绑定对象](#环境对象)的子对象。 * 若有序对操作数的非前缀元素是空列表的引用值,则有序对操作数构成列表。 * 有序对操作数的非前缀元素不会匹配[形式参数树](#绑定操作)中的[符号值](#符号)而被绑定到变量。 * 关于操作数匹配的规则避免匹配操作数序列时对文法元素的对应关系可能具有歧义。 #### 未求值的操作数   未求值的操作数的文法约定如下: * `` :[符号](#符号)。 * **注释** 内部使用和 [``](#求值得到的操作数)一一对应的表示,不提供符号和外部表示的其它映射关系。 * `` :元素为 `` 的列表,形式为 `(...)` 。 * `` :表示可选提供的环境名称的 `` 或 [`#ignore`](#npla1-扩展字面量) ,或这些值的引用值。 * **注释** 通常为动态环境。 * `` :待求值的表达式。 * **注释** 这是 [NPL 语法](#表达式)的直接实现。作为右值,它是词法元素的[值](#基本语义概念),或这些元素的[真列表](#npla1-广义列表)。 * `` :形式为 `...` 的待求值形式。 * 求值时,`` 被作为单一表达式(即视为求值 `(...)` )。 * `` :绑定列表的元素,形式为 ` ` ,用于指定被求值的表达式和绑定参数的符号值。 * 和 Kernel 不同,`` 后不要求是整个 `` 。 * `` 绑定列表,形式为 ` ` ,用于指定被求值的表达式和绑定参数的符号值。 * `` :绑定列表,即元素为 `` 的列表,形式为 `(...)` 。 * ``: 出现在元素的结尾 `` 形式。 * **注释** 一般用于[函数体](#函数)等替换求值的目标。 * `` :同 `...` 但蕴含顺序求值其中的子项。 * 求值 `` 的结果是求值其最后一个子表达式(若存在)的结果,或当不存在子表达式时为[未指定值](#基本语义概念)。 * `` :同 `` ,仅用于 [``](#求值得到的操作数) 求值结果经[左值到右值转换](#值类别转换)不为 `#f` 时。 * `` :同 `` ,仅用于 `` 求值结果经左值到右值转换为 `#f` 时。 * `` :形式参数树,是包含符号值或 `#ignore` 及其它形式参数树构成的 [DAG](#自引用数据结构和循环引用) 的表达式。 * 语法要求由上下文无关文法描述:` ::= | #ignore | () | (...)` 。 * `` :被绑定项的目标的 `` 。 * 引入绑定时,蕴含按[绑定规则](#绑定操作)附加 `` 以外的[绑定匹配](#绑定匹配)等语义检查。 * `` :作为形式参数的 `` 。同 `` 但允许[派生实现](#略称)定义更多检查。 * `` :元素为条件分支的列表,形式为 `( )...` 。 * `` :[变量](#基本语义概念)。用于表示被[声明](#基本语义概念)的名称。 * 同 `` ,其中的处理与作为非列表的 `` 相同。   关于 `eval` ,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   以 `` 代替 `` 可避免语法中要求过多的括号及 `eval` 等求值形式中显式构造列表的需要。   因为 `` 存在元素的结尾,明确元素中的其它词法元素后即可自然确定边界。   `` 可以是多个表达式的词法组合,允许具体使用时不需要附加括号即可实现整体求值。   特别地,作为其它 `` 嵌套的 `` 实例在这种情况下,可以更有效地减少嵌套一层以上的括号。   `` 整体求值的一个必要条件:构成 `` 的表达式不被以其它方式分别求值,如蕴含[顺序求值](#文法元素补充约定)。 **注释**   和 \[RnRK] 不同,`` 可以是多个表达式的词法组合。   尽管 `` 不保证可直接构成一个表达式(而是构成某个表达式的所在元素中的多个子表达式),一般仍被作为一个整体求值。   被整体求值时,这些表达式被视为某个假想的表达式,这个表达式包含被整体求值的表达式作为子表达式。   若 `` 存在超过一个子表达式,按求值算法的 [NPLA1 规范求值算法步骤](#npla1-规范求值算法),表达式分别作为合并子和之后的参数。   若 `` 不存在子表达式,则结果是 `()` 而不是 `#inert` 。这和 \[RnRK] 的经重定义而隐含 `$sequence` 的 `$vau` 以及 `$let` 等合并子不同,但和 `eval` 仍然相同。 #### 求值得到的操作数   求值得到的操作数的文法约定如下: * `` :一般对象,包括引用对象的[引用值](#npla1-引用)。 * `` :对象引用值。 * `` :[有序对](#npla1-广义列表)。 * **注释** 可构成[真列表或非真列表](#npla1-广义列表)。 * `` :[列表](#npla1-广义列表):空列表或第二个元素为空列表的有序对。 * `` :元素都是列表的列表。 * `` :[布尔值](#基本语义概念),值为 `#t` 或 `#f` 的集合。 * 是[类型映射](#类型映射)指定的用于条件判断的单一值的类型。 * 推论:`` 对应的宿主类型是 `bool` 。 * `` :类似 `` ,通常预期为 `` ,作为条件。 * 当[求值结果](#基本语义概念)非 `#f` 时条件成立。 * **原理** 和 Scheme 类似但和 Kernel 不同,非 `#t` 的值在决定分支时视同 `#f` ,以允许在 `` 外自然扩展的逻辑代数操作。 * **原理** 和 Common Lisp 不同,不使用空列表(或符号值 `nil` )代替 `#f` ,以避免需要特设的规则以特定的其它类型的值(如 Common Lisp 的符号值 `t` )表示逻辑真(这在逻辑非操作中不可避免)。 * `` :[合并子](#npla1-合并子)。 * `` :[应用子](#npla1-合并子)。 * `` :[操作子](#npla1-合并子)。 * `` :[谓词](#基本语义概念),是应用操作数的求值结果的值为 `` 的 `` 。 * **注释** 通常实现结果是 `` 的[纯求值](#求值性质)。 * `` :[一等环境](#npla-环境)。 * `` :指定环境的[父环境](#npla-环境)的值,包括: * 环境引用值:`` 或以 `` 值作为被引用对象的 `` 。 * 元素为环境引用值的 `` 。 * [被引用对象](#一等引用)是元素为环境引用值的 `` 的 `` 。 * `` :字符串。 * 字符串是包括[数据字面量](#npla-名称和字面量求值)作为表示的值的类型。 * 字符串的内部表示在具体实现中保持一致。除非另行指定,使用 [ISO/IEC 10646](https://www.iso.org/standard/76835.html) 定义的 UCS 的 UTF-8 编码,其值不包含空字符(编码数值为 0 的 UCS 代码点)。 * 关于当前实现,另见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 * **注释** 为[互操作](#npla1-互操作约定)的兼容性,一般建议实现使用兼容 \[ISO C++] 中定义的 [NTMBS(null-terminated multibyte string)](https://eel.is/c++draft/multibyte.strings#2) 的方式表达。 * 此外,支持的数值操作数参见 [NPLA 数值类型](#数值类型)。 **原理**   `` 等求值得到的操作数不保证是语法意义上连续的词法组合,不能由多个表达式构成,因此即便出现在元素末尾,也不能如 `` 一样减少括号。   `` 作为[类型全集](#类型全集),其元素可被断言在[论域](#开放性)内,即任何其它类型都是 `` 的[子类型](#类型序)。[类型检查](#类型检查)可对此进行验证。   和 \[RnRK] 的理由不同,允许布尔代数以外扩展的比较判断在此不认为是易错的,而是有意的设计(by design) 。这避免预设地假定类型的名义语用作用(“角色(role) ” ),也避免限制语言和派生语言的类型全集的设计。 **注释**   空列表构成的[单元类型](#类型)是真列表的子类型,而不是有序对的子类型。   非空真列表是有序对的子类型。 #### 文法元素补充约定 * 除非另行指定,以 `` 指定的值被作为 `` 或 `` 使用时不引起错误。 * **注释** `` 在被其它上下文使用时仍可能引起错误。 * `` 形式的符号列表在绑定[变量名](#npla-环境)时支持引用标记字符 [`&` 和 `%`](#引用标记字符)。符号作为被绑定的初值符时,移除符号中发现的这些引用标记字符。 * `` 和 `` 不要求重复符号值检查。另见[绑定操作](#绑定操作)。 * 使用 `` 的情形包括合并子基本操作和可通过这些操作派生的操作在对应位置的操作数。 * **原理** \[RnRK] 和 \[Shu10] 都没有显式区分 `` 和 `` ,两者在上下文中实质可互换,差别仅在 \[RnRS] 中的 define 形式中定义的位置可具有和 `` 不兼容的扩展形式。 * **注释** 这实质等价使用 \[Shu09] 中的记法,即 `` 用于除和 \[Shu09] 的 `$define!` 类似外的所有操作(包括 `$set!` 和 `$let` 等,而不论是否对应 `` )。这些上下文中总是隐含了上述的可派生实现的要求。 * `` 不蕴含顺序求值子项。 * **原理** 这也允许 `` 中的表达式被[整体求值](#未求值的操作数)。   关于合并子基本操作,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   和传统 Lisp 方言(包括 \[RnRS] 和 \[RnRK] )的函数体不同,`` 的各个表达式之间不蕴含顺序求值。   因此,和 \[RnRK] 不同,`$vau` 不需要在基本操作之后再次派生。这使操作的功能更加正交。 **注释**   和 \[RnRK] 不同,`` 、`` 和 `` 具有一些附加的约定支持;`` 不蕴含顺序求值子项;NPLA1 的符号可通过[代码字面量求值](#npla-名称和字面量求值)得到。 ## NPLA1 对象语言语法   基于 NPLA 基本语法约定参见 [NPLA 整体约定](#npla-整体约定)。   NPLA1 表达式符合 [NPL 表达式语法](#表达式)。 **注释**   [NPL-GA](#简单文法约定)包含的[转义规则](#词法分析)中包含 `` 的 `` 要求类似 \[R6RS] 在字符串中的元素;其中仅有 \\ 和 \" 被 \[R5RS] 直接支持,而 \[R7RS] 不支持 \v 。   后者支持的其它转义字符序列词法可被派生实现以 `<$literal-char>` 的形式另行指定(其中 \[R7RS] 可涵盖对应 `` 的功能)。 ### NPLA1 字面量   基于 [NPLA 词法规则](#npla-词法和语法),本节指定[字面量](#字面量)的词素集合。   派生实现可指定不同的字面量,但不应和已指定词法构造的记号冲突,包括本节指定的字面量。   NPLA1 字面量都是[纯右值](#值类别),但总是允许[实质化转换](#值类别转换)为消亡值并引入允许[互操作](#npla1-互操作约定)的[临时对象](#临时对象)。 **注释** 这和宿主语言的字符串字面量是左值不同。当前 NPLA1 对象语言不提供能引起互操作差异的接口(字符串字面量不被修改),以后可能改变。 #### 字符串字面量   字符串字面量的类型为 [``](#求值得到的操作数)。 #### NPLA1 数值字面量   基于 [NPLA 数值类型](#数值类型)和[数值字面量](#npla-名称和字面量求值),NPLA1 数值字面量的类型为 `` 。   除非另行指定,数值的具体宿主类型未指定。 **注释** 部分数值可指定具体的[子类型](#类型序)。   NPLA1 支持 `` 类型的精确数数值字面量和 [flonum](#数值类型) 不精确数数值字面量。   支持的字面量包括词素符合 NPLAMath [数值表示](#数值表示)的字面量。   派生实现可定义其它数值字面量。 **注释**   以上字面量包含十进制数值的字面量。其它字面量是 [NPLA 扩展字面量](#npla-扩展字面量)。   无限大值和 NaN 值同 [\[Racket\] 的字面量词法](https://docs.racket-lang.org/reference/numbers.html),除这些类型总是启用,且使用明确属于 flonum 且对应明确宿主类型的 long double 代替不被作为一般 flonum 的 extflonum 。   [\[SRFI-73\](已撤消)](https://srfi.schemers.org/srfi-73/srfi-73.html)提出扩展 \[R5RS] 的带有 `#e` 或 `#i` 前缀的精确数和不精确数无限大值字面量,其中[前缀及精确数的支持](#数值类型)和 \[RnRK] 类似。NPLA1 不支持无限大值精确数。   关于无限大值的在 \[RnRS] 的一些实现情形,另见[这里](https://people.csail.mit.edu/jaffer/III/RAWI)。 #### NPLA1 扩展字面量   NPLA1 支持 [NPLA 扩展字面量](#npla-扩展字面量)作为部分数值字面量。   NPLA1 还支持以下以 `#` 起始的扩展字面量: * `#t` :[布尔值](#基本语义概念)逻辑真,类型为 `` 。 * `#f` :布尔值逻辑假,`` 。 * `#true` :同 `#t` 。 * `#false` :同 `#f` 。 * `#inert` :类似 Kernel 的 `#inert` 字面量, * `#ignore` :类似 Kernel 的 `#ignore` 字面量。 **原理**   `#inert` 和 `#ignore` 类似 \[RnRK] 。   从表达上,`#inert` 和 `#ignore` 仍都可以被视为特定[单元类型](#类型)的值:等价的类型判断谓词可以直接使用值的相等关系确定。   和 \[RnRS] 及 klisp 不同,不需要因兼容性支持扩展字面量中不同的大小写变体,特别是 \[R6RS] 的 `#T` 和 `#F` 。   和 \[RnRS] 类似而和 \[RnRK] 不同,NPLA1 表达[结果](#规范化中间表示)结果通常不依赖 `#inert` ,而直接使用[未指定值](#基本语义概念)。这避免用户必须引入 `#inert` 等具体的值实现相同隐式效果而违反[关注点分离原则](#关注点分离原则)。   尽管在接口意义上通常是不必要的,若有需要(如派生结果等效 `#inert` 的操作),`#inert` 的值仍可被显式使用。 **注释**   \[R5RS] 和 \[RnRK] 指定 `#t` 和 `#f` 。\[R7RS] 指定同义的 `#true` 和 `#false`(参见 [R7RS ticket 219](https://small.r7rs.org/ticket/219/) )。   后者被认为可提供若干可读性,但[具有冗余](https://small.r7rs.org/ticket/526/)。本文档中,以下不使用 `#true` 和 `#false` 替代 `#t` 和 `#f` 。   派生实现可扩展支持,提供非 `` 类型的布尔值,使用与这些字面量不同的对应表示。 ### NPLA1 函数合并   以下使用 [`...`](#元文法基本约定)作为函数的操作数时,可支持没有操作数的[函数合并](#函数合并)。此情形下应用表达式仍需要前缀 `()` ,但不在以下规约文法中显式地表示。 **注释**   和 Scheme 及 Kernel 不同,求值算法决定求值为[函数合并表达式](#函数合并)的语法表达不需要括号,且具有不同的[函数合并形式](#函数合并求值)。 ## 对象语言内存安全保证   对象语言可能提供关于[内存安全](#内存安全)的检查。   除非另行指定,假定实现进行[互操作](#npla1-互操作约定)无法保证内存安全。 ### 对象语言基本内存安全保证   对象语言提供关于内存安全的基本保证:不存在违反内存安全相关的要求以外的未定义行为(包括[循环引用](#循环引用)等)、不存在不保证内存安全的互操作且不存在[不安全间接值访问](#不安全间接值访问)时,对象语言的程序执行保证内存安全。   [非内存安全操作](#非内存安全操作)在对象语言中以不安全间接值访问的一部分情形体现。 ### 不安全操作   *不安全(unsafe)* 操作是可能在程序的执行中引入[未定义行为](#npla1-未定义行为)的[操作](#规范化中间表示)。   这里的未定义行为包含在操作中直接引入的未定义行为,以及因为操作被执行而使程序在之后无法确保排除的未定义行为。   不安全操作是实现可选提供的。   当前对象语言不支持[并发访问](#npla-并发访问)对象。数据竞争仅可由和宿主语言的互操作引入。 ### 不安全间接值访问   对象语言的不安全[间接值访问](#间接值)包括: * 通过[无效的间接值](#间接值)的间接访问。 * 通过[不安全引用值](#不安全引用值)的间接访问。 * 通过可能由实现定义的其它不安全间接值访问。 #### 无效的环境引用   环境对象被销毁导致[作为间接值的环境引用](#环境间接值)被[无效化](#间接值)。 **注释**   另见[环境生存期](#环境生存期)。 #### 无效的引用值   [作为间接值的引用值](#引用间接值)是间接值的实例,因此无效的间接值包含无效的[引用值](#引用值),通过无效间接值访问包括无效的引用值的访问。   对象语言不提供[悬空引用](#引用值的有效性)以外构造无效引用值的操作。   对象语言中可引入悬空引用的情形包括: * 调用可能返回引用值的合并子,且没有另行保存函数体求值所在的[当前环境](#当前环境),[返回值](#函数调用)对应的对象不在生存期内: * 绑定到形式参数的[右值](#值类别)保存在过程调用的[局部环境](#函数和函数应用的求值环境)中,退出[函数体](#函数)的求值,局部环境被释放后,返回的其引用是悬空引用。 * 实现等效上述情形的派生操作的使用,如: * [绑定列表](#未求值的操作数)[传递引用](#绑定操作)且[传递的引用值被求值](#npla1-引用值使用约定)使用。 * 间接保留引用值可能提供变量绑定,这些绑定的目标对象可能依赖环境(如合并子的[静态环境](#函数和函数应用的求值环境)),被销毁时访问被依赖的环境(如合并子调用求值函数体)。   关于间接保留引用值和互操作可能引入悬空引用的情形,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **注释**   另见[对象语言的引用值](#npla1-引用)。 #### 其它无效的间接值   使用其它不保证内存安全的操作可引入[不具有内存安全保证的间接值](#项对象和关联对象所有权)访问实体。   这些间接值可能因为和[悬空引用](#引用值的有效性)相同的情形[无效化](#间接值)。 ### 保留间接值   对象的(直接或间接)[子对象](#npla1-子对象)是间接值时,对象包含间接值。   [修改](#对象的修改和改变)对象为间接值或使之包含间接值时,对象保留间接值。   被保留的间接值是对应的通过修改得到或包含的间接值。   本节中的概念对应适用于具体的间接值,如被保留的引用值和函数[在结果中保留引用值](#被保留的引用值的目标)。 #### 被保留的引用值的目标   [函数调用](#函数调用)返回(在对象语言中允许出现的,下同)间接值或包含间接值的对象时,在[函数值](#函数合并)中保留间接值。   函数调用修改环境使[环境对象](#环境对象)保留间接值(绑定间接值或包含间接值作为子对象的对象作为[被绑定对象](#环境对象))时,在环境中保留间接值。   函数调用修改[一等对象](#基本语义概念)或其子对象,使之保留间接值时,在对象中保留间接值。   在函数值中保留间接值、在环境中保留间接值、在对象中保留间接值的函数保留间接值。   被保留的间接值被函数调用的[求值结果](#基本语义概念)蕴含时,函数在结果中保留间接值。 **注释**   函数调用的[求值结果](#求值和对象所有权)排除[副作用](#基本语义概念)即函数值。   在结果中保留间接值包含以下情形: * 在函数值中保留间接值。 * 在环境中保留间接值,环境是函数值或其子对象。 * 在对象中保留间接值,对象是函数值的子对象。 #### 被保留的引用值的来源   函数返回包含间接值的对象由参数的值决定时,保留参数中的间接值。   按被保留的间接值的来源,这分为以下两个子类: * 直接保留间接值:接受间接值参数。 * 间接保留间接值:接受的参数或参数在特定环境中被求值得到的结果决定是否直接保留间接值。 * **注释** 如合并子或构成[形式参数树](#绑定操作)的可能带有[引用标记字符](#引用标记字符)的[符号值](#符号)。 #### 保留间接值的操作   操作可保留间接值: * 使用函数调用实现的操作可通过[函数调用](#函数调用)保留间接值。 * 其它实现方式可等效地保留间接值。 **注释**   在结果中保留间接值的操作不区分被保留的间接值的来源和目标。但多数情形下,这通过函数值保留参数中的间接值蕴含。 #### 保留间接值和内存安全   保留间接值操作的内存安全的一个必要条件是所有被保留的间接值在之后的使用中都满足内存安全。   保留间接值在操作后可能因[间接值无效](#间接值)(如[悬空引用](#引用值的有效性)),无法继续保证内存安全。   在环境中保留间接值时,应保证环境具有足够的生存期,以避免间接值依赖无效的环境引用导致访问环境中对象的未定义行为。 ### 对象语言接口的安全保证机制   对象语言接口的安全保证机制提供不同接口的分类,通过允许区分是否具有内存安全保证的接口帮助程序利用[对象语言基本内存安全保证](#对象语言基本内存安全保证)。   通过避免或限制使用[不安全操作](#不安全操作),实现上述安全保证。   因为允许引入 [NPLA 未定义行为](#npla-未定义行为),无法提供安全证明的[互操作](#npla1-互操作约定)应视为不安全操作。   基于[求值算法](#对象语言求值算法)的安全保证的非形式的证明框架概述如下: * 任意步骤中,访问间接值指定的目标对象是安全的,仅当间接值是安全的。 * [符号值](#符号)的求值是安全的,仅当引用的环境是安全的。 * 合并子调用的求值是安全的,仅当合并子、操作数及调用的操作是安全的。 **原理**   满足安全保证的推理如下: * 因为 NPLA 实现的非互操作引入的、非求值规约的[管理规约](#管理规约)不存在未定义行为,以上求值算法中的步骤中通过排除不安全的实体能保证规约中不存在未定义行为。 * 因为[规约决定程序执行的语义](#规约规则和求值),在求值中排除不安全的实体可以保证不存在未定义行为,而满足安全保证。 #### 安全性附加证明   一些不安全操作是否蕴含未定义行为可能依赖具体调用使用的操作数。   若能证明特定的前提保证任意的调用实例中的操作数满足附加的安全假设,则这些不安全操作的调用仍可保证安全。   排除不确保安全性假设的互操作时,NPLA1 提供附加调用安全:若不存在[隐藏环境](#隐藏环境)中绑定的可修改对象的引用,则仅因可能违反[值稳定性](#被绑定对象的值和可观察行为)的不安全操作的调用是安全的。   派生实现可对特定调用附加使用限制以便提供证明,或定义其它的调用并提供更强的保证。 ## 诊断   NPLA1 的特定[求值步骤](#对象语言求值算法)可引起[诊断](../Terminology.zh-CN.md#程序设计语言)。   引起诊断时[求值](#基本语义概念)被终止,或在失败可被恢复时以其它派生实现定义的方式继续求值。   其它引起诊断的条件可被派生实现补充指定。 **注释** 注意[未定义行为](../Terminology.zh-CN.md#程序设计语言)取消对诊断的要求。   本节以外的[诊断消息](../Terminology.zh-CN.md#程序设计语言)的其它形式未指定。 **注释**   引起诊断的求值包括: * [抽象求值](#基本语义概念)的失败。 * 例如,REPL(read-eval-print loop) 中进行的翻译。 * 在[环境](#npla1-环境)中访问指定名称的对象失败时。 * 特定的函数应用。   其它求值条件详见具体操作的规定。 ### NPLA1 错误   NPLA1 中的[错误](#错误)是按接口的约定不符合预期的[正常条件](#异常)(如[不被正常处理的操作数类型](#错误检查))引起的诊断。   求值特定的表达式可[引起错误](#错误),包括: * 违反[求值算法](#对象语言求值算法)步骤中的要求而直接引起的*语法错误(syntax error)* 。 * 其它情形引起的*语义错误(semantic error)* 。   以[接口文法约定](#接口文法约定)的形式约定的[操作](#规范化中间表示)中,除[类型检查](#npla1-类型检查)外,[绑定初始化](#绑定初始化)之前的[参数绑定](#绑定操作)失败是语法错误。   语法错误包含两类: * 总是依赖程序运行时确定的值不满足特定操作的要求引起动态语法错误。 * 其它语法错误违反[语法正确性](#正确性)要求,是静态语法错误。   类似地,语义错误包含两类: * 总是依赖程序运行时确定的值不满足特定操作的要求引起动态语义错误。 * 其它语义错误违反[语义正确性](#正确性)要求,是静态语义错误。   静态语法错误可能通过语法分析从源代码决定。   引起动态语法错误或动态语义错误依赖的值是合并子的具体实际参数的值,以及派生实现可选指定的其它的值。   引起动态语法错误或动态语义错误的情形包括求值特定的函数应用,由具体操作指定。   程序可通过*引发(raise)* 一个*错误对象(error object)* 指定引起诊断。   除非另行指定,NPLA1 的错误对象不需要是 NPLA1 支持的[对象](#一等实体和一等对象),而可以仅在[宿主环境](#嵌入宿主语言实现)中可见。   因果性引起的错误可构成错误之间具有依赖关系。   错误对象的其它具体形式由派生实现指定。 ### NPLA1 异常   NPLA1 的当前诊断使用的[异常](#异常)执行机制由宿主语言支持,通过宿主语言中的异常类型区分不同的异常条件。   NPLA1 约定的所有要求引起异常的诊断情形都是错误。 **注释** 用户操作引起异常不一定是错误。   不引起未定义行为的翻译失败应[抛出异常](#异常)。   [引发错误对象](#npla1-错误)可能通过抛出异常实现。此时,被抛出的宿主语言异常对象是错误对象。被抛出的异常类型可具有被显式指定的 `public` 基类,这些基类应无歧义以允许宿主语言捕获。   若存在[依赖错误](#npla1-错误)且引发被依赖的错误对象使用抛出异常实现,使用宿主语言标准库的*嵌套异常(nested error)* 机制实现依赖错误。   当前没有提供相关操作,但抛出的宿主异常在具有[表示](#表示)的意义上是 NPLA1 的[一等对象](#基本语义概念)。   关于抛出异常的[宿主类型](#类型映射),参见项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 ### 运行时错误条件   除非另行指定,实现应对以下全局的[运行时](#实现的执行阶段)[错误条件](#错误)按要求引起诊断。   当实现无法提供需要的资源,*资源耗尽(resource exhaustion)* 。此时,引发特定的关于资源耗尽的错误对象。   除非另行指定,上述表示资源耗尽的错误对象满足宿主语言的以下类型的异常对象: * 宿主资源耗尽时,异常类型满足[常规宿主资源分配要求](#常规宿主资源分配要求)的类型。 * 否则,同[异常](#npla1-异常)。 **注释** \[ISO C++] 的本机实现宿主资源耗尽时,一般抛出派生 `std::bad_alloc` 的异常对象。这不包括[本机实现无法提供资源的未定义行为](#npla-未定义行为)。 ### 错误检查   *检查(check)* 是限定成功的操作应满足的(必要非充分)条件引起诊断的操作。检查失败时要求引起诊断。   [良定义的](../Terminology.zh-CN.md#规范)检查应具有[强规范化性质](#范式),以保证有限数量的检查总在有限的计算步骤内终止。在进行检查的[上下文](#上下文相关求值),实现假定检查良定义。 **注释** 实现不需在此之前对检查的这个性质附加检查。   检查条件限定检查的通过或失败。除非另行指定,通过的检查没有[作用](#基本语义概念),失败时总是具有作用。 **注释** 检查失败通常可引起[副作用](#基本语义概念)。   NPLA1 要求在特定上下文进行[类型检查](#npla1-类型检查)。派生实现可定义其它检查。   函数操作的语义可单独指定检查,具体形式由具体操作指定。 #### NPLA1 类型检查   基于[名义类型](#类型等价性),对象语言实现应具有语义规则指定的[类型检查](#类型检查),以确保程序的执行符合操作的必要前置条件。   操作的语义可要求以下的类型检查: * 对[求值得到的操作数](#求值得到的操作数),按[文法约定](#接口文法约定)的约束进行类型检查。 * 根据特定对象状态,指定[动态类型](#类型)的检查。 * **注释** 可通过类型检查规则明确要求对象具有特定的属性,例如[可修改](#实体的不可变性)。   实现可能添加其它不违反语义要求的类型检查。   基于[表达式的类型](#表达式的类型),对应对象语言表达式的表示实体的元素可指定操作数上明确的类型要求。   部分实体从属于其它实体类型而构成[子类型](#类型序)关系;部分的规约操作取得求值结果保证结果中的值可能具有的特定类型集合,这些类型也一并在以下描述中给出;其它情形不指定类型。   规约预期符合约束。若违反由项的表示的对象的动态类型不匹配导致,则求值失败;否则,行为未指定。   类型检查的完成应[先序](#规约顺序)依赖被检查特定类型的值的访问。   除非另行指定,类型检查和程序中的其它作用(包括不同的其它类型检查)的顺序未指定。   [类型错误](#类型检查)引发错误对象。   若[合并子调用](#合并子)不接受[非真列表](#npla1-广义列表)参数构成[函数合并](#函数合并),检查参数是[真列表](#npla1-广义列表),即*参数列表(parameter list)* 。对参数列表的类型检查的完成应先序于其中任意子表达式的求值。 **原理**   类型检查有助于维护程序的正确性,并及早发现编程错误。   但是,类型检查自身存在开销;在一个阶段中集中检查类型的限制不是必要的。特别地,[静态类型检查](#类型检查)不被要求。   这些设计同时确保程序容易在程序在实现的不同[执行阶段](#实现的执行阶段)重现相同的检查逻辑乃至直接复用其实现。   为减小开销等目的,实现可能合并不同类型检查,而不改变程序的[可观察行为](#状态和行为)。   对子表达式的求值需[访问](#基本语义概念)子表达式。因此,对参数列表的检查蕴含顺序要求。 **注释**   一个值可被多次针对不同的对象进行类型检查。   不同的类型检查中,对特定类型的值的访问之间没有必然的隐含联系。 ## NPLA1 外部表示   [外部表示](#表示)若被确定,由实现和派生实现定义。   NPLA1 不要求对象和其它实体存在外部表示,也不要求外部表示唯一。 **注释**   对外部表示的存在性要求和 \[RnRK] 不同。   NPLA1 当前直接使用其它已被指定的表示规则,如[互操作](#npla1-互操作约定)隐含的宿主语言对象表示。   NPLA1 当前不提供可移植的互操作接口(包括一些基本 I/O 操作),也不约定其涉及的外部表示形式。 ## 表达式语义   表达式具有和[语法构造](#表达式)不直接相关的且可能[上下文相关](#上下文相关求值)的语义。   部分语义不需要通过[求值](#基本语义概念)体现。 ### NPLA1 规范求值算法   以[被求值的表达式](#求值规约)和所在的环境作为参数,NPLA1 使用以下*规范(canonical)* 求值算法取得表达式的[求值结果](#基本语义概念): 1. [自求值](#范式):若被求值的表达式不是[符号值](#符号)且不是[有序对](#npla1-广义列表),则求值结果是自身。 2. [名称解析](#名称解析):若被求值的表达式是一个符号值,则被视为[变量名](#npla-环境),求值结果是它在[上下文](#上下文相关求值)(当前环境确定的[词法作用域](#实现环境提供的求值环境))中变量绑定确定的对象的经[引用折叠](#引用折叠)的[左值引用](#引用值的子类型)。 3. 否则: **注释** 被求值的表达式是有序对。 1. 若被求值的表达式是具有一个[元素](#广义列表)([子表达式](#基本语义概念))的[列表](#npla1-广义列表),则求值结果是这个子表达式的求值结果。否则,继续以下求值步骤。 **注释** 被求值的表达式是具有不少于一个元素的列表或非真列表。 2. 若被求值的表达式第一个子表达式是空列表,则移除,并继续以下求值。 **注释** 起始空列表的语法用于继续求值可能不提供[实际参数](#函数合并)的[函数合并](#函数合并)。具有实际参数的函数合并不一定需要起始空列表。 3. 对第一个子表达式求值。 4. 以第一个子表达式的[值计算](#基本语义概念)的求值结果作为[操作符](#规范化中间表示),以其余子表达式作为操作数,求值[合并](#函数合并)。   有序对以外的表达式被求值时: * [标识符](#基本词法构造)的值是构成标识符的符号值。 * [代码字面量](#字面量)的值是去除其边界的 `'` 的标识符构成的符号值。 **注释** 代码字面量可表达直接作为标识符时不能作为符号值的词素的转义,例如 `''` 是一个空的符号值;而 `'#ignore'` 和 `42` 这样的形式允许其中的表达作为变量名,而不是字面量。 * `#t` 和 `#f` 求值为自身,是[布尔值](#基本语义概念)。 * `#ignore` 和 `#inert` 求值为自身,具有和其它值不同的[单元类型](#类型)。 * 数值字面量求值为*数值(numerical value)* 。   [非空列表](#广义列表)和代码字面量以外的对象作为表达式,都是[自求值表达式](#范式)。 **原理**   NPLA1 规范[求值算法](#求值规约)和 \[RnRK] 中定义的 Kernel 求值算法(以及 \[Shu10] 中定义的 [vau 演算](#其它设计和实现参考))类似,差异为: * 求值算法不直接约定取得 [WHNF](#规范化中间表示) 以外的[子项](#基本语义概念)是否被求值,而由被调用的第一个子项决定。 * [对符号值的求值](#记号求值)包含对[引用值](#引用值)的区分。 * 要求一个子项的列表总是使用其列表元素求值。   最后一个差异在对象语言中是实质性的,它决定[列表表达式](#列表表达式)和其中的[子表达式](#表达式)的求值总是等价。   求值算法保持[当前环境](#当前环境)。   NPLA1 翻译单元中,未求值的表达式满足以下性质: * 作为[记号](#基本词法构造)的标识符是[符号](#符号)类型的值。 * [列表表达式](#列表表达式)中若不含有[子表达式](#表达式),则这个表达式是[空列表](#广义列表);否则,是非空的[真列表](#广义列表),子表达式是它的[元素](#广义列表)。   NPLA1 规范求值算法和 \[RnRK] 的求值算法具有近似的简单性。   因为 NPLA1 [不支持存在环的非真列表](#广义列表),[cons 对](#有序对)的描述被对应替换。   求值算法使用的环境同 \[RnRK] 。   同 \[RnRK] ,而非 \[RnRS] ,NPLA1 规范求值算法避免对*顶层(top-level)* 的特殊引用,以避免[上下文相关性](#上下文相关求值)的不同规则带来的复杂性和限制。   使用顶层的不同求值规则的限制可能简化一些编译实现需要的假设。但这泄漏了抽象,且在实际使用中[引起大量问题](https://gist.github.com/samth/3083053)。   特别地,不同的顶层的特设规则相对更动态,反映[一些用户对 fexpr 的期望](https://calculist.blogspot.com/2009/01/fexprs-in-scheme.html),但在此这已被 vau 抽象替代。因此,使用不同的顶层求值规则以提供更强的动态性是多余的。   另一方面,当前环境一般允许被[具现](../Terminology.zh-CN.md#程序设计语言)为[一等环境](#npla-环境)在程序中可编程地访问而代表求值算法使用的上下文。为不同的上下文特设不同的顶层求值规则也是多余的。   关于实现,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。   以下各节补充描述 NPLA1 规范求值算法的局部性质。 **注释**   关于 WHNF 求值在 Kernel 中的描述,参见 \[RnRK] 关于 `unwrap` 的 Rationale 的描述。   语法分析器的实现应使结果取得和这些性质兼容的中间表示。 #### 函数合并求值   求值算法向函数合并传递当前环境作为[函数合并的动态环境](#函数和函数应用的求值环境)。   为支持没有操作数的函数应用,需约定其它表达式表达求值为[函数合并的应用表达式](#函数合并): * 当[复合表达式](#复合表达式)的第一个子表达式是空列表(`()`)时,求值为一个函数合并。 * **注释** 对没有操作数的情形,这是唯一被直接支持函数应用的语法 * 否则,求值的作用同移除第一个子项 `()` 后的剩余形式。 **注释**   关于区分函数类型的替代设计(使用 `$` 作为第一个[子项](#基本语义概念))的一个例子,参见[这里](https://eighty-twenty.org/2011/09/29/fexprs-remain-inscrutable#comment-422279775)。   基于其中类似的对语义的影响(区分函数合并是否针对一个操作子)上的理由,这不被使用。   与此不同,尽管在对象语言中接受 `()` 的使用也需要求值算法的显式支持,这在目的上是纯语法意义上的——仅在无法避免语法歧义时,才必须使用。   只要能确定求值算法使用的环境,就能静态地区分复合表达式是否是函数合并。此时,其中的第一个子表达式是否显式为 `()` 不影响关于语义的推理。使用不同的内部中间表示可完全消除是否使用 `()` 的函数合并的差异;或者,也可以约定在代码中默认使用第一个子项是 `()` 的表达式作为函数合并的规范形式,而把第一个子项不是 `()` 的形式视为隐含 `()` 的语法糖。   因此,相对使用 `$` 而言,使用 `()` 的设计具有更少的缺陷(尽管需要更多的字符)。 #### 空列表求值   空列表 `()` 作为表达式是自求值表达式,而不是没有函数的空过程调用。 **原理**   关于 `()` 的求值规则避免这种简洁有用的语法导致[语法错误](#npla1-错误)。 **注释**   这和 \[RnRS] 不同而同 \[RnRK] 。在前者构造空列表需要 `'()` 。   和 Kernel 不同的函数合并求值规则使这个设计和函数求值字面上没有直接的关联,避免了 Kernel 中为什么 `()` 不是词法上类似的如 `(f x)` 这样的表达式的特例的问题。   注意以 `()` 作为前缀并不要求要求特定函数的子类型而可能破坏子类型[封装性](#封装)的假设。 #### 记号求值   具有不同大小写字符的标识符不同。   可使用(能在求值时作为名称的)[代码字面量](#npla-名称和字面量求值)即 '' 分隔)表达没有分隔符时被解释为字面量或其它值的符号值。   [符号值](#npla-一等对象类型)作为[名称表达式](#名称表达式),经[名称解析](#名称解析)求值,访问当前环境中的[被绑定对象](#环境对象)。   其中,若被绑定对象是[引用值](#引用值),结果是[被折叠一次](#引用折叠)的引用值;否则,结果是被绑定对象作为[被引用对象](#一等引用)的引用值。   求值的结果是确保为[左值](#值类别)引用值。   结果不继续特别处理。引用值在此作为[一等对象](#基本语义概念),作为表达式时[不发生左值到右值转换](#默认值类别转换约定)。 **注释**   标识符大小写敏感的设计和 \[R5RS] 及 klisp 不同,而和 \[R6RS] 相同。和 \[R7RS] 的默认行为相同,但不提供切换大小写不敏感的方法。   代码字面量和 klisp 使用 `||` 作为分隔符的语法不同,但作用类似。   和 klisp 不同,NPLA1 允许使用 `.` 作为[变量名](#npla-环境),但在特定的上下文不被求值时符号值 `.` 可被特别处理,如[绑定匹配](#绑定匹配)时忽略以 `.` 为符号值的绑定。   和 klisp 不同,NPLA1 允许使用 `++` 等全以 `+` 或 `-` 组成的字符序列构成标识符。   以 `#` 、`+` 或 `-` 起始的不能构成标识符的词素是 [NPLA 扩展字面量](#npla-扩展字面量)。 ### 对象语言求值算法   除非另行指定,NPLA1 对象语言的求值总是使用 [NPLA1 规范求值算法](#npla1-规范求值算法)。   在输入求值算法接受的语法形式之前,求值使用基于[中缀语法](#中缀语法)识别的分隔符进行处理。   由此引起的其它语法差异参见[绑定构造](#绑定构造)。 #### 中缀语法   NPLA1 提供符合特定谓词指定的过滤条件的中缀分隔项替换为特定[名称表达式](#名称表达式)指定的前缀操作形式的列表。   这些中缀变换作为预处理操作,可识别和接受 [NPL-GA 语法](#语句)外的记号,即转换扩展的 [NPL-GA 文法](#简单文法约定)输入为严格的 NPL-GA 语法要求的源语言。   中缀变换递归替换构成表达式的形如 ` ( )*` 的记号序列为 ` +` 形式的记号序列。   其中,被支持的中缀记号 `` 是 `;` 或 `,` ,而 `` 是语法不可见的中缀变换函数。   其中,分隔符 `,` 优先组合。   分隔符对应的 `` 分别表示对被分隔的序列参数进行有序和无序列表求值(替换后合并子功能对应[参照实现环境](#npla1-参照实现环境)中函数 [`$sequence`](#基本派生特性) 和 [`list%`](#基本派生特性) 求值后的合并子)。   对分隔符的处理使用和组合顺序相反的两遍分别对 `;` 和 `,` 遍历替换。   变换的不同 `` 的实例以相同的词法顺序在变换后的结果中被保存。 #### 求值算法实现风格 **原理**   和 Scheme 不同而和 Kernel 类似,求值通常使用显式的风格(详见 \[Shu10] )而不是依赖 `quote` 的隐式风格;这和不需要括号的语法特性无关。 ### 值类别和类型   基本内容参见 [NPLA 值类别](#值类别)和[表达式的类型](#表达式的类型)。   特定的表达式维护[可修改性](#对象的修改和改变)。 **注释** 这类似宿主语言的 `const` 类型限定,但只适合[左值](#值类别)且仅使用[隐式类型](#类型标注)。   特定的操作集合可约定关于确定结果值类别和类型的具体规则,如[子对象访问约定](#子对象访问约定)。 ### 绑定操作   绑定操作决定[符号值](#符号)或具有符号值的数据结构与项的对应关系,并[初始化](#初始化)[被绑定对象](#环境对象)而引入[变量](#基本语义概念)。   作为[函数语法](#函数)的推广,两者分别由绑定操作使用[形式参数](#函数内部的变量)和[操作数](#规范化中间表示)指定。   操作数的[表示](#表示)具有树的构造,即[操作数树](#规范化中间表示)。   为决定形式参数对应的操作数,形式参数和操作数树或它们的[子对象](#子对象)的结构被比较,即绑定*匹配(match)* 。匹配操作数树的形式参数对应也可具有树的构造,即*形式参数树(formal parameter tree)* 。   被匹配的操作数是操作数树作为[有序对](#有序对)的[元素](#有序对)。类似地,形式参数是形式参数树作为有序对的元素。   绑定操作初始化对应的变量的名称和值分别由形式参数树和操作数树决定。   NPLA1 形式参数树具有特定的语法规则:树的叶节点为[符号值](#符号)、符号的[引用值](#引用值)或其它形式参数树构成的 [DAG](#自引用数据结构和循环引用) 。若构造的形式参数树不符合语法规则,[引起错误](#错误),不进行绑定。   成功的匹配决定形式参数对应的操作数或其[子项](#基本语义概念),作为其[实际参数](#函数合并)。这种对应关系是单射但不一定是满射,即匹配成功后,每个参数总存在对应的操作数或其子项,而操作数和子项允许不对应形式参数而被忽略。   被绑定的项的操作数中的元素对应是[项](#基本语义概念)中的元素。   形式参数树中的引用值可能被间接访问其[被引用对象](#一等引用)一次,其余元素在匹配时被[视为右值](#默认值类别转换约定)。   绑定操作符合以下节的绑定规则。 **原理**   被绑定的参数可作为函数的形式参数。绑定操作对形式参数的处理也可以作为其它初始化变量的语法构造的基础。   作为推广,绑定操作也可以引入函数的形式参数以外的变量。 **注释**   形式参数树的节点可以是符号的引用值,但不支持[多重引用](#多重引用)。   关于对形式参数树的具体的语法要求,另见 [`` 的定义](#绑定构造)。   因为 NPLA1 支持的[绑定构造](#绑定构造)都具有[函数合并](#函数合并)的形式,操作数或其子项总能直接被作为函数的实际参数。   DAG 要求和 Kernel 类似。   和 Kernel 不同,操作数树同时支持作为[引用](#引用值)的[左值](#值类别)和非引用的[右值](#值类别),在实现上需要解析引用。 #### 绑定初始化   绑定的对象节点的值和子节点元素被[复制初始化](#复制初始化和直接初始化)。   绑定前不对形式参数或实际参数中的元素求值。   除非另行指定,不同变量的绑定初始化之间[非决定性有序](#规约顺序)。   绑定初始化不[修改](#对象的修改和改变)形式参数,但可能因[初始化转移初值符而修改操作数](#初始化)。 **注释**   初始化元素类似宿主语言的参数传递中可发生初始化。   若形式参数或实际参数可能由求值得到,需在匹配前另行处理。   由[非决定性规约规则](#规约顺序),一般地,变量仅通过初值的求值决定的[依赖关系](#递归蕴含规则)及[子对象](#子对象)决定之间初始化的相对顺序。   因为绑定的初始化不负责实际参数的求值,一般地,即使初值符位于相邻的语法构造,也不保证隐含顺序;这和宿主语言不同。   初始化的顺序规则和宿主语言初始化不同的函数参数类似。 #### 绑定临时对象   被绑定的[临时对象](#临时对象)的[子对象](#子对象)不具有[临时对象属性](#对象属性)。 **原理**   因为[记号求值](#记号求值)保证求值[符号值](#符号)是左值,被绑定的对象[名称解析](#名称解析)最终得到的引用值不包含[唯一引用属性](#对象属性)。   这不清除绑定临时对象引入到表示被绑定对象的项或引用值中的[其它属性](#引用值的属性),因此其它属性可跟随一等对象[被跨过程传递](#对象属性)(若不经过[返回值转换](#返回值转换)或其它操作)。   同[绑定临时对象属性](#绑定临时对象属性)的讨论,被传递的属性类似宿主语言的指定转发引用参数类型,以下记作 `P` 。   特别地,被传递的属性包含临时对象属性。这对应宿主语言中 `P` 是左值引用。   [跨过程传递并不被宿主语言支持](#绑定临时对象属性)。因此,一般仅限为了实现类似宿主语言的根据值类别和类型转发参数的*转发上下文(forwarding context)* 中使用。   通过从传递的属性中提取的[标签](#对象属性)访问引用[引用值的属性](#引用值的属性)代替保存[环境引用](#环境引用)并以其它底层的方式查询作为[被引用对象](#一等引用)的被绑定对象的元数据能以更低的开销实现一些常见的相同的目的,如判断被引用对象是否表示可被转移的资源。   另见[临时对象的表示](#临时对象的表示)、[非递归绑定](#非递归绑定)和[递归绑定](#递归绑定)。 **注释**   使用[引用标记字符](#引用标记字符)可保留来自引用值实际参数的作为引用值属性的临时对象属性。   使用引用标记字符 `&` 可启用转发推断值类别。 #### 绑定匹配   绑定匹配以一个形式参数树和操作数树作为输入,比较两者的结构并尝试关联形式参数树中的子项到操作数蕴含的对象,以[创建变量绑定](#绑定初始化)。   若绑定匹配成功,则可能进行以符号值为名称的对应变量的[绑定初始化](#绑定初始化);否则,绑定匹配失败,引起错误。   绑定匹配确定每一个符号值的过程[先序](#规约顺序)这个符号值确定的变量的绑定初始化。   绑定匹配不[修改](#对象的修改和改变)形式参数,在匹配成功进行绑定初始化前不修改操作数。   匹配使用如下算法搜索形式参数树和操作数的对应位置: * 初始化输入的形式参数树为当前形式参数,函数合并构成的操作数树作为当前操作数。 * 对每一对当前形式参数和当前操作数,比较两者(除非另行指定,操作数的值是引用值的,视为匹配[被引用对象](#一等引用),下同): * 若两者都是有序对,则: * 若形式参数有序对元素的结尾元素不是符号也不是有序对,则参数匹配失败。 * 若形式参数是列表,且元素的结尾元素是以 `.` 起始的符号值,则存在*省略(ellipsis)* ;保存移除 `.` 的符号值,并从子项中移除结尾元素,继续进行比较。 * 若形式参数和操作数的(直接)[前缀元素](#广义列表)数相等,或存在省略时移除结尾元素后的形式参数前缀元素数不大于操作数子节点的元素数,则: * **注释** 直接比较前缀元素数,不计算有序对的非前缀元素是引用值且其被引用对象是非空列表时具有的元素数。 * 忽略形式参数中的省略的元素,以深度优先搜索从左到右逐一递归匹配两者的元素。 * 若存在省略的元素,若保存移除 `.` 的符号值非空,以移除 `.` 的符号值作为形式参数,匹配操作数构成的*结尾序列(trailing sequence)* 。 * 否则,若形式参数是非列表的有序对(最后的元素非空),匹配结尾序列。 * **注释** 结尾序列支持匹配有序对操作数的非前缀元素。这个元素可能是引用值,它的被引用对象被作为操作数继续匹配并进行[非递归绑定](#非递归绑定)。 * 否则,若所在的形式参数列表的结尾元素是 `.` ,参数匹配成功,忽略结尾序列,不绑定对象。 * 否则,没有其余元素需要匹配,参数匹配成功。 * **注释** 先前对形式参数和操作数的节点数判断同时确保结尾序列为空。 * 匹配结尾序列的规则参见非递归绑定。 * **注释** 结尾序列预期匹配的操作数是空列表或有序对。对操作数是列表的情形,结尾序列是*结尾列表(trailing list)* 。 * 否则,若不存在省略,列表的元素数不相等,参数匹配失败。 * 否则,操作数的子节点不足,参数匹配失败。 * 若形式参数是空列表,则: * 若实际参数不是空列表,则参数匹配失败。 * 否则,参数匹配成功。 * 若形式参数是引用值且没有因为本条匹配规则递归进入匹配,则以其被绑定对象代替当前形式参数递归匹配。 * 若形式参数不是符号,则参数匹配失败。 * 若形式参数不是 `#ignore` ,则尝试绑定操作数到以符号值确定的名称的形式参数。 * 若符号值以一个[*引用标记字符*](#引用标记字符)起始,则被绑定的[变量名](#npla-环境)中去除此前缀。 * 若去除前缀得到的符号为空,则忽略操作数,不绑定对象。   绑定匹配时不检查重复的符号值。若形式参数树中出现重复的符号值,可被多次匹配成功。这可导致之后的绑定初始化中,只有其中某个未指定的绑定生效,其它绑定被覆盖。 **原理**   虽然可能匹配被引用对象,操作数匹配不蕴含时引用值不被[消除](#引用值的消除)。   和 \[RnRK] 不同,明确直接比较前缀元素数,因为: * 这允许在元素数不同时给出更具有针对性的诊断,避免误用。 * 这能避免匹配在任何情形都总是顺序地依赖每一个操作数的值,允许[并发实现](#并发实现)。   实现使用的表示允许访问元素数具有 O(1) 的时间复杂度,而访问前缀元素数具有 O(n) 时间复杂度。但限制不访引用值时,不会有较大的附加开销。 **注释**   函数合并构成的操作数树包括作为合并子的第一个子项和作为操作数的之后余下的子项。   数据结构和匹配算法类似 Kernel 中用于 `$define!` 和 `$vau` 等[操作子](#合并子)的递归的匹配机制,但有以下不同(另见 [NPLA1 合并子](#npla1-合并子)): * 不支持 [cons 对](#有序对)的中缀 `.` ,但支持形式参数树中的列表最后以带省略的符号值匹配多个列表项的参数,绑定结尾序列。 * 对参数子项的符号值中可选的 `.` 起始以及之后可选的前缀作为标记字符作为引用标记进行处理。 * 不提供转义,若符号值去除可选的前缀及标记字符 `.` 后为空则忽略绑定。 * 若参数子项[按引用传递](#求值策略)则间接访问并绑定[被引用对象](#一等引用)。 * 只支持[无环列表](#广义列表),且不检查(因为 API 已经保证只支持真列表)。 * 列表外的 `.` 起始的[词素](#基本词法构造)当前视为普通的符号,但此行为可能会在未来改变)。   被忽略的绑定不保存绑定的对象。   不在列表内最后位置的带有前缀 `.` 的形式参数绑定的是普通的变量,不忽略绑定。   和 Kernel 不同,不检查重复符号值,且绑定匹配[对特定模式的形式参数进行不同的处理](#非递归绑定)。   其它一些不支持 cons 对的语言,如 \[ECMAScript 2019] 的 `rest` 参数支持类似结尾列表的效果。   绑定匹配和创建绑定的初始化之间的顺序约定是必要的,因为这里约定的是一般的规约规则而非求值规则,[递归蕴含规则](#递归蕴含规则)等求值的默认规则不适用。   绑定匹配允许[并行化](../Terminology.zh-CN.md#程序设计语言)。 #### 引用标记字符   应用在形式参数树叶节点符号值的前缀 `%` 、`&` 或 `@` 为标记字符表示名称绑定的可按需引入引用,称为引用*标记字符(sigil)* 。   绑定引用时,可使用引用推断规则: * 引用值[按值的副本传递](#求值策略)给形式参数,非引用值[按引用传递](#求值策略)给[形式参数](#绑定匹配)。 * 否则,操作数按值的副本传递给形式参数。   标记字符引起的绑定的差异为: * 不存在标记字符时,对操作数[按值的副本](#求值策略)绑定,实际参数的[值的副本](#实体的副本)传递给对应的形式参数。 * 若实际参数是[泛左值](#值类别),则实际参数上首先隐含[左值到右值转换](#值类别转换)。 * 存在标记字符 `%` 或 `&` 时,按上述的引用推断规则直接绑定或转发操作数。 * 当实际参数是引用值时,在[可能对其它属性进行的处理](#非递归绑定)后,隐含一次[引用折叠](#引用折叠)。 * 存在标记字符 `@` 时,绑定以实际参数作为[被引用对象](#一等引用)的引用值,不论操作数的类型和值类别。 * 初始化引用值时,没有[引用值的消除](#引用值的消除)。 **注释**   除[复制消除](#非递归绑定)转移有序对操作数的子对象外,绑定时不修改被绑定操作数。   支持修改操作数的绑定的其它标记字符可能在未来支持。 #### 非递归绑定   非递归绑定在一次匹配之后创建对应的变量绑定。   合并使用或不使用[引用标记字符](#引用标记字符)的情形,非[结尾序列](#绑定匹配)的单一参数对象的[绑定初始化](#绑定初始化)包含以下过程: * 若不存在标记字符 `@` ,则: * 若操作数为[可转移的](#对象的可转移条件)对象的引用值,则被绑定对象是按以下规则初始化的蕴含隐含的[引用折叠](#引用标记字符)的引用值: * 存在标记字符时,使用[引用推断规则](#引用标记字符),被绑定对象是操作数直接初始化的引用值,其属性由操作数的(引用值)的属性决定: * 当存在标记字符 `&` 、绑定非结尾序列且作为操作数的引用值的属性包含[唯一引用属性](#对象属性)时,其中包含绑定[临时对象属性](#绑定临时对象)。 * **注释** 使用 `%` 可避免操作数中的唯一引用属性在被绑定对象中蕴含[临时对象属性](#对象属性)。 * 否则,被绑定对象的属性和作为操作数的引用值的属性相同。 * 否则,被绑定对象是操作数[复制初始化](#复制初始化和直接初始化)(复制或转移)的值。 * 否则,若[操作数属性](#递归绑定)指定可修改的临时值或有标记字符 `%` 时的临时值,操作数是可转移的非引用值,被绑定的对象是临时对象。 * 否则,当存在标记字符 `&` 时,被绑定对象是操作数的引用值,其属性是操作数属性和[操作数项的属性](#对象属性)的并,但总是排除绑定临时对象属性。 * **注释** 此处的被绑定对象可在[作为符号求值](#记号求值)时被折叠并在结果中[进一步去除](#npla1-引用值使用约定)可能具有的[唯一引用属性](#对象属性)。 * 否则,被绑定对象是复制自操作数的值。 * 否则,被绑定对象的是操作数的引用值: * 绑定操作数的引用时,要求引用的是列表中的项,否则引起错误。 * 被绑定的对象应是不唯一的值(直接绑定操作数右值以外的值),被绑定对象是操作数的引用值。   绑定结尾序列包含以下情形: * 若不存在标记字符 `@` ,则: * 若操作数为可转移的对象的引用值,按非结尾序列的规则绑定操作数。 * 否则,若操作数属性指定可修改的临时值或有标记字符 `%` 时的临时值,按非结尾序列的规则绑定操作数。 * 否则,创建新的有序对,在其中以相应的标记字符(若存在)绑定各个元素子对象。 * 否则,创建新的有序对,在其中以标记字符 `@` 绑定各个元素子对象。   绑定结尾序列创建新的有序对并绑定元素子对象时,作为列表[完全分解](#广义列表)得到的每个元素组合的列表,满足: * 若操作数是临时对象,则操作数子项在绑定元素子对象时被[复制消除](#复制消除)。 * 组合的列表是非真列表,当且仅当操作数是非真列表。 * 子对象的元素是对应的操作数以对应的引用标记字符(若存在)绑定单一参数得到的值。 * **注释** 若不存在引用标记字符,元素被对应复制初始化。 * 若操作数是非真列表: * **注释** 此时需初始化组合中的非列表结尾元素。 * 当不存在标记字符或存在标记字符 `%` 时,组合的最后一个元素是操作数中的最后一个元素的副本。 * 否则,组合中的最后一个元素是新创建的[子对象引用](#子对象引用)。 * 其被引用对象的表示中没有子项。 * 创建的有序对初始化完成后,参与初始化被绑定对象: * 若存在标记字符 `&` ,则创建子对象引用作为被绑定对象,其被引用对象是创建的有序对。 * 被创建的子对象引用的被引用对象的表示应避免复制初始化任何操作数一等对象。 * 否则,创建的有序对直接被作为被绑定对象。 * 若存在标记字符,同时视为[绑定临时对象](#绑定临时对象),设置其表示的[临时对象标签](#对象属性)使之具有临时对象属性。 * 被绑定对象的元素总是不具有临时对象属性。 * **原理** 这使实现能[避免临时对象的子对象具有临时对象属性](#绑定临时对象)。   绑定临时对象外的引用临时对象视为对被引用对象的[访问](#基本语义概念)。 **注释** 这意味着除绑定临时对象外,若绑定操作数的初始化的引用值时实际引用临时对象,则[因超出生存期的对象访问,行为未定义](#npla-未定义行为)。   仅在绑定临时对象且操作数可转移或使用标记字符 `%` 时使用复制消除。 **原理**   绑定的默认行为对引用值特殊处理,是为了满足 [G1b](#统一性) ,而不是像某些语言(如 \[ISO C] 和 \[Rust] )仅通过内建的机制提供特定的*左值上下文(lvalue context)* 。   绑定的默认行为不使用[析构性转移](#项的转移)的操作(类似 \[Rust] 的设计),原因是考虑到绑定的副作用影响操作数(即便因为对象被销毁而不一定是修改操作)和破坏[幂等性](#基本语义概念)(特别是指定[过程调用](#函数调用)的形式参数时)违反[易预测性原则](#易预测性)。   为允许调用[宿主对象](#类型映射)的[转移构造函数](#对象的复制和转移),限制复制消除。初始化引用之外的参数创建也不是 \[ISO C++17] 约定要求消除复制的上下文。   作为操作数的引用值中的唯一引用在使用 `&` 引用标记字符时可同时蕴含[绑定临时对象属性](#绑定临时对象),这使绑定为变量的消亡值可能以名称表达式求值结果(不会是消亡值)的引用值访问时,能和其它引用值区分。提供这种设计的理由是: * 以下两种涉及[消亡值](#值类别)的资源访问可被统一: * 直接访问消亡值表达式。 * 消亡值表示即将被转移的资源。 * 以消亡值初始化一个带有 `&` 引用标记字符的非结尾序列变量,并以这个变量的名称作为表达式进行访问。 * 这通常需要使变量指称消亡值引用的资源,而不仅仅是表示即将被转移的消亡值自身。 * 具有临时对象属性的引用值通过右值初始化,相当于宿主语言中的右值引用,典型地表示能被转移的资源(而不一定需要立刻被转移)。 * 初始化变量同时转移资源,相当于宿主语言中复制初始化时调用转移构造函数转移操作数的资源到变量(对象或绑定到临时对象的引用),使之表示转移后的资源。 * 尽管值类别可能不同,这两种表达式都可以表示蕴含被转移的资源的对象。 * 直接求值名称表达式往往比其它替代方式更直接高效,但结果总是[左值](#记号求值)而不具有[唯一引用属性](#绑定临时对象),而使用临时对象属性允许在求值的结果中被保留。 * [项引用](#引用间接值)(而不是[临时对象引用](#引用值的属性))中的临时对象属性不影响值类别。 * 类似地,在宿主语言中,和值类别不同的状态以右值引用类型声明的形式编码在类型系统中。 * 引用值中的临时对象属性接近宿主语言中[转发引用](#绑定临时对象)蕴含的静态类型。 * 但和宿主语言不同,临时对象属性能随初始化后的引用值跨过程传递,而无需多次转发并在每次推导引用类型。 * 基于上述规则,对象语言中特定的转发操作处理可统一的方式处理两种表达式以转移资源。这种设计能简化一般的使用。 * 引用值支持临时对象属性对有效的转发对象应用子的实现是必要的。 * 通过对象属性,转发对象操作可避免总是从实际的操作数提取值类别的需要,允许作为应用子而非[操作子](#合并子)。 * 若不使用临时对象属性,则需要其它方式编码和值类别不同的状态以和消亡值区分,例如宿主语言的静态类型信息。 * 这会增加语言规则的复杂性。 * 需要转发资源时,一般只需要使用转发对象操作;其它情形可安全忽略引用值中的临时对象属性。 * 类似地,在宿主语言中,编码在类型系统中的状态在特定上下文中用于实现[完美转发](#函数参数和函数值传递)。 * 宿主语言中,右值引用类型的变量作为左值(而不是消亡值)被访问,在大多数操作中没有和其它左值区分的意义。 * 在 `std::forward` 这样需要区分引用类型的转发操作(实例是一个函数,而不是宏)中,右值引用类型在局部是有意义的。 * 但是这仍然存在限制:因为没有跨过程传递的状态支持,明确具体类型还是需要程序显式指定 `std::forward` 的类型参数(或者宏),而不是 C++ 函数(应用子)的方式实现。 * 消亡值应和[纯右值](#值类别)在初始化其它变量时转移资源的作用一致,对应唯一引用属性和临时对象属性的相似处理。 * 通过唯一引用属性仍可区分一个具有临时对象属性的引用值以消亡值还是纯右值初始化。 * 可使用 `&` 以外的引用标记字符避免这里的行为而被初始化的被绑定对象(引用值)中引入非预期的临时对象属性。   绑定结尾序列和非结尾序列的非递归绑定规则略有不同。 * 特别地,除非被绑定对象是引用值,引用标记字符(不论是否存在)同时被作用到作为一等对象的元素上。这是因为: * 此时,需要把操作数作为一等对象进行分解,使用引用标记字符或者不使用引用标记字符不破坏其它语义规则。 * 和非引用结尾序列相比,这使有序对的两个元素在初始化时的规则不同,但这具有合理性,因为: * 有序对作为(非真)列表时,结尾元素和其它元素的地位不是相同的。 * 有序对的元素在 NPLA 对象表示中即已不对称,地位不可交换。 * 这种设计简化了一些重要的派生实现。 * 蕴含绑定[临时对象属性](#对象属性)的规则不适用绑定结尾序列中的元素,因为: * 结尾序列的元素不具有名称,而不是通过绑定创建的能作为名称访问表达式的变量。访问元素需通过其它方式(如对象语言中的[子对象访问操作](#子对象访问约定)),方法和结果不唯一(如可能具有不同的值类别,可能即时转移资源等),不具有和消亡值的统一性。 * 引入附加的临时对象属性容易引起非预期的转移。除子对象访问时可能发生的直接转移(通常较明确),随引用值跨过程传递的临时对象属性在之后可能继续引起其它转移。因为首先通过子对象而非名称表达式访问,这种转移的存在性在创建变量绑定的位置通常不显然而无法预知,容易误用。 * 一旦不需要附加的临时对象属性,去除属性而得到引用值通常是较为困难的,需要对象语言提供特设的操作或绑定新的对象(尽管引入临时对象属性可能同样困难)。 **注释**   引用折叠的结果满足[不可修改引用属性的传播性质](#引用值的属性)。其它情形应满足 [NPLA1 引用值使用约定](#npla1-引用值使用约定)。因此,仅有使用标记字符 `%` 进行消除引用时,被消除的引用值的不可修改属性被忽略。   绑定临时对象外不和 \[ISO C++] 一样可能延长右值类类型[子对象](#子对象)的生存期。   具有引用标记字符的形式参数支持引入引用值并支持绑定引入[临时对象](#临时对象)的实际参数。 #### 递归绑定   形式参数树子项和操作数树的子项成功[匹配](#绑定匹配)后绑定子项。   递归的[绑定匹配](#绑定匹配)对应递归的绑定创建,允许以操作数树的子项对应初始化形式参数树的各个子项。 **注释** 和形式参数树中的[结尾列表](#非递归绑定)的符号值被视为整体不同,递归绑定可包含项和其子项的多次递归的匹配。   绑定算法应确定和当前处理的操作数树的属性,即*操作数属性(operand property)* 。其中蕴含的表示操作数的项对应的[标签](#对象属性),称为*操作数标签(operand tags)* 。   操作数属性和形式参数的引用标记字符结合决定是否[按引用传递](#求值策略)初始化,并判断[绑定初始化](#绑定初始化)时是否允许转移。   绑定匹配递归处理子项时,应确定子项的操作数标签,以指定子项可能具有的上下文相关的差异。   绑定初始时,操作数应为[纯右值](#值类别)。此时,以临时对象标签作为初始操作数标签。 **注释** 这指定指定操作数是可被唯一使用的临时值。与此不同,若项表示作为[一等对象](#基本语义概念)的[求值结果](#基本语义概念),应[不具有临时对象标签](#临时对象的表示)。   一个项的子项的操作数标签由这个项的操作数标签(处理子项时,代表先前确定的所在的项的标签;以下称为当前操作数标签)和本节中以下约定的子项继承规则决定: * 若操作数子项不是引用值,则子项的操作数标签和当前操作数标签相同。 * 否则,匹配的子项是这个引用值的[被引用对象](#一等引用),子项的操作数标签以操作数子项中的[引用值的属性](#引用值的属性)和当前操作数标签按以下引用项继承约束限定: * 子项的操作数标签不包含[临时对象标签](#对象属性)。 * **原理** 这使实现能避免临时对象的子对象具有[临时对象标签](#绑定临时对象)。 * 子项的操作数标签是否包含[唯一引用标签](#对象属性)同引用值的属性。 * 子项的操作数标签是否包含其它标签同对应的当前操作数标签。 * 在以上基础上,引用值的属性向子项的操作数标签对应的属性[传播](#引用值的属性):若前者包含不可修改属性,后者应包含不可修改标签。   绑定需转移子项(包括[绑定子项的复制消除](#非递归绑定))时,使用[项的转移](#项的转移)。   绑定临时对象属性标签可影响[参数转发](#绑定临时对象)。若需按类似宿主语言的成员表达式的值类别而不是成员是否为非左值引用进行转发,需确保被转发的值不是带有临时对象标签的引用值。   操作数标签中: * 唯一引用标签由所在的项单独决定。 * 临时对象标签仅在递归绑定时所在的所有列表项都是非引用值时包含。 **原理**   [引用值的不可修改属性](#引用值的属性)标记不可修改项而避免非临时对象的转移。这和宿主语言中的非 `mutable` 类数据成员访问操作符决定 `const` 限定符的规则类似。   子项标签继承规则保证使用 `&` 或 `%` [标记字符](#引用标记字符)时,值类别的决定规则和宿主语言的成员访问操作符类似: * 列表左值中的元素总是被绑定为[左值](#值类别)。 * 列表右值的元素按元素是否为引用被绑定左值或[消亡值](#值类别)。 * 特别地,[项引用](#引用间接值)的临时对象标签不被继承到作为子项的被引用对象,因为即便被引用的列表对象是一个临时对象,它的元素不被作为纯右值匹配。这和宿主语言中成员访问操作符访问的右值是消亡值而不是纯右值类似。   使用对象语言,若需判断列表左值中的元素是否为引用值,可直接绑定列表操作数为引用并按需转换为消亡值再递归绑定列表元素。 ### 绑定构造   部分[函数合并](#函数合并)的求值包含[形式参数树](#绑定操作),通过[绑定规则](#绑定操作)在[环境](#npla1-环境)中引入绑定,其调用指定[绑定操作](#绑定操作)。具有这样的语法构造的表达式是*绑定构造(binding construct)* 。   一些绑定构造使用 [``](#未求值的操作数)提供在一个表达式多次出现的形式参数树和操作数树。 **注释**   绑定在[符号值](#符号)上的[值](#基本语义概念)引入[变量](#基本语义概念)。   按[绑定初始化](#绑定初始化)的约定,[操作数树](#规范化中间表示)的子节点初始化被绑定的形式参数树的对应子节点。   和 \[RnRK] 不同,各种绑定构造可使用 [``](#未求值的操作数) 提供操作数。   对绑定项的处理和 \[RnRK] 的其它不同参见[文法元素补充约定](#文法元素补充约定)。   另见[初始化](#初始化)。 #### 强递归绑定   除类似 Kernel 的常规绑定外,NPLA1 的部分绑定构造支持延迟附加的绑定的形式。   强递归绑定支持若同时绑定的递归[符号值](#符号)构成[循环引用](#循环引用),则递归绑定的值都是未指定的内部表示而不[引起错误](#npla1-错误)。   强递归绑定是对象语言的绑定构造实现的附加机制,形式参数树的递归匹配仍使用[递归绑定](#递归绑定)。 #### 参数转发   绑定构造可支持*参数转发(argument forwarding)* ,根据实际参数确定形式参数中是否为引用值,保留[值类别和可修改性](#值类别和类型)。 **注释** 类似[宿主语言中的转发引用](#引用折叠)参数。 #### 作用顺序   绑定构造引起的绑定初始化的作用[顺序](#规约顺序)满足[初始化](#初始化)的约定。   若其中存在[副作用](#基本语义概念),其顺序还满足: * 若存在同一形式参数树子节点的不同绑定的操作,则这些操作的副作用之间[非决定性有序](#规约顺序)。 * 不同[符号值](#符号)的形式参数树子节点的绑定操作的副作用之间[无序](#规约顺序)。 * 形式参数树的子节点上的绑定操作的副作用[先序](#规约顺序)所在的节点上的绑定操作的其它副作用。 **原理**   这些规则允许[并行的](../Terminology.zh-CN.md#计算机科学)深度优先遍历的绑定实现。深度优先遍历使任一时刻成功完成绑定的对象集中,相对其它策略其状态更[易预测](#易预测性)。 ## 对象语义   关于对象的存储,基本内容参见 [NPLA 存储和对象模型](#存储和对象模型)。   另见[对象语言内存安全保证](#对象语言内存安全保证)。 ### NPLA1 对象同一性   NPLA1 的对象是[一等对象](#基本语义概念)。由定义,NPLA1 的对象默认确保[同一性](#基本语义概念)。   例外参见 [NPLA1 子对象](#npla1-子对象)。   对象的[引用值](#引用值)通常不保证其作为[被引用对象](#一等引用)和其它对象都不同一,包括[唯一引用](#对象属性)的情形。但除非另行指定,作为函数实际参数的对象若是[右值引用](#引用值的子类型),则实现可假定被引用对象唯一。 **注释**   关于右值引用的保证类似 \[ISO C++] [\[res.on.arguments\]](https://eel.is/c++draft/res.on.arguments) 。注意这在对象语言而非宿主语言中适用。 ### NPLA1 子对象   基本内容参见 [NPLA 子对象](#子对象)。   子对象可具有引用值,即[子对象引用](#子对象引用)。   子对象引用访问的被引用对象不保证具有同一性。 **原理**   和[宿主语言](#实现的执行阶段)不同,通过相同方式构造的子对象引用访问的被引用对象[未指定是否为同一对象](#子对象)。   这允许实现使用和宿主语言不同的方式创建[非一等对象](#一等实体和一等对象)作为子对象的[表示](#表示)。 **注释**   和宿主语言不同,NPLA1 对象语言不直接提供访问子对象的内建语法。 #### 子对象访问约定   作为[使用名称表达式访问对象](#npla1-引用值使用约定)的推广,特定操作可使用非环境的其它对象显式地访问其子对象。   除非另行指定,这些[访问](#基本语义概念)操作以本节约定的规则确定结果的[值类别和类型](#值类别和类型)。   若指称非环境对象的[表达式](#表达式) `E1` 是访问操作的(被求值的)实际参数,子对象来自这个参数指定的对象;否则,子对象来自 `E1` 引用的[环境对象](#环境对象)中的[被绑定对象](#环境对象)。   具体的访问操作确定具体的被访问的子对象。   访问操作中: * 若指定结果是引用值,或指定[(在结果中)保留引用值](#被保留的引用值的目标)且被访问的对象实际存在可被保留的引用值,则结果是被访问的子对象的可能经[引用折叠](#引用折叠)的引用值。 * 否则,结果是被访问的被访问的子对象[复制初始化](#复制初始化和直接初始化)的[值](#基本语义概念)。 * 此时,按[对象的可转移条件](#对象的可转移条件)判断复制或转移。   访问操作的[结果](#规范化中间表示)的[值的类型和值类别满足一一对应](#引用值的子类型),且结果经值类别转换后和被访问的子对象的类型相同。访问操作中没有明确指定的结果的值类别以此通过结果的类型推断。   若通过以上约定,仍没有明确结果的值类别,则按以下默认规则确定: * 若被访问的子对象是引用值,则结果是[泛左值](#值类别)。 * 否则,若 `E1` 是[左值](#值类别),则结果是左值。 * 否则,结果是[右值](#值类别)。   结果是具有被访问的子对象类型对应的值,它的更具体的值类别通过上述等价关系按结果的类型对应确定。   `E1` 或被访问的子对象的[传播](#引用值的属性)引用值的[不可修改属性](#引用值的消除)。   被访问的子对象访问若具有影响值类别或被传播以外的其它属性被保留,对应在结果中出现。   *成员访问(member access)* 操作访问称为对象的*成员(member)* 的子对象,满足本节的约定。   具体操作可具有其它改变结果的值类别和类型的约定而实际使用不同的规则。 **原理**   确定结果的值类别和类型的方式类似按宿主语言的成员访问确定对表达式 `E1.E2` 的值类别和类型。`E1` 不一定是左值。   NPLA1 没有形如 `E1.E2` 的特设对象访问表达式语法,而以具体的访问操作代替,因此可具有近似但不同的规则。   特别地,除非 `E1` 引用环境,在 `E1.E2` 中显式指定被访问的子对象的表达式 `E2` 在访问操作中一般并不存在。代替这里的 `E2` 的是由具体访问操作指定被访问的子对象,其类型直接代替 `E2` 的类型。   其中,按有序对访问列表的最后一个元素时,被访问的子对象不是引用值,即视为[纯右值](#值类别)。这里不蕴含[求值](#基本语义概念),不会有[值类别转换](#值类别转换)。   通过被访问的子对象的类型和 `E1` 的值类别确定结果的值类别的默认规则类似 C++ 成员访问表达式 `E1.E2` 确定值类别的规则,但略有不同: * 因为不保证存在名称表达式 `E2` ,不需要[求值算法](#对象语言求值算法)使用类似 C++ 的 `unqualified-id` 一致的方式使结果总是左值。 * 被访问的对象是右值引用值时,结果是右值引用值(即[消亡值](#值类别)),而不一定是左值。 * 和 C++ 不同,NPLA 消亡值总是右值引用类型,NPLA 纯右值也此类似 C++ 纯右值实质化转换初始化的消亡值,因此逻辑上需要 C++ 消亡值的情形和此处的 NPLA 右值近似。 **注释**   初始化非引用值的复制初始化(包括以下的替换消亡值为右值)可能通过[返回值转换](#返回值转换)实现。   在对象表示上,传播[引用值的不可修改属性](#引用值的属性)决定 `E1` 指定的被访问对象或被访问的子对象若具有[不可修改属性](#对象属性),结果也具有不可修改属性。   典型地,被保留的其它属性包括[临时对象引用](#引用值的属性)。临时对象引用可被继续绑定而可实现[按需转发](#绑定临时对象)被引用对象。   按默认规则访问相当于这些规则确定的值类似 C++ 表达式 `std::forward(E1.E2)` 的值。   具体操作可具有其它改变结果的值类别和类型的约定而实际使用不同的规则。   例如,推断结果的值类别的规则中的左值和消亡值可被替换为右值,则近似 C++ 表达式 `std::forward>(E1.E2)` 。   传播引用值属性和宿主语言及[递归绑定](#递归绑定)规则类似。   关于子对象的修改,参见[对象的修改和改变](#对象的修改和改变)。 ### 对象的修改和改变   对象作为实体可修改和改变,可具有[可变数据状态](#实体的不可变性)及[可变管理状态](#实体的不可变性)。   [NPLA 约定](#npla-整体约定)的[表示](#表示)同[宿主环境](#嵌入宿主语言实现)的对象,其修改也同这些对象的修改。   [隐藏状态](#状态和行为)在针对对象语言的的讨论中被排除。除非另行指定(由具体操作的语义蕴含),所有可变状态都不属于这些被排除的状态。   改变对象可引起[诊断](#翻译时正确性规则): * 对明确不可变的对象进行改变的操作[引起错误](#npla1-错误)。 * 具体操作的语义中,所有操作都允许的不要求诊断错误的[改变操作](#实体的不可变性)隐式地指定可变管理状态的改变。   以下状态是可变管理状态: * 环境中的被绑定对象。   除非另行指定,其余可变状态都是可变数据状态。   类似宿主语言(如关于 `const` 限定符的语义),生存期开始前或结束后的(可能并未完成构造的)对象中的子对象的修改不是对象的修改;对应地,此处的子对象的变化也不是对象的改变操作。   改变上述的被排除的状态的修改操作不被视为对象语言中的对象的改变操作。   对包含所有权的子对象的修改是对所在对象的修改。   除非另行指定,NPLA1 不限制任意对象不可修改。   等价关系和限制不可修改性的方法的方式不唯一,因此不可修改性也不唯一。   因为[外部表示不唯一](#npla1-外部表示),不需要基于此定义一种正规的关于外部表示的等价判断形式。   对象的不保证[同一性](#npla1-对象同一性)的[子对象](#npla1-子对象)的修改和改变不保证蕴含对对象的修改和改变。 **原理**   [开放类型映射](#类型映射)不保证非特定对象之间的不可修改性具有唯一的定义。   对象的修改和改变作用在确定的对象上。   若不同的对象之间不具有同一性,则作用之间无关。因此,修改和改变作为副作用,[不保证在不同一的对象之间共享](#子对象)。 **注释**   所有对对象的状态的约定针对同一个对象。   对象的子对象作为可变管理状态,使不可变对象具有允许这些状态改变的[内部可变性](#实体的不可变性)而和对象的可变性不同。   对诊断的要求类似 \[RnRK] 。   环境中的被绑定对象在仅讨论不可变性的意义外仍是数据对象。   引起对象内的可变管理状态的改变而不改变对象的操作在宿主语言[可通过类的 `mutable` 数据成员实现](#实体的不可变性),但 NPLA1 不提供特性使任意的[子对象](#npla1-子对象)的可修改性的限制如宿主语言的 `const` 限定符自动传播(而一般需要使用[成员访问操作](#子对象访问约定)),因此也不需要提供对应的类型检查修改机制。   和 \[RnRK] 不同,NPLA1 支持直接修改对象,而不只是通过指定子对象关联的被引用对象的[改变操作](#实体的不可变性)。   [冻结](#冻结)操作是使环境对象上具有类似宿主语言的 `const` 传播约束的操作;和宿主语言不同,这不是静态类型系统约束。 #### 赋值   NPLA1 的*赋值(assignment)* 操作专指以引用值操作数指定对象且不引起同一性改变的[对象修改](#对象的修改和改变)。   被修改的对象由赋值操作的目的操作数决定,可能是操作数对象或其引用的对象。赋值操作后,被修改对象的值来自源操作数。   操作数和源操作数相同的赋值是*自赋值(self assignment)* 。   除非另行指定,赋值操作不保留源操作数的[值类别](#值类别和类型)和可修改性。   赋值可引起源操作数[对象的复制或转移](#对象的复制和转移),分别称为*复制赋值(copy assignment)* 和*转移赋值(move assignment)* 。   复制赋值时不会[复制消除](#复制消除)对象。若被赋值的源操作数的值在复制出错,目的操作数引用的对象不被修改。   通过对象的[子对象引用](#npla1-子对象)修改对象的子对象不保证作用在对象上。 **原理**   赋值不引起同一性改变的保证和区分复制赋值和转移赋值类似宿主语言。   宿主语言中,通过源操作数的[静态类型](#类型系统和类型机制)(左值或右值引用类型)明确区分两者,但 NPLA 不要求类型系统(尽管支持类似作用的[元数据](#对象属性)),两者区分实际依赖具体行为。   子对象引用不一定保证引用完整对象,而修改的副作用可能需要完整对象的信息: * 例如,修改作为列表的子对象的有序对时需要维护保持子对象关系的内部状态,而子有序对引用若不提供所在的列表的引用,则无法实现维护状态。 * 为维持子对象引用实现的[简单性](#简单性),不对这类情形进行一般要求。 * 特定操作可以提供更强的保证以允许满足[变化的自由](#变化的自由)。 **注释**   赋值操作可能伴随赋值以外的其它副作用,如[转移导致的修改](#转移导致的修改)。   特定的赋值操作可能不支持自赋值,指定自赋值具有[未定义行为](#npla1-未定义行为)或引起错误。   注意避免使用引用值作为操作数的自赋值引起[循环引用](#循环引用):此时除非另行指定,引起 [NPLA 未定义行为](#npla-未定义行为)。   不引起同一性改变的保证和 Kernel 的赋值操作包含以特定对象进行替换(可使用[项的转移](#项的转移)实现)而使对象被修改的情形不同。   赋值不保证子对象的同一性不被改变;子对象的引用仍可能被赋值[无效化](#无效化)。 #### 转移导致的修改   转移可导致被转移对象的外部可见的修改。   转移不需要是直接显式求值特定的函数调用的副作用。 **注释** 例如,使用[唯一引用](#引用值的属性)初始化对象,可[转移](#项的转移)表示[被引用对象](#一等引用)的项。   和[宿主环境](#嵌入宿主语言实现)不同,当前实现不直接通过初始化[转移](#对象的复制和转移)[宿主对象](#类型映射)。   被转移的对象在转移后具有[有效但未指定](#实体的副本)的状态。 **注释**   当前实现中,当项被转移后,表示的值为 `()` 。这和[返回值转换](#返回值转换)等引入[实质化临时对象](#值类别转换)时可能具有的转移的[作用](#基本语义概念)(仅在互操作时可见)不保证相同。   作为[赋值规则](#赋值)的推论,通过转移对象的[子对象引用](#npla1-子对象)修改对象的子对象不保证作用在对象上。但和其它修改不同,这同时被转移对象后的状态的规则覆盖。 ### 驻留   出现在表达式中多个位置的值在实现中可共享一个对象作为内部表示。这个对象被*驻留(intern)* 。   当前实现不使用对象驻留,以简化[存储对象的互操作](#存储和对象模型)。 **原理**   因为[子对象](#npla1-子对象)允许通过引用值被直接[修改](#对象的修改和改变),驻留对象创建的共享可能影响[可观察行为](#状态和行为)。   因此兼容 NPLA1 语义的驻留要求排除[可修改的操作](#对象的修改和改变),且被驻留的值对应的对象的[同一性](#基本语义概念)不被外部依赖。 **注释**   驻留的对象在实现上共享存储,但仍区分同一性。   一般地,驻留仅适合不可变对象,或改变后提供不同副本区分同一性的可变对象。   \[RnRS] 等不可变的符号可被驻留,但没有特别要求。   \[R7RS] 明确要求空列表的唯一性。和驻留一致,这可实现为全局共享对象。 ### 无效化   若对象的[引用值保持有效](#npla1-引用),则指称的左值的对象[同一性](#基本语义概念)不变。   作为[间接值](#间接值)的派生实现,对象语言中的引用值的[无效化](#间接值)包括以下情形: * 被引用的对象存储期已结束(此时引用值是[悬空引用](#无效的引用值))。 * 对象被除通过[重绑定](#重绑定)、[赋值](#赋值)和另行指定的情形以外的方式[修改](#对象的修改和改变),而引起对象同一性的改变。 **注释**   对项的重绑定或赋值仍可能因为对子项的修改蕴含被替换的对象的销毁,引起[子对象](#npla1-子对象)的[生存期结束](#子对象),而使其表示的对象的引用值无效化。 ### 类型分类   NPLA1 不要求支持任意[类型的集合表示](#类型)不相交,即*分区(partition)* 。   但除非另行指定,基于[实体元素文法](#实体元素文法约定)引入的类型仍被分区。 **原理**   不要求分区这避免全局地假定[类型全集](#类型全集)的具体表示,并支持开放的[类型映射](#类型映射)。   NPLA1 的[类型谓词](#类型谓词)是一元谓词,[只接受一个参数](#类型谓词),以强调语言提供的接口的[正交性](#正交性)。 **注释**   通过指定[子类型关系](#类型序)可使两个[名义类型](#类型等价性)作为集合相交。   列表类型只包括[真列表](#npla1-广义列表)。   不要求分区、类型判断谓词、列表类型的设计都和 Kernel 不同。 ## NPLA1 对象语言数据结构   本节指定在 NPLA1 允许以[一等实体](#实体语义)被使用的基本元素。   [NPLA 一等对象](#npla-一等对象类型)是 NPLA1 一等对象。 **注释**   部分设计原则和规则和 Kernel 不同。   另见[对象语义](#对象语义)。 ### NPLA1 引用   NPLA1 基于 NPLA [项引用](#引用间接值)支持实体的[引用](#引用值)。   NPLA1 语义中对[广义实体](../Terminology.zh-CN.md#程序设计语言)的构成依赖的使用也被称为引用,这不限被对象语言中的引用值表达。另见[环境引用](#环境引用)。   NPLA1 [明确允许不通过对象的引用保存对象](#npla1-环境),但是也允许使用对象引用;即对象和对象的引用都可作为[一等对象](#基本语义概念)。   这也允许子对象直接被所在的对象蕴含。   [左值](#值类别)都通过[引用值](#引用值)表示。另见[一等引用](#一等引用)、[表示](#表示)和[存储和对象模型](#存储和对象模型)。   引用值在创建时即引用在生存期内的对象。 **注释**   引用和 Kernel 及 Scheme 的引用类似。   明确允许不通过对象的引用保存对象和 Kernel 不同详见[实体语义](#实体语义)。 #### NPLA1 引用值使用约定   除非另行指定: * 不在求值引入右值引用。 * 引用值的[初始值](#初始化)经过不超过一次[引用折叠](#引用折叠)。 * 为在表达式的求值结果中取得折叠的引用值: * 当[被引用对象](#一等引用)是[被绑定对象](#环境对象)时,引用值被[折叠一次](#引用折叠)。 * 否则,引用值通过蕴含一次[引用值提升转换](#值类别转换)的方式被[消除](#引用值的消除)一次。 * 违反[不可修改引用](#引用值的属性)引入的假定的修改操作引起[类型错误](#类型检查)。 **原理**   按 [NPLA1 规范求值算法](#npla1-规范求值算法),隐含[当前环境](#当前环境)直接求值名称表达式的[求值结果](#基本语义概念)是左值。这和宿主语言求值的 `unqualified-id` 在除了*枚举器(enumerator)* 外的大多数情形中类似。   在此,这被约定为默认情形。其它情形需要附加的规则指定。   特定的显式指定环境和名称表达式的操作访问环境中的被绑定对象,其求值结果可以是[左值](#值类别)或[消亡值](#值类别)。这和宿主语言的涉及成员访问的表达式(形如 `E1.E2` 或 `E1->E2` )类似。环境相当于宿主语言中形如 `E1` 的对象表达式。   因为类型系统的不同,类比成员访问的表达式时,忽略 C++ 的位域(bit-field) 、静态成员、成员函数和枚举器的访问规则。   此处 `E1` 总是被视为左值,所以类似宿主语言的规则,结果的值类别由环境中的对象类型确定:当且仅当对象是左值时,结果是左值;否则是消亡值。   因为 NPLA 的左值和消亡值是[引用值](#引用值的子类型),存在推论: * 当且仅当对象是左值时,显式指定环境的成员访问的结果是[左值引用](#引用值的子类型)。 * 否则,结果是[右值引用](#引用值的子类型)([唯一引用](#引用值的属性))。   访问不作为被绑定对象的子对象时,通常并非如宿主语言为支持推断参数类型的方式使用引用折叠,构造折叠的引用值默认不直接使用[引用折叠的规则](#引用折叠)(除临时对象标签外同 \[ISO C++] ),而直接由被引用对象确定。   对访问列表中的子项构成的子对象引用,这也和宿主语言的涉及成员访问的表达式类似,除以下不同: * 元素是引用值时允许结果是唯一引用(而不是宿主语言的左值)。 * 这是因为在此唯一引用指定的是结果的值的类型,而非类似宿主语言声明的右值引用类型。 * 元素是临时对象的引用值时,允许引用值上的[临时对象属性](#引用值的属性)在访问中被区分(类似宿主语言以成员访问表达式作为 `decltype` 的操作数的结果)。   访问被绑定对象使用引用值也满足[不可修改引用属性的传播性质](#引用值的属性),避免被绑定对象被任意非预期地修改。   其它情形是否需要满足不可修改引用属性的传播性质和具体操作相关,因此不明确要求。   对违反不可修改引用引入的假定的修改操作要求错误[避免隐式的 NPLA 未定义行为](#引用值的属性),因此引入类型错误作为[违反引用值的属性引入的错误](#引用值的属性)。 **注释**   [互操作](#npla1-互操作约定)可能引入右值引用。   访问子对象的具体规则参见[子对象访问约定](#子对象访问约定)。   作为列表或者环境中绑定对象的一部分,引用值可能通过[求值算法](#求值规约)或对象语言提供的操作访问。这同时确定值类别。   蕴含一次引用值提升转换的方式包括[返回值转换](#返回值转换)。 #### 循环引用   除非另行指定(如[强递归绑定](#强递归绑定)),对象中的循环引用引起 [NPLA 未定义行为](#npla-未定义行为)。 **原理**   循环引用破坏一些实现的假设而引起非预期的访问。   不显式访问环境的操作也可能引入循环引用(而引起未定义行为),例如 [NPLA1 参照实现环境](#npla1-参照实现环境) 下: ```npla1 $def! l (); $def! l list% l; ```   典型实现中,具有所有权的循环引用可引起[资源泄漏](#资源泄漏);无条件遍历访问循环引用[子对象](#npla1-子对象)的求值不具有[终止保证](#终止保证)。   例如 NPLA1 参照实现环境下求值以下表达式: ```npla1 $let ((nenv () make-environment)) $set! nenv self nenv ```   可引起被捕获的环境中存储的对象无法释放。   能同时保证避免资源泄漏的实现引起一般意义上更根本的设计限制,因此不被使用。详见[自引用数据结构和循环引用](#自引用数据结构和循环引用)。   此外,为了避免 [`$lambda`](#基本派生特性) 等引起不经意的循环引用误用,根据[易预测性原则](#易预测性),这些[合并子](#合并子)的[构造器](#创建和访问对象的函数)默认不使用[强引用](#环境引用)作为静态环境。   若需保持静态环境的所有权,使用显式指定静态环境的构造器(如 [`$lambda/e`](#基本派生特性) )和 [`lock-current-environment`](#基本派生特性) 等。   否则,容易引起循环引用,如以下表达式: ```npla1 $def! f $lambda () ```   会相当于当前设计的: ```npla1 $def! f $lambda/e (() lock-current-environment) ```   此处锁定的[当前环境](#当前环境)的强引用被作为闭包的一部分绑定到当前环境中,引起循环引用。   而求值当前设计中等价的: ```npla1 $def! f $lambda/e (() get-current-environment) ```   不引起未定义行为。 #### 自引用数据结构   因为不支持[循环引用](#循环引用),不支持引用自身的[自引用数据结构](#自引用数据结构和循环引用)。 **注释** 另见[列表](#npla1-广义列表)。 ### NPLA1 环境   NPLA1 支持[一等环境](#npla-环境)。   [环境对象](#环境对象)也可能是语言中显式约定的和[环境引用](#环境引用)不同的非一等对象。   NPLA1 的环境关连的[父环境](#npla-环境)的[重定向](#名称解析)使用 DFS(Depth-First Search ,深度优先搜索)遍历目标。   环境中的一等对象是环境对象的[子对象](#npla1-子对象)。子对象是环境对象,即*环境子对象(environment subobject)* 。   语言实现可提供不是一等环境的环境对象,即*非一等环境(non-first-class environment)* 。 **注释**   除了支持非一等对象的环境,和 Kernel 类似。 #### 隐藏环境   总是不能被对象语言以一等对象访问的环境是*隐藏环境(hidden environment)* 。 **注释** 推论:非一等环境是隐藏环境。   一般地,隐藏环境是某一个(非隐藏的)一等环境的直接或间接父环境(而能通过求值等间接操作被访问)。 #### 新环境   *新(fresh)* 环境是新创建的环境。   新环境和先前的其它的(特别地,包括[当前环境](#当前环境))不共享相同环境对象。   除非另行指定,新环境是[空环境](#求值环境)。   创建新环境的一个例子是 [vau 抽象](#vau-抽象)实现[过程调用](#函数调用)。 #### 环境的稳定性   环境在特定情形保证*稳定性(stability)* :一个环境是*稳定的(stable)* ,仅当总是可假定绑定维持一定意义的等价性,而可确保访问其中同名实体的[可观察行为等价](#状态和行为)。   违反关于环境的稳定性的要求的程序具有[扩展 NPLA 未定义行为](#npla1-未定义行为)。   当前要求确保的稳定性包括: * [隐藏环境](#隐藏环境)的[绑定有效稳定性](#重绑定)和[值稳定性](#被绑定对象的值和可观察行为)。 * 从构造时即明确要求的一等环境的稳定性,包括[模块稳定性](#模块稳定性)和可能由派生实现定义的其它稳定性。   环境中的绑定的对象可以在引入后通过[对象的引用](#npla1-引用)被修改(#对象的修改和改变)。 **原理**   一般地,环境的稳定性要求构造环境时不能依赖非特定的动态环境(作为被[名称解析](#名称解析)访问的父环境),因为这些环境的绑定可能具有在构造环境之后确定的绑定,而不能确保环境中的名称具有可预知的含义。   环境的稳定性简化分析程序的推理过程,也在许多上下文中允许程序更易被优化。   从稳定的环境多次访问对象的计算作用是幂等的。这允许合并多次访问为一次而不改变程序的行为,允许具有较小的实现开销。   环境的稳定性不易被可靠地判定甚至不可能被判定(例如,一个无法检查但可信的来源提供的环境),因此语言规则在此不要求进行检查。   和 \[RnRK] 不同,[环境基本操作](#环境基本函数)和[合并子基本操作](#合并子基本函数)及基于这些操作的一些[派生操作](#基础派生特性)的[操作数树](#绑定操作)构造时不检查其中的非列表项是否都为[符号](#符号)或 [`#ignore`](#npla1-扩展字面量)(而延迟到匹配时检查);匹配时不检查符号重复;若形式参数中的符号重复,则绑定的目标未指定。   此外,NPLA1 提供单独的递归绑定符号的机制,且明确支持[在操作数中同时递归绑定之前未被绑定的多个符号](#强递归绑定)。   要求隐藏环境稳定允许实现共享隐藏环境作为父环境而提供[标准环境](#基本派生特性)。 **注释**   \[RnRK] 的 `make-kernel-standard-environment` 若通过共享基础环境作为隐藏的父环境实现,也具有这里的稳定性。   但是,\[RnRK] 不提供[同一性保证](#npla1-对象同一性),也没有通过对象的引用[修改](#对象的修改和改变)[被引用对象](#一等引用)的操作,因此不需要支持[不可修改引用](#引用值的属性)即可保证值稳定性。   环境对象符合默认的等价比较规则以及绑定的对象可通过引用被修改和 \[RnRK] 不同。   关于要求的环境稳定性,存在推论:稳定环境中的同名[被绑定实体](#求值环境)可证明排除通过对象的引用[使其改变的副作用(如被修改)](#对象的修改和改变)或总是具有[同一性](#基本语义概念)。 #### 环境生存期   对象语言的实现提供给[用户程序](#程序实现)使用的[初始环境](#npla1-初始求值环境)的环境对象及其中的子对象满足: * 其创建[先序于](#规约顺序)用户程序的对象的创建。 * 除非提供为不满足[环境的稳定性](#环境的稳定性)的环境中的被绑定对象,其销毁后序于用户程序的对象的销毁。   程序引用环境中的名称时,应确保环境在生存期内。 **注释**   环境中不满足稳定性的被绑定对象可能被修改且具有外部可见的[可观察行为](#状态和行为)。若这个对象是一个环境的唯一强引用,则对应的环境对象在替换为其它值时被销毁。   特别地,应注意使用函数时引入父环境的生存期。   另见[对象语言内存安全保证](#对象语言内存安全保证)。 #### 环境中的绑定   环境中的[绑定](#基本语义概念)的抽象不依赖对象语言中表达的引用的概念,允许直接关联一个没有引用的值。   环境中的[绑定对被绑定的对象具有所有权](#环境对象)。除在环境中绑定中间值的[不安全操作](#不安全操作),这种直接所有权是独占的。   绑定的[变量名](#npla-环境)是[符号值](#符号)构成的[名称表达式](#λ-完备语义和对应语法),[解析](#名称解析)的结果总是[左值](#值类别)。 **注释**   环境中的绑定不依赖引用以及绑定所有权和 Kernel 的设计不同。   另见[一等引用](#一等引用)和[绑定构造](#绑定构造)。 #### 重绑定   环境中允许变量以相同的名称被重新绑定,即*重绑定(rebinding)* 。 **注释** 和 Scheme 类似。   [被绑定对象](#环境对象)的[引用](#npla1-引用)不因其引用的对象被重绑定操作替换值而被无效化。   重绑定替换被绑定对象的值,不改变对象的[同一性](#npla1-对象同一性)。若其中存在[子对象](#npla1-子对象),则子对象被销毁,任何子对象的引用值被[无效化](#无效化)。   特别地,若继续访问已被[求值](#对象语言求值算法)指称的引用值引用的对象,则超出生存期访问而引起 [NPLA 未定义行为](#npla-未定义行为)。   任意[隐藏环境](#隐藏环境) e 应满足以下绑定有效稳定性:通过引用值间接访问 e 中绑定的对象时绑定保持有效(蕴含不被移除或重绑定),保持被绑定对象的生存期和 e 对其[所有权](#所有权抽象)。   这避免因为上述访问违反[内存安全](#内存安全)而引起 NPLA 未定义行为。   关于无效化,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 #### 被绑定对象的值和可观察行为   任意[隐藏环境](#隐藏环境)的 e 的任意[同一](#npla1-对象同一性)被绑定对象 o 应满足以下的*值稳定性(value stability)* :若 o 上发生[使其改变的副作用(如被修改)](#对象的修改和改变),则之后在以 e 或任意以 e 作为直接或间接父环境的环境中直接以[名称解析](#名称解析)或 o 的引用值访问 o 时,o 的值和发生作用前的 o 的值在影响[可观察行为](#状态和行为)的意义上等价。   若不满足值稳定性,访问副作用发生后的对象引起[扩展 NPLA 未定义行为](#npla1-未定义行为)。   以下情形使对象[改变](#对象的修改和改变)的副作用不受值稳定性要求的约束: * 对象通过合并子调用的返回值或其中的[子对象引用](#子对象引用)指定(不论合并子是否是隐藏环境中的[子对象](#npla1-子对象))。 * 对象间接访问具有[内部可变性](#实体的不可变性)的对象的[可变管理状态](#对象的修改和改变)。 **注释**   通过限制[引用值](#引用值)[不可修改](#引用值的属性)可以维护[被引用对象](#一等引用)的值稳定性。   对象[间接访问具有内部可变性的对象的可变管理状态](#对象的修改和改变)的一类典型实例是一等环境中的绑定中的子对象(即便这个一等环境对象是隐藏环境中的子对象)。 #### 冻结   环境可进行*冻结(freeze)* 。*冻结的(frozen)* 环境中取得的绑定和引用值[不可修改](#对象属性)。   特定的环境[修改](#对象的修改和改变)要求环境不在冻结状态以确保不变量,要求[类型检查](#npla1-类型检查)。检查失败则[引起类型错误](#npla1-类型检查)。   冻结一个已被冻结的环境没有作用。 **注释** 冻结环境是[幂等](#基本语义概念)操作。   NPLA1 [隐藏环境](#隐藏环境)是冻结的。   当前 NPLA1 对象语言不提供在已有环境撤销冻结或在冻结的环境中添加、移除绑定或[重绑定](#重绑定)的方法。   若程序中使用其它方法(附加初始化或提供本机实现操作)撤销冻结或在冻结的环境中添加、移除绑定或重绑定而使对象语言安全性保证失效,这种方法应由派生实现定义,否则程序[行为未定义](#npla1-未定义行为)。   关于对象语言安全性保证,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   环境的冻结操作类似 \[ECMAScript] 的对象的冻结操作。类似地,冻结环境不会冻结其中的变量绑定中可能存在的[环境子对象](#npla1-环境)。 ### NPLA1 广义列表   NPLA1 的[广义列表](#广义列表)是[真列表](#广义列表)或者无环的[非真列表](#广义列表),其[元素](#广义列表)不构成[环](#广义列表)。   列表的[引用](#npla1-引用)构成其它对象时,也不构成[环](#循环引用)。 **原理** 排除环使处理列表的操作不需要考虑一些复杂的[自引用](#自引用数据结构)情形。   [绑定构造](#绑定构造)的[形式参数树](#绑定操作)是可能是[符号](#符号)或真列表。 **注释** 形式参数树可作为表达式直接在[源程序](../Terminology.zh-CN.md#程序设计语言)中表达。   通常意义的列表即[真列表](#广义列表)。   除非另行指定,NPLA1 列表类型指[真列表](#求值得到的操作数)。非真列表的类型是[有序对](#求值得到的操作数)。 **原理**   和 Scheme 及 Kernel 不同,NPLA 支持的列表都是真列表。另见[关于自引用数据结构和循环引用的分析](#自引用数据结构和循环引用)。   列表的这些特性确保基于列表的数据结构在对象语言逻辑上的简单性。也因此 NPLA1 对应的操作中,没有对环存在性的检查。   没有环的结构能保证所有权语义能按需嵌入(embed) 到列表中,即列表可保证表示为同构的具有对节点所有权的嵌套 [cons 对](#有序对)。 #### 有序对的子对象和子对象引用   有序对的元素是有序对的[子对象](#npla1-子对象),有序对对作为元素的表示具有所有权。同一个有序对的元素节点之间没有所有权关系。   部分操作可能修改有序对的子对象。   除非另行指定,有序对的子对象被转移,使用[项的转移](#项的转移)。被转移的子对象在被转移后不在被转移的有序对中存在。   [子有序对引用](#子对象引用)可被[绑定构造](#绑定构造)引入。   关于子对象被修改和转移,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 **原理**   有序对的子对象是表示它的项的[子项和值数据成员](#项的子对象)对应表示的对象。   对象被转移后通常其子对象不需要再被访问,此时保持子对象的[同一性](#实体的同一性)和转移前的状态一一对应缺乏意义。作为[析构性转移](#实体的副本),使用项的转移可以复用已使用类型擦除或其它的间接存储方式持有的子对象,减小不必要的开销。   作为默认规则,明确要求项的转移,而不是未指定是否使用项的转移,以满足语言规则自身的[简单性](#简单性)和[易预测性](#易预测性)。这类似 \[WG21 P0135R1] 引入强制复制消除(madatory copy elision) 对 \[ISO C++] 的规则起到简化作用。不同的是,因为没有[静态类型](#类型系统和类型机制)的限制,子对象的类型能在程序运行时改变,而不需要引入静态分析开销,这同时使实现也更简单。 **注释**   [子有序对引用](#子对象引用)和[子列表引用](#子对象引用)是[子对象引用](#子对象引用)。   当前有序对引用总是引用至少一个有序对的元素。   由绑定使用引用标记字符的[非递归绑定](#非递归绑定)的规则,绑定列表的子对象引用不直接共享操作数有序对对象,而共享元素是原容器元素的([经折叠的](#引用标记字符))引用值的有序对,是子有序对引用。   这类似宿主语言的容器对象一般不能转换为共享容器部分元素的 C++ 对象引用。 ### NPLA1 合并子   除非另行指定(如[强递归绑定](#强递归绑定)),对象语言中的所有合并子都是[真合并子](#合并子)。   NPLA1 对象语言不提供其它合并子的普遍操作。 **注释** 但[互操作](#npla1-互操作约定)意义上的 NPLA1 API 可支持其它合并子。   合并子和操作数组合构成的[函数合并](#函数合并)是一个 NPLA1 对象,称为*函数合并对象(function combination object)* 。 **注释** [求值算法](#npla1-规范求值算法)可接受的函数合并对象是[有序对](#npla1-广义列表)。[函数合并表达式](#函数合并)作为函数合并对象是[列表](#npla1-广义列表)。   NPLA1 的合并子使用*包装数(wrapping count)* 存储可能需要求值操作数的次数。   在不出错时行为和不使用包装数而直接使用嵌套子对象实现的行为完全一致,但在到达实现支持的最大包装数时继续包装即*包装数溢出(wrapping count overflow)* ,行为可能不相同:   若某个操作使合并子超出上限,则符合[非宿主资源耗尽的错误条件](#运行时错误条件)。   实现支持的最大包装数应满足:若发生包装数溢出,则直接创建和包装数相同个数的合并子符合[宿主资源耗尽的错误条件](#运行时错误条件)。 **原理**   为[可修改性](#可修改性),允许非真合并子。这可在互操作中表示类似合并子但在语言中不可见的非一等对象。   为维护语言规则的[简单性](#简单性),合并子默认是真合并子。   NPLA1 对象语言不提供其它合并子的普遍操作,这不保证完全满足类似 [G1b](#统一性) 的原则。   尽管[没有要求](#实体类型),这种规约也更符合 G1b ;同时,这易于移植 Kernel 代码。 **注释**   Kernel 的合并子对应 NPL 的[真合并子](#合并子)。   对最大包装数的要求需要实现支持包装数是能和宿主资源的空间相较规模的值,这保证使用包装数的实现的空间效率不弱于不使用包装数而直接分配合并子包装的实现。   这也表示通常[用户程序](#程序实现)的操作不会发生包装数溢出:若包装操作的次数导致包装数溢出,则直接分配合并子的替代操作也应由于宿主资源耗尽而失败。 ### NPLA1 数值   [数值支持](#附加功能)的实现兼容[NPLA 数学功能](#npla-数学功能)。   [NPLA1 数值字面量](#npla1-数值字面量)的[求值结果](#基本语义概念)是数值。 ## 函数的间接值使用约定   引用值作为间接值,首先符合作为实体的[引用](#npla1-引用)的使用约定。 ### 间接值作为实际参数   除非另行指定,一般地,函数接受左值引用操作数,使用引用的对象的值和直接使用右值作用相同,但不会修改被左值引用的对象。   这等价隐含无副作用的[左值到右值转换](#值类别转换),被视为蕴含左值到右值转换。 **注释**   另行指定的例子如[函数参数转发](#函数参数转发)。   此处的左值引用和宿主语言中的( `const` 非 `volatile` )左值作用类似。 ### 间接值作为函数值   部分[函数值](#函数合并)总是非引用值。   这些操作对应的函数调用返回非引用值。   返回非引用值的行为应等价[返回值转换](#返回值转换)。   其它操作可具有引用值结果,对应的函数调用可返回引用值。 **原理**   函数值非引用值可满足具体操作的语义要求(如非引用值的[构造器](#创建和访问对象的函数)),减少误用的可能性,并帮助提供[内存安全保证](#对象语言内存安全保证)。 ### 保留引用值   [保留间接值](#保留间接值),包括[直接保留间接值和间接保留间接值](#被保留的引用值的来源),适用[间接值](#间接值)是[引用值](#引用值)的情形,对应地称为保留引用值、直接保留引用值和间接保留引用值。   除非另行指定,被保留的引用值不被[折叠](#引用折叠)。 **原理**   必要时要求引用折叠可避免引入非预期的引用的引用值。   被保留的引用值可能[逃逸](#函数调用)或不逃逸而通常不能直接证明具有[内存安全保证](#对象语言内存安全保证)。 ### 保留环境引用   [保留间接](#保留间接值)值适用[环境引用](#环境引用)。 **注释**   和[保留引用值](#保留引用值)的情形不同,因为只允许通过环境引用在对象语言中访问[环境对象](#npla1-环境)及其子对象,访问环境但不保留环境引用的操作[只可能在(不保证内存安全的)互操作中出现](#对象语言基本内存安全保证)。 ## 函数参数和函数值传递约定   函数可能[接受引用值参数](#间接值作为实际参数)和[返回引用值](#间接值作为函数值),是对函数的形式参数或函数值的[初始化](#初始化)。   在复制初始化形式参数和[函数值](#函数合并)时,部分函数保证被初始化的值和[初值符](#初始化)的[值类别和可修改性](#值类别和类型)一致。这些初始化是[转发](#函数参数和函数值传递)操作。 **注释**   另见[函数参数和函数值传递](#函数参数和函数值传递)。 ### 传递非引用值参数   一些函数的参数进行[左值到右值转换](#值类别转换),实现参数的[按值传递](#求值策略)。   这类似宿主语言中直接使用对象类型的形式参数。 ### 函数参数转发   一些求值为引用值的函数的部分实际参数被保留,而不进行[左值到右值转换](#值类别转换)。   这些值以保留值类别不变的形式被直接作为操作数,用于调用其它合并子。这种参数被[转发](#函数参数和函数值传递)。 **注释**   这些参数的转发类似[绑定构造](#绑定构造)支持的[参数转发](#参数转发)。   参数转发的实现可判断[值类别](#值类别)后分别对[传递非引用值](#传递非引用值参数)或直接传递引用值提供实现,或直接使用绑定构造。前者支持本机实现。 ### 返回非引用值   返回非引用值和参数的[按值传递](#求值策略)类似:若初始化函数值的初值符是引用,复制或转移被引用对象的值而不是引用值。 **注释**   这类似宿主语言中返回 `auto` 类型。 ### 函数值转发   一些其它保留引用值的操作中,引用值来自参数,且难以通过自身的逻辑单独决定可否安全地直接返回引用值。   此时,在返回之前根据特定参数是否为引用值,可选地转换[函数值](#函数合并)以确定是否保留引用值,即进行转发。   特定的显式转发操作转发[临时对象](#临时对象)的[引用值](#引用值)使临时对象被转移,以转发的值作为结果,可不同于使用[返回值转换](#返回值转换): * 同返回值转换,转发转移右值,复制左值。 * 但当转发临时对象可确定唯一使用时,也转移临时对象。 **原理**   函数值转发使某些操作在默认情况下满足间接值生存期规则而保持内存安全,符合[适用性原则](#适用性)。 **注释**   确定是否保留引用值的机制类似 \[ISO C++14] 中从没有括号的 `id-expression` 上推断返回 `decltype(auto)` 类型是否为引用类型。   函数值转发的实现可通过判断是否需要转发引用而[按需决定返回引用值或非引用值](#间接值作为函数值),或使用标准库的相关函数。前者支持本机实现。   另见[对象的可转移条件](#对象的可转移条件)。   显式转发操作把右值、消亡值和带有[临时对象属性](#引用值的属性)的[左值引用](#引用值的子类型)视为被转发的目标。   转发列表对象的[子对象](#有序对的子对象和子对象引用)可能转移这个对象。 ### 创建和访问对象的函数   *构造器(constructor)* 是用于创建对象的函数。   除非显式指定创建的对象具有引用值类型,构造器是*典型的(typical)* ,[返回非引用值](#返回非引用值)。   部分操作涉及对其它对象具有所有权的对象。   一部分对象的构造器创建的对象完全通过其它对象的引用或[对象的值](#基本语义概念)作为构造器的参数而决定,且创建的对象对这些参数具有所有权,这样的对象称为容器(container) 。   容器构造器的参数作为容器的[子对象](#npla1-子对象),是容器的*元素(element)* 。   以容器对象或其引用作为参数,取得容器元素对象或其引用的函数是容器元素*访问器(accessor)* 。   标准库提供一些属于构造器和访问器的操作。除非另行指定,标准库的访问器符合[子对象访问约定](#子对象访问约定)。 **注释**   容器的元素扩展了[有序对和列表的元素](#npla1-广义列表)的概念。   一些不是容器的对象(如[真合并子](#npla1-合并子))可通过非容器形式的构造器创建。 ### 转发参数或返回值的实现   没有约定需要转发的情形不使用显式的转发。 **注释**   可[转发参数](#函数参数转发)或[转发返回值](#函数值转发)的函数可包含以下实现方式: * 使用特定的操作,以需被转发的表达式作为其操作数。 * (仅对参数转发)使用[标记字符](#引用标记字符) `%` 的[参数绑定](#非递归绑定)的变量。   上述特定的操作可在被求值的表达式中构造显式的转发。 # NPLA1 参照实现环境   NPLA1 提供参照实现环境。其实现可在内部使用 NPLA1 [库特性](#程序实现),提供给 NPLA1 [用户程序](#程序实现)。   NPLA1 参照实现环境和用户程序遵循部分不同的要求和约定。   本章中的其它约定适用 NPLA1 参照实现环境,且可选地被用户程序使用。 ## NPLA1 初始求值环境   NPLA1 以[环境对象](#npla1-环境)中的绑定作为公开的接口提供库特性,以进一步提供*初始环境(initial environment)* 作为[求值环境](#求值环境),即用户程序初始的[当前环境](#当前环境)。   这些环境包含两类: * *根环境(root environment)* :提供[核心语言特性](#程序实现)的[非一等环境](#npla1-环境)。 * 根环境应能和[一等环境](#npla1-环境)在[名称解析](#名称解析)中的作用一致:作为[父环境](#npla-环境)。 * *库环境(library environment)* :作为库特性的一等环境。 * **注释** 通常是其它环境的[子对象](#npla1-子对象)。   这些环境应按语言规范要求的方式[包含和展示](#求值环境)所有[可见的绑定](#求值环境)。   除非另行指定,这些环境对象初始化后在用户程序访问前被[冻结](#冻结)。   *基础环境(ground environment)* 是 NPLA1 程序可假定存在的一个根环境。   基础环境是[隐藏环境](#隐藏环境)。   除非另行指定: * 基础环境不展示名称语言规范要求的除[保留名称](#标识符附加规则)外的绑定。 * 根环境是否展示基础环境中的绑定未指定。   初始环境是一个包含基础环境作为直接或间接父环境的[空环境](#求值环境)。   实现可提供基础环境以外的根环境,允许派生实现定义在用户程序中[修改其中的绑定](#对象的修改和改变)的机制(而不一定是隐藏环境),直至被实现初始化参考环境的特定用户程序封装或冻结而避免进一步修改。   如有必要,用户程序可通过派生实现定义的方式引入其它根环境。 **原理**   这些环境对象设计为在参照实现环境提供,因为: * 不都保证能在用户程序中可移植地创建,而有必要在参照实现环境中提供。 * 提供的库特性在可移植程序中可能经常出现,而适合在参照实现环境中提供。   根环境在功能上不需要展示基础环境中的绑定,这允许简化初始化。但允许展示基础环境中的绑定也使[派生实现](#实体实现约定)能被简化。 **注释**   提供基础环境和 Kernel 类似。   [互操作](#npla-互操作支持)可能直接访问基础环境,这些操作应避免破坏实现和程序的假定。   特性设计注记: * 为避免依赖逻辑上复杂的形式,一些特性当前在当前设计中排除。 * 例如,依赖一阶算术的操作、其硬件加速形式的 ISA 表示的整数操作及依赖这些操作实现的[*数值塔(numerical tower)* (en-US)](https://en.wikipedia.org/wiki/Numerical_tower) 被整体忽略。 * 上述忽略的特性可由派生实现补充,在派生根环境后按需进行 AOT(ahead-of-time) 优化(如 Kernel 的 `$let-safe!` 中包含的内容,其中引用基础环境的符号不再可变),然后组成类似基础环境。   通过派生实现定义的方式一般依赖[本机实现](#npla-互操作支持)。 ### NPLA1 实现环境初始化   [实现环境](../Terminology.zh-CN.md#规范)的初始化完成初始环境的准备,包括蕴含所有初始环境依赖的资源*基础上下文(ground context)* 的初始化。   初始化基础上下文蕴含的根环境是[求值](#对象语言求值算法)默认使用的求值环境,初始化后可直接封装为基础环境使用。   派生实现可在以上初始化结束之后,在运行用户程序之前完成其它初始化。   初始化成功后,用户程序被运行;否则,程序非正常终止。   以上初始化同时可能提供[扩展字面量](#npla1-扩展字面量)支持。 **注释**   对初始化失败而终止的程序,建议但不要求实现给出诊断。 ### 导入符号   在环境中定义另一个环境中的同名变量,使被定义的变量是后者的引用值或值的副本,则称指定此[变量名](#npla-环境)的[符号值](#符号)在后者被*导入(import)* 前者。 **注释** 用户程序可导入环境中的符号值使用库中的绑定。 ## 模块   NPLA1 以绑定提供的语言特性被分组归类为[模块](#模块化)。 **注释** 同 \[RnRK] 。   模块的*源(source)* 提供特性的实现,可以是本机实现或者 NPLA1 程序。对应的模块分别是本机模块和[源程序](../Terminology.zh-CN.md#程序设计语言)模块。   模块的源可以是实现内建的,或位于实现环境提供的外部资源(如文件系统)。   因为模块以绑定的集合的形式提供,需被包含在可访问的环境,或包含环境作为[子对象](#npla1-子对象)的其它对象中。   以[环境对象](#npla1-环境)作为模块的源的模块化方式称为*环境作为模块(environment as module)* 。\[RnRK] 的 `get-module` 的结果和[参照实现扩展环境](#npla1-参照实现扩展环境)的模块是这种方式的例子。   模块可能包含*子模块(submodule)* 提供其特性子集。以环境作为模块时,[环境子对象](#npla1-环境)可作为子模块。   从模块的源得到提供一个模块的所有绑定集合的环境对象的过程称为模块的*加载(loading)* 。   模块加载可能失败。失败的模块加载[引起错误](#npla1-错误)。   [根环境](#npla1-初始求值环境)加载的失败不被直接依赖这些环境的 NPLA1 用户程序处理(而视为实现初始化的运行时错误)。   一般地,模块和加载模块得到的环境对象不保证具有直接的对应关系:一个模块的绑定可以由一个或多个环境提供,一个已被加载的环境可能提供零个或多个程序可见的模块。但除非另行指定,一个模块的绑定不在超过一个的不相交的环境(之间没有直接或间接父环境关系)中提供。   [库](#程序实现)的实现可作为[语言实现](../Terminology.zh-CN.md#规范)。[库特性](#程序实现)可以被模块化,在其中通过[库环境](#npla1-初始求值环境)一并被提供。   程序可通过加载外部模块来源取得模块。除非另行指定,这种模块以一个一等环境对象(可包含作为环境的直接或间接子对象)中的绑定提供。 ### 模块的初始化和加载   [实现环境初始化](#npla1-实现环境初始化)时可提供模块,包含其中必要的使模块中特性可用的初始化。   实现环境初始化时,可访问不作为公开接口提供的模块的源,从源*加载(load)* 这些模块。 **注释** 派生实现可同时以标准库以外形式提供这些源为公开接口,用户程序也可显式地加载这些源对应的模块。   待加载的源的位置可通过称为*加载路径(load path)* 的字符串指定。加载路径可精确指定确切的源,或提供使实现*搜索(search)* 特定的源的起点。具体可接受的加载路径格式以及加载路径映射到确切的源的规则由提供特性的实现定义。 **原理** 有的语言实现直接使用加载路径作为搜索路径,如 [GNU Guile](https://www.gnu.org/software/guile/manual/html_node/Installing-Site-Packages.html) 。有的语言实现基于不同的原语,称为*导入路径(import path)* ,如 [Python](https://docs.python.org/3/reference/import.html) 。   除非另行指定: * 若这些源可能引起引入非公开的接口的副作用,则对应的模块不应被用户程序直接加载。 * 假定加载这些模块时,[当前环境](#当前环境)是和[标准环境](#基本派生特性)或与其等价的其它环境。   * **注释** 关于标准环境,参见 [`make-standard-environment`](#基本派生特性) 。 * 其中,等价指使用其它环境不引入程序[可观察行为](#状态和行为)差异。 * **注释** 等价的环境的例子包括以标准环境为父环境的[空环境](#求值环境),以及这样的空环境[导入符号](#导入符号)的得到的结果。 * **注释** 若模块的加载不访问加载时初始的当前环境(通常仅在本机模块上适用),加载模块使用的环境可不影响可观察行为而不影响假定(即便和标准环境不等价)。   违反以上要求或假定的程序[行为未定义](#npla1-未定义行为)。 **原理**   以源程序模块实现时,一般不要求检查[初始环境](#npla1-初始求值环境)。这能有效减少实现的复杂性。   因为标准环境不提供用户程序检查是否和其中定义的实体一致的直接的方法,通过替代的检查可能排除符合假定的初始环境。 **注释**   这里的初始化可包含派生实现定义的其它初始化。   虽然 NPLA1 标准库不作为接口保证提供这些源,这里的假定和 \[ISO C++] [\[using.headers\]](https://eel.is/c++draft/using.headers) 对引入标准库头的程序位置的限制类似:语言实现能有效地假定源程序中引入标准库头的上下文,因此标准库中的名称具有预期的含义。 ### 模块稳定性   提供模块绑定的环境依赖已知来源的绑定而确保[稳定](#环境的稳定性)。   除非另行指定,模块中的特性依赖提供模块绑定的环境的生存期。   除非另行指定,标准库实现应确保其中的模块在程序的生存期中可用。 **原理**   特性依赖性允许实现操作的模块中绑定的[合并子](#npla1-合并子)可具有静态环境是提供模块绑定的环境的[子对象](#npla1-子对象)的合并子的实现。 **注释**   稳定要求同 \[RnRK] 的 `get-module` 的约定。但因为[值稳定性](#被绑定对象的值和可观察行为) ,[和 Kernel 不同](#环境的稳定性),NPLA1 的稳定绑定[一般不可修改](#被绑定对象的值和可观察行为)。   对标准库模块,稳定性要求一般表示其中的特性不能依赖用户程序运行时的非特定的[当前环境](#当前环境),而可依赖从[基础环境](#npla1-初始求值环境)及从基础环境派生的[新环境](#新环境)。   生存期可用的规则一般要求标准库实现在初始化后保存[环境强引用](#环境引用)。 ## 库接口约定   [基础环境](#npla1-初始求值环境)的特性在[根环境](#npla1-初始求值环境)中直接绑定,统称根环境特性。   关于特性的约束作用于接口描述。不改变[可观察行为](#状态和行为)时,实现可使用不同的未指定的根环境提供绑定。   描述模块接口的小节可以指定适用于该小节的模块约定。此时,描述的边界应能和其余的[实体](#库接口实体)区分。   接口可能提供关于宿主语言[互操作的约定](#npla1-互操作约定),作为对提供这些支持的实现的要求。 **注释**   具体特性参见 [NPLA1 根环境特性](#npla1-根环境特性)。   具体根环境的存在性未指定。在同一个环境中可见的不同变量可能来自不同的根环境。   接口描述的顺序同 \[RnRK] §4 的原理,允许接口仅依赖先前出现的接口派生实现。   和 \[RnRK] 不同,库主要提供[一元谓词](#类型分类),也不需要为 `` 隐含 `$sequence` 支持重新定义 `$vau` 等操作,不需要拆分 Kernel 的核心库特性到 \[RnRK] §5 和 §6 。 ### 库接口实体   按实体区分,NPLA1 的库特性有两类:[对象](#基本语义概念)和[操作](#规范化中间表示)。   对象语言中可实现的操作以[函数](#函数)的形式提供,可以是[本机实现](#npla-互操作支持)的[宿主语言](#实现的执行阶段)函数或由现有操作派生的[合并子](#npla1-合并子)。   除此之外,派生实现可指定提供对象或操作对应的[非常规函数](#非常规函数)。   操作的[结果](#规范化中间表示)是对应的[函数调用](#函数调用)在[正常控制](#程序的控制执行条件)下取得的[求值结果](#基本语义概念),即[函数值](#函数合并);操作的作用即[函数调用的作用](#规范化中间表示)。   根据操作的功能描述,对应的函数可能具有[非正常的控制条件](#程序的控制执行条件)。此时,函数调用不取得函数值,操作不具有结果。 **注释** 非正常退出时,函数调用的求值结果可以是[错误对象](#npla1-错误)或派生实现定义的其它表示求值结果的实体。   除非另行指定,函数调用时具有的[错误条件](#运行时错误条件)是非正常的控制条件;其中,以[异常](#npla1-异常)实现错误条件的情形具有[异常条件](#异常)。   特定的操作约定对应的函数是[终止函数](#终止保证)或[全函数](#终止保证);这不适用于满足错误条件的情形。   特定的操作约定对应的函数[作为算法过程满足计算复杂度约定](#计算复杂度约定)。 **注释** 排除错误条件,指定复杂度的函数是终止函数。   本章其余各节适用 NPLA1 对象语言中的这些操作。   操作中的大部分具有特定的名称,满足[函数名称约定](#函数名称约定)。   其它操作不具有特定名称,可由上述操作间接地提供,如蕴含在某些操作涉及的[函数值](#函数合并)中。 **注释**   在对象语言中不能直接表达的操作不能作为库特性,这些操作不对应库接口实体,其结果和作用仍照[更一般的规则](#规范化中间表示)处理。   渐进复杂度常以 [O 记号](https://zh.wikipedia.org/zh-cn/%E5%A4%A7O%E7%AC%A6%E5%8F%B7)指定上界。   若函数调用总是取得值,指定复杂度的函数同时是全函数。 ### 库特性实现分类   库特性分为*基本的(primitive)* 和*派生的(derived)* 。 **原理**   前者在设计上不分解为更小的其它特性的组合,通常需要本机实现;后者可由可移植的 NPLA1 源代码实现。 **注释**   区分基本和派生的特性在设计上类似 \[RnRK] 中的基本和库特性。   注意和 \[RnRK] 的库特性不同(而更接近宿主语言),NPLA1 的库特性是以 NPLA1 程序使用的接口而非实现的角度定义的,不总是使用对象语言实现,外延更广。 ### 标准库   本文档中要求的通过[基础环境](#npla1-初始求值环境)直接或间接提供的[库](#程序实现)总称*标准库(standard library)* 。   标准库的接口随语言规范在本章和[参照实现扩展环境](#npla1-参照实现扩展环境)约定。   *核心库(core library)* 是提供直接绑定在基础环境中的、保证可派生实现的接口的标准库模块。   在参照实现环境中的不同标准库模块的绑定都可在基础环境访问。   在参照实现扩展环境中的标准库模块以其它环境(通常作为基础环境的子对象提供)中的绑定和基础环境隔离。   派生实现可以库的形式提供语言扩展或其它功能特性,扩充标准库。 **注释**   因为[库的定义](#程序实现)和 \[RnRK] 指定的不同,类似 \[RnRK] §4 约定的基本特性,属于 NPLA1 [库特性](#程序实现)。   [基本派生特性](#基本派生特性)可能从[核心库](#核心库)中迁移。因为绑定的来源不变,使用其中的接口程序仍然可能保持兼容。 ### 扩展库   基础环境也可提供的附带的其它接口,和标准库使用相同的约束。   一些操作的描述使用等价的表达式求值指定。除非另行指定,这些表达式中: * 若[符号值](#符号)和先前出现的函数同名,则指称对应的操作。 * 默认使用基础环境作为求值环境。 ## 常规函数约定   本节提供[作为库特性的函数](#库接口实体)的默认规则以简化库特性的描述。 **注释** 库的一般派生实现和用户程序的实现也建议参照本节约定。   除非另行指定: * 操作以指定名称的变量的形式提供,求值为可参与[函数合并](#函数合并)的一等实体(但函数合并不一定保证是[合式的](#翻译时正确性规则)可求值的表达式)。 * 函数作为表达式,求值为[合并子](#npla1-合并子),其函数合并的求值蕴含[函数调用](#函数调用)。 * 本文档约定的函数在其调用不依赖用户程序提供的非终止函数时,总是[终止函数](#终止保证)。 * 若满足上述条件的函数不具有[错误条件](#运行时错误条件),忽略因实现环境引发的错误(如[宿主资源耗尽](#运行时错误条件)),视为(对象语言中的)[全函数](#终止保证)。 * 本文档约定的函数蕴含以下情形时,调用非[纯求值](#求值性质): * [引起](#错误)操作或其它语言规则指定的[错误](#npla1-错误)。 * 引起[对象的修改和改变](#对象的修改和改变)。 * 在指定对应的函数调用是纯求值的操作上,排除满足引起非纯求值的条件的作用的情形外的其余求值,仍应是纯求值。 **原理**   一些操作因不保证排除[副作用](#基本语义概念) ,对应的调用非纯求值。   但通过补充约定,特定的作用可能视为[未指定行为](../Terminology.zh-CN.md#程序设计语言),且不被程序的其它[行为](#状态和行为)依赖(如[可变管理状态](#实体的不可变性)的改变),仍可假定其求值是视为纯求值。 **注释**   无条件遍历访问[循环引用子对象](#循环引用)的程序具有 [NPLA 未定义行为](#npla-未定义行为),在讨论终止函数时已被排除。   对象的修改和改变可包括[转移](#对象的复制和转移)参数。 ### 函数值约定   除非另行指定: * [函数值](#函数合并)不是[引用值](#引用值),以便通过保证满足[间接值](#间接值)生存期规则维护[内存安全](#内存安全)。 * 函数不在函数值中[保留引用值](#保留引用值),即[函数值经过返回值转换](#间接值作为函数值)。 * **注释** 保留引用值时,函数值仍可能是非引用值。 * 若函数值保留引用值,引用值被[折叠](#引用折叠)。 ### 实际参数约定   除非另行指定: * 函数的实际参数的[传递](#求值策略)不具有影响[可观察行为](#状态和行为)的[作用](#基本语义概念)。 * **注释** 这一般要求避免复制宿主语言中复制时具有副作用的对象。 * 函数的实际参数若被求值,[蕴含左值到右值转换](#间接值作为实际参数)。 ### 错误处理   除非另行指定: * 若函数合并指定的操作的[约束](#接口文法约定)或要求[检查](#错误检查)的条件不被满足,[引起错误](#npla1-错误)。 * 引起错误时[抛出异常](#npla1-异常)。   求值时引起的错误使求值中断,[可引起副作用](#作用使用原则),这样的副作用总是[后序](#规约顺序)于已被求值的表达式中引起的副作用。   被错误处理和检查的函数不[修改](#对象的修改和改变)参数或者函数调用外创建的对象。 ### 非常规函数   [续延](#过程)是默认不符合[常规函数约定](#常规函数约定)的例外。   非常规函数归类为对象而非[操作](#库接口实体),但调用时[错误处理](#错误处理)同常规函数。 **注释**   类似 \[RnRK] 而和 \[RnRS] 不同,作为一等对象的续延和续延的实际参数是否求值无关,因此不是合并子,且默认[求值算法](#对象语言求值算法)不支持续延作为函数合并被求值;但续延可通过特定的操作转换为应用子。 ## 函数名称约定   除非另行指定,本文档以指定名称的函数表示具有名称的操作时,其命名由本节的规则约定。   *函数名(function name)* 即函数的名称。 **注释** 除非派生实现另行指定,[函数名是变量名](#npla-环境)。   为提供库的描述,本节同时约定这些函数名关联的函数具有的性质。 ### 函数名称前缀   确定为求值为操作子的函数名以 `$` 作为命名的前缀。 **原理**   同 \[RnRK] ,操作子一般应在视觉上被强调而避免误用。`$` 来自 **S**pecial form 。 ### 函数名称后缀   函数名的最后的字符表示函数预期满足特定的约束或具有特定的目的。   以[引用标记字符](#引用标记字符)结尾的表示涉及引用的操作,参见[以下](#引用标记字符的函数名后缀)约定。 #### 谓词名称后缀   NPLA1 中的[谓词](#基本语义概念)是 [``](#求值得到的操作数),即返回类型为 [``](#求值得到的操作数) 的函数。   谓词的名称使用 `?` 结尾。 **注释** 这类似 \[RnRS] 和 \[RnRK] 。   除非另行指定,以下引入的对象语言中的谓词对应的函数调用的求值是[纯求值](#求值性质)。   以下是谓词的典型实例: * [类型谓词](#类型谓词):接受一个 [``](#求值得到的操作数) 参数,判断参数是特定的类型的对象。 * 调用这些类型谓词不引起错误。 * 仅当参数指定的对象具有对应类型时结果是 `#t` 。 * 除非另行指定,这些类型谓词忽略值类别的差异。 * [等价谓词](#实体的等价性):接受两个参数,判断参数是否属于同一个等价类。   因为 `` 是 [``](#求值得到的操作数)的[子类型](#npla1-类型检查),按照返回值为 `` 的函数可在不严格要求 `` 的上下文中起类似的作用,视为*广义谓词(general predicate)* 。   谓词是广义谓词的子类型。 **注释**   大多数上下文接受 `` 而不严格要求 `` 。这[和 \[RnRK\] 不同](#求值得到的操作数)。   大多数类型谓词判断的类型一般同[文法约定](#接口文法约定),而无关[值类别](#值类别和类型)。   以具名的函数提供的类型谓词,其函数名称通常和[文法指定的类型](#实体元素文法约定)对应。   引起错误可具有副作用,其求值不保证是纯求值。   广义谓词不使用后缀 `?` 。另见 \[RnRK] §6.1.2 关于 `$and?` 的原理及 \[R6RS-Rationale] §15.1 。 #### 赋值函数名称后缀   [修改](#对象的修改和改变)一个对象而不要求第一参数是引用值且不改变被赋值对象类型的[赋值操作](#赋值)对应的函数名以 `<-` 结尾。 **注释**   这通常和宿主语言的赋值操作对应,可能有附加的副作用而不是简单地替换值。 #### 修改函数名称后缀   除[函数名以 `<-` 结尾](#赋值函数名称后缀)外的操作中,为了蕴含(不直接通过求值操作数或其子表达式引起的[副作用](#基本语义概念)的)[修改](#对象的修改和改变)的函数是*修改函数(modification function)* ,其名称使用 `!` 结尾。   [可变管理状态的改变](#对象的修改和改变)不需要指示可修改;此外,类似地,不改变[可观察行为](#状态和行为)的[隐藏状态](#状态和行为)的修改不属于上述修改。 **注释** 这类似 \[RnRK] 。   这类操作同时是[改变操作](#对象的修改和改变)。 **注释** 这类似 \[RnRS] 和 \[RnRK] 。   一些其它情形下,特定的函数不被视为改变操作但其调用仍可能引起副作用,这样的函数名不要求带有 `!` 后缀: * 直接求值操作数、其子表达式关联的对象可能引起副作用。 * **注释** 这和 \[RnRK] 类似。 * 一些操作可能非确定性地包含使操作数指称或引用的对象的值[有效但未指定](#实体的副本)的修改。 * **注释** 这和 \[RnRK] 不同。 * **注释** 如[隐式的转移操作](#转移导致的修改)。 * 这不包含预期成功时确定总是转移的操作。 * 排除不允许任何修改(如总是进行[类型错误](#npla1-类型检查),要求操作数是[不可修改引用](#引用值的属性))的情形,即便转移后[状态不变](#实体的不可变性),仍被视为修改。   修改函数的调用的求值可能因为不同的操作数的影响,而可能依赖具体的条件确定是否改变对象: * 当通过操作数直接指定的条件确定是否修改时。 * 当通过操作数确定被修改的对象时。 * 当通过操作数确定引起修改的对象时。   这些函数的[返回值](#函数调用)是[未指定值](#基本语义概念)。 **注释**   修改函数的调用不一定总是具有能视为修改的副作用,可能不改变任何对象。   这些函数的返回值的规定和 \[RnRS] 相同,但和使用可用谓词 `inert?` 判断的*惰性(inert)* 值的 \[RnRK] 不同。 ### 引用标记字符的函数名后缀   一些操作以([可能的 `!`](#函数名称后缀) 前)结尾[引用标记字符](#引用标记字符)和不以引用标记字符结尾的名称提供多个变体。其中不含结尾的引用标记字符的表示[操作的结果](#库接口实体)不是引用值,要求[按值传递](#求值策略)。   其它一些操作可能只提供以 `%` 结尾的变体。   不使用引用标记字符的函数及其函数值时,不因引入引用值违反[内存安全](#对象语言内存安全保证)。 **原理** 这允许通过避免公开带有引用标记字符后缀的操作提供一个内存安全的子集,以在派生实现对语言进行裁剪。   名称以引用标记字符结尾的操作属于以下分类之一: * 可能[直接保留引用值](#保留引用值)的操作:操作的[结果](#规范化中间表示)及[作用](#规范化中间表示)依赖实际参数(需要时经过隐含的[左值到右值转换](#引用标记字符))的值。 * 可能[间接保留引用值](#保留引用值)的操作:可涉及不同的环境,结果和作用依赖实际参数(需要时经过隐含的左值到右值转换)在这些环境中被求值后确定。   对可能在函数值中间接保留引用值的操作,以 `%` 结尾表示对应的函数返回时[不要求返回非引用值](#间接值作为函数值)。   其它可能在函数值中直接保留引用值的提供不同引用标记字符的多个变体的操作: * 以 `%` 结尾表示函数使用不进行[左值到右值转换](#值类别转换)的[折叠](#引用折叠)的引用值参数,或返回折叠的引用值。 * 以 `&` 结尾表示函数使用不进行左值到右值转换的折叠的引用值参数,或返回折叠的引用值。 * 以 `@` 结尾表示函数使用不进行左值到右值转换的[未折叠的引用值](#多重引用)参数,或返回未折叠的引用值。   以上引用值参数的使用指以依赖这些参数的方式构成函数的[返回值](#函数调用)和/或决定引起的相应的副作用;返回的引用值来自引用值参数(若存在)。 **原理**   为满足[适用性](#适用性),同时考虑避免误用和允许使用引用避免复制,对一些操作显式使用以 `%` 或 `&` 结尾的函数名称以得到特别关注。   因为语义相关,结尾引用标记字符使用和绑定的[引用标记字符](#引用标记字符)相同的字符,但不复用具体规则。   尽管设计时没有参照,使用函数结尾的引用标记字符和其它一些语言的类似特性的使用惯例也一致,如 [PHP 的 function `&` 语法](https://www.php.net/references.return)。 **注释**   按这些规则,函数名以 `%` 结尾的操作在取折叠的引用值时,可能同时实现[被引用对象](#一等引用)的转发。这相当于被访问的被引用对象作为宿主语言的 `std::forward` 的参数后的调用结果作为操作的结果,但此处一般仍然保证支持 [PTC](#尾调用和-ptc) 。   函数名以 `&` 结尾的操作取得的折叠的引用值可能是[唯一引用](#引用值的属性)。 #### 引用折叠的约定   取得折叠的引用值的默认约定同[NPLA1 默认规则](#npla1-引用值使用约定)。   以上函数[返回值](#函数调用)中: * 折叠的引用值对调用时引入的引用值(不论是否来自参数)有效。 * 除非另行指定,不对同一个对象引用折叠多次。 * **注释** 这些规则不保证结果是[完全折叠的引用值](#多重引用)。返回折叠引用值的函数可因未完全折叠的引用值参数等返回未折叠的引用值。   这允许函数的内部实现引入一次引用值时,对来自每个参数的引用值至多只需要实现一次折叠。   推论:若参数都不是未折叠的引用值,调用名称不以 `@` 结尾的函数不引入未折叠的引用值。   若指定的操作按不同操作数可涉及或不涉及和当前不同环境下的求值,提供不保留引用值和保留引用值的多个变体的操作以便保证内存安全。 **注释**   提供不保留引用值和保留引用值的多个变体的操作以便保证内存安全的操作包括[可提供以引用标记字符结尾变体的函数](#可提供以引用标记字符结尾变体的函数)和[不提供结尾引用标记字符对应变体的函数](#不提供结尾引用标记字符对应变体的函数)指定的操作。 #### 引用标记字符函数名与内存安全的关系   利用区分引用标记字符结尾的操作,可指定具体关于具体操作的[对象语言接口的安全保证机制](#对象语言接口的安全保证机制)。 **原理**   [函数名](#函数名称约定)结尾的引用标记字符用于强调无法总是保证内存安全的[危险操作](#适用性)。   一般地,仅在明确需要引用值时使用引用标记字符结尾的操作,而避免返回悬空引用。这类似宿主语言函数的 `auto` 而非 `auto&&` 的返回类型,但宿主语言中返回非引用类型的表达式两者含义不同。   函数名不带有引用标记字符结尾的操作通过避免[保留引用值](#保留引用值)提供一定的[内存安全保证](#对象语言内存安全保证),而带有引用标记字符结尾的操作较容易引起注意。   这符合[易预测性](#易预测性)。 **注释**   一个典型例子是在函数中[返回标识符求值](#对象语言求值算法)的表达式: * [标识符求值后指称左值引用值](#npla1-规范求值算法),这个引用值的有效性依赖[合并子调用](#合并子)时创建的[新环境](#npla1-环境)的可被访问。 * 这个环境在调用后通常被销毁,若使用带有引用标记字符结尾的操作关于对应的函数返回引用值的语义,在函数值中保留引用值,返回为[悬空引用](#无效的引用值),容易误用。 #### 保留引用值的约定   可能[直接保留引用值](#保留引用值)的操作中,不带有引用标记字符的操作[传递非引用值参数](#传递非引用值参数),其它函数[转发参数](#函数参数转发)。   可能直接保留引用值的操作包括[容器构造器或访问器](#创建和访问对象的函数),以及可能[使对象中包含引用值](#被保留的引用值的目标)的[修改操作](#对象的修改和改变) 。   这些操作的[结果](#库接口实体)或引起的副作用完全由实际参数(根据是否存在引用标记字符 `%` 指定是否不经过隐含的左值到右值转换)的值确定。   其中,带有引用标记字符结尾的操作是直接保留引用值操作。   容器构造器可在元素保留参数的引用值。作为结果的容器总是[作为非引用值返回](#间接值作为函数值),即在结果中保留参数的引用值。 **原理**   以上操作是否确定地保留引用值在一些情形容易证明[附加调用安全](#安全性附加证明),此时可放宽[安全特性子集](#安全操作子集)的条件确保安全性;在此不作要求。   对构造器及部分修改操作区分引用标记字符结尾可强调一些非预期保留引用值的容易误用情形;尽管总是返回非引用值。   因[转发参数而被保留的引用值](#保留引用值)不会被[返回值转换](#间接值作为函数值)或类似的操作影响,在构造的容器对象作为非引用值返回时,仍会保留引用值。对应宿主语言中,可有更显著的差异,如构造器对应的 `std::tuple` 的 `std::make_tuple` 和 `std::forward_as_tuple` 。 ### 可提供以引用标记字符结尾变体的操作   部分操作使用[以引用标记字符结尾的函数名](#引用标记字符的函数名后缀)。   可提供不同变体的操作被严格限制,以避免过度区分造成在使用上不必要的复杂性。 #### 可能使结果包含引用值的容器构造器   容器构造器作为包括典型的构造器,可提供不同的变体(或其中之一): * 不在函数值中保留引用值,实际参数发生左值到右值转换作为容器的元素,这减少误用[悬空引用](#无效的引用值)的可能性。 * 在函数值中保留引用值,实际参数不发生左值到右值转换而直接作为容器的元素,是[不安全操作](#不安全操作),但可以确保构造的对象中包含参数指定的引用值。 **注释**   当只提供没有结尾引用标记字符对应名称的操作时,不需要满足[以下](#不提供结尾引用标记字符对应名称的操作)的约定。   在结果中保留参数的引用值的[容器构造器](#保留引用值的约定)可能[保留可能无效的间接值](#保留间接值)而属于不安全操作。 #### 可能使结果包含引用值的容器元素访问器   带有引用标记字符结尾的操作是[直接保留引用值](#保留引用值)操作。   函数名不带有标记字符结尾的访问器属于[参数转发操作](#函数参数转发)和[函数值转发操作](#函数值转发)。 #### 可能使对象中包含引用值的修改操作   [修改对象或对象的子对象](#npla1-子对象)可[无效化引用值](#间接值)而影响内存安全。   对[可能保留参数中的引用值](#被保留的引用值的来源)的操作,内存安全也依赖这些操作的指定修改后的值的内存安全性。   在判定[内存安全](#对象语言内存安全保证)的意义上,以下操作的所有参数都可能是[被保留的间接值](#保留间接值): * *简单赋值(simple assignment)*(包含于[赋值操作](#赋值))。 * 列表元素*改变器(mutator)* 。   修改的结果由实际参数(必要时经过隐含的[左值到右值转换](#实际参数约定))的值确定。   以上操作都要求检查表示被修改的参数是左值。   以上操作中,带有引用标记字符结尾的操作[在对象中直接保留引用值](#保留引用值)。 #### 可能间接保留引用值的操作   一些操作可涉及不同的环境,参数在这些环境中被求值可能得到引用值。   这些操作包括求值为操作子的以下函数: * 以求值 `` 作为[尾上下文](#上下文相关求值)的操作。 * 以求值 `` 初始化 `` 指定的对象的[绑定构造](#绑定构造)。 * 以求值 `` 或视为 `` 的 ``(及可能发生的[返回值转换](#间接值作为函数值))作为唯一作用的函数。   以上操作中,带有引用标记字符结尾的操作是间接保留引用值操作,表示求值结果不要求[按值传递](#求值策略)并可[返回引用值](#间接值作为函数值)。 **原理**   不提供[函数值转发](#函数值转发)的形式,因为: * 选用经过[返回值转换](#返回值转换)得到的值已可保证避免求值结果中的[悬空引用](#无效的引用值)。 * 直接返回可能是引用值的值具有更简明直观的语义。 * 若需转发,可直接[在被求值的表达式中显式构造](#转发参数或返回值的实现)。 * 不需要假设求值结果[初值符](#初始化)作为函数值转发依据的参数。   和此处直接在参数中给出被求值表达式不同,应用子中的一些求值的操作不属于上述操作,而不提供[结尾引用标记字符对应名称的操作](#可能间接保留引用值的操作)。 ### 不提供结尾引用标记字符对应名称的操作   其它操作不使用[以引用标记字符结尾的函数名](#引用标记字符的函数名后缀)。   若这些操作的结果直接[来自操作数或其子对象](#npla1-子对象)(和以 `%` 结尾操作的情形类似),则: * 可[保留参数中的引用值](#被保留的引用值的来源)并[在函数值中保留](#被保留的引用值的目标)。 * 不提供避免保留值的对应操作,或仅在此基础上提供总是包含等价[返回值转换](#间接值作为函数值)的对应操作。   否则,这些操作不具有引用值结果。   部分操作的结果直接来自实际参数。此时,若不具有引用值结果,则[蕴含左值到右值转换](#实际参数约定)。 **原理** 这些操作不会使用[临时对象](#临时对象)作为环境,所以不需要使用以引用标记字符结尾的变体要求注意区分返回引用值而避免误用。因此,不提供区分涉及引用的变体,这也使接口设计更清晰。   这些操作包括以下小节的情形。   部分操作涉及[参数转发](#函数参数转发)和[函数值转发](#函数值转发)。这些操作不包含可提供[以引用标记字符结尾变体的操作中的个别变体](#可提供以引用标记字符结尾变体的操作)。   其它不提供结尾引用标记字符对应名称的操作暂不保证支持保留引用值。   部分操作的内存安全性和可提供[以引用标记字符结尾变体的操作](#可提供以引用标记字符结尾变体的操作)类似,也是在函数值中保留引用值的不安全操作,但仅在引用值参数被保留且以此访问[被引用对象](#一等引用)时体现。   这包括[直接保留引用值和间接保留引用值](#保留引用值)的不同情形。   除[可直接以引用值作为结果的操作](#可直接以引用值作为结果的操作)和其它节的操作不相交,以下分类对操作的参数和函数值分别约定,可能相交。 #### 可直接以引用值作为结果的操作   一些求值为操作子提供的函数选取特定的参数进行求值,作为控制操作。   操作数中被求值的参数直接决定是否为引用部分操作直接返回引用值。   被求值的 `` [蕴含左值到右值转换](#间接值作为实际参数),其它被求值的参数不蕴含左值到右值转换,调用者需负责决定是否求值其它参数。 **注释**   这类似宿主语言中参数传递和返回 `auto&&` 类型。 #### 不以引用值作为结果的操作   部分操作类似[容器构造器](#可能使结果包含引用值的容器构造器)保证[返回非引用值](#返回非引用值),但并非直接以参数实现决定函数值:   若非构造器的操作总是返回列表和其它对元素具有所有权的容器对象,返回的对象总是按值传递。   为简化接口以及满足其它分类(如[直接参数转发操作](#直接参数转发操作)),不提供不保留引用值的操作。   和提供不同的变体的作为构造器的操作不同,此处的情形的结果可能包含引用值(和以 `%` 结尾构造器的情形类似)。   若需要排除通过参数引入的引用值,应进行适当处理使参数中不含有会使这些操作引入引用值的构造。   类似保留引用值的容器构造器,这些操作[可在结果中保留参数的引用值](#保留引用值的约定)。 #### 直接参数转发操作   部分[不带有引用标记字符的参数转发操作](#函数参数转发)是[可能直接保留引用值的操作](#保留引用值),称为直接参数转发操作。   函数名不使用引用标记字符,和[可直接保留引用值的函数名](#可提供以引用标记字符结尾变体的操作)使用引用标记字符不一致: * 本节约定的函数和可直接保留引用值的函数名中带有 `%` 结尾的函数同属参数转发操作,但后者同时有不带有引用标记字符的变体。 * 本节不约定和可直接保留引用值的函数名中不带有引用标记字符结尾的函数对应的操作。   这种不一致(和[函数值转发操作](#函数值转发操作)不同)是预期的特性:   和可直接保留引用值的操作不同: * 这些操作并非用于构造对参数具有所有权的对象,不适合提供不保留引用值的操作。 * 这些操作并非用于[取子对象](#npla1-子对象),返回值不一定是引用值,和具体操作相关,不适合使用引用标记字符区分。 * 为简化接口及满足其它分类(如[不以引用值作为结果的操作](#不以引用值作为结果的操作)),不适合提供不保留引用值的操作。   本节约定的函数对引用标记字符的使用和可提供[以引用标记字符结尾变体的操作](#可提供以引用标记字符结尾变体的操作)的函数名的使用不一致,含义相当于前者的结尾的 `%` 。   以下的[函数值转发操作](#函数值转发操作)同时也是直接参数转发操作。   其它函数的参数传递的一般规则参见[引用值作为实际参数](#间接值作为实际参数)、[函数参数和函数值传递约定](#函数参数和函数值传递约定)和[实际参数约定](#实际参数约定)。 #### 函数值转发操作   若其它情形确需非转发操作取得引用值,可使用[带有 `%` 或 `&` 结尾的操作](#可提供以引用标记字符结尾变体的操作)及可直接[以引用值作为结果的操作](#可直接以引用值作为结果的操作)替代实现。   本节约定的函数不使用引用标记字符,和[容器元素访问器](#可能使结果包含引用值的容器元素访问器)的函数名不使用引用标记字符一致:   本节约定的函数和上述容器元素访问器的函数名中不带有引用标记字符结尾的函数同属函数值转发操作,但后者同时有带有引用标记字符的变体。 **原理**   和[容器构造器](#可能使结果包含引用值的容器构造器)引入引用值的情形不同,不带有后缀 `%` 相对不容易引起误用,因为返回值保留的引用[可以继续被返回值转换](#间接值作为函数值)影响。   例如,使用[保证返回非引用值的涉及环境中求值的操作](#可能间接保留引用值的操作),引用值会在引用的对象生存期结束前被返回值转换而不影响内存安全。 #### 可能间接保留引用值的无引用标记字符对应名称的操作   类似可提供[以引用标记字符结尾变体的对应操作](#可能间接保留引用值的操作),部分不带有引用标记字符的操作可能[间接保留引用值](#保留引用值)。   这包括由类型为合并子的参数(而非 `` 或 `` )决定是否保留引用值同时对其它参数进行转发的操作。 ### 函数名称中缀   中缀 `->` 在函数名中可能出现一次,表示其(移除前缀和后缀之后的函数名中的)左边为源类型名的值到右边为目标类型名的值的转换操作。   除作为源类型的值外,可能支持可选的附加其它参数。   除非另行指定,转换得到的值是[纯右值](#值类别)。   除非另行指定,转换函数调用的求值是[纯求值](#求值性质)。   除非另行指定,若被转换的[值的复制](#对象的复制和转移)可能影响[可观察行为](#状态和行为),被转换的值被[转发](#函数参数转发)以[初始化返回值](#间接值作为函数值)。其中的右值被转移时,使用[对象的转移](#对象的复制和转移)或[项的转移](#项的转移)未指定。   除非另行转定,按[接口文法约定](#接口文法约定)引入的操作数作为对应名称之间的转换,仅在引入其中之一的模块提供。具体规则如下: * 源或目标具有在同一个[根环境](#npla1-初始求值环境)或其中的环境引入的对象类型时,在根环境中提供转换函数的名称。 * 源或目标具有从根环境中的 [`std.strings`](#字符串库) 以外的环境中引入的类型时,仅在引入其中之一提供转换函数的名称。 * 否则,在模块 `std.strings` 提供转换函数的名称。 **原理**   如有可能,被转换的值一般应避免被复制。在接口上要求转发右值避免不必要的复制。   标准库模块 `std.strings` 支持 [``](#求值得到的操作数) 的值的有关操作。因为相关转换的潜在的普遍性,在此进行特殊约定。 **注释**   一般地,转换操作是源类型的值作为单一实际参数的转换目标类型的[构造器](#创建和访问对象的函数)。 ## 不安全操作约定   除非另行指定,执行时蕴含以下操作的操作是[不安全操作](#不安全操作): * 以下不具有[内存安全保证](#对象语言内存安全保证)的操作: * 使用[函数名称约定](#函数名称约定)的[函数名带有后缀的操作](#可提供以引用标记字符结尾变体的操作)。 * 其它[保留间接值的操作](#保留间接值)。 * 以下可能引入循环引用的操作: * 引入引用现有[环境对象](#npla1-环境)的[环境强引用](#环境引用)的操作。 * 引入共享持有者的[值数据成员](#项的子对象)而可能通过循环引用等引起未定义行为操作。 * 和以上不安全操作等效的互操作。 * 其它另行指定的操作。   按不安全操作引起不同的未定义行为和不同的[间接值](#不安全间接值访问),以下小节对不安全操作进行分类。   分类之间的关系详见[保留间接值](#保留间接值)。   分类可能不完全的且可能相交的(不安全操作可能不属于任何一个分类或同时属于多个分类)。 ### 在函数值中保留引用值的操作   在函数值中保留引用值的操作包括按[函数名称约定](#函数名称约定)具有引用标记字符结尾的操作。   [直接保留引用值](#保留引用值)操作可配合带有[返回值转换](#返回值转换)的操作,指定个别函数参数不再保留引用值。   这些操作可引起之后的[不安全引用值访问](#不安全间接值访问)。   保留的引用值同时可能被构造[循环引用](#循环引用)。 **注释**   一些修改操作[无效化](#无效化)引用值。这些引用值若被保留且被访问,可引起未定义行为。   不引起被绑定对象无效的修改操作不被视为不安全操作,即便它们无效化[子对象](#npla1-子对象)的引用值。 ### 在函数值中保留环境引用的操作   [环境引用](#环境间接值)被返回时,总是被[保留](#保留环境引用)。   创建[环境强引用](#环境引用)的操作是在函数值中保留环境引用的操作。   这些对象可能因为没有及时保存环境引用使环境对象和其中的绑定一并被销毁,而使引用值访问其中的对象的程序具有未定义行为。   通过非引用的形式引入环境循环引用的操作同时可破坏环境的资源所有权。 **注释**   直接返回有效的[环境弱引用](#环境引用)的操作不引起环境失效,不在此列。 ### 在函数值中保留其它间接值的操作   特定的支持[强递归绑定](#强递归绑定)而在函数值中保留其它间接值,可能存在[其它无效的间接值](#其它无效的间接值)。   在函数值中保留其它间接值的操作的强递归绑定过程中引用共享对象的中间值。 ### 在环境中保留环境引用的操作   环境中的被绑定对象可具有环境引用[子对象](#npla1-子对象),间接地在环境中保留环境引用。   这些操作使[当前环境](#当前环境)或参数指定的环境(而不是[合并子调用](#合并子)时创建的[新环境](#npla1-环境))中的变量绑定包含间接值,后者可能依赖合并子调用时创建的新环境。   被绑定的对象中可能保留环境引用,而使用环境间接地保留对象中的引用。   使用这些操作时应总是注意被依赖的环境的可用性。   若环境对象销毁,所有直接和间接依赖环境对象的[间接值被无效化](#无效化)。这些间接值的[不安全间接值访问](#不安全间接值访问)引起未定义行为。 **注释**   绑定的对象中可能保留环境引用的典型的例子是合并子对象的静态环境。   创建合并子可在合并子中的环境中保留环境引用。 ### 无效化被绑定对象或环境引用的操作   特定的操作蕴含被绑定对象的存储期的结束而无效化它的引用值。   若[子对象](#npla1-子对象)的引用值已被绑定,这些引用值不需要通过其它不安全操作,而仅通过之后[访问标识符求值](#对象语言求值算法)的[结果](#npla1-规范求值算法)即可引起未定义行为。   因为[环境稳定性](#环境的稳定性)要求,NPLA1 实现环境不提供这类绑定,因此这些操作不是不安全操作。   但派生实现可能在语言实现中提供不满足环境稳定性的一等环境,其中对象的子对象的引用值被绑定为变量,且前者可能被修改。   此时,这些操作可能允许无效化引用后的[被引用对象](#一等引用)被访问,成为不安全操作。   类似地,无效化环境引用而无效化环境对象也可使其中包含的被绑定对象的引用无效化。   但[环境生存期](#环境生存期)要求,除非作为不满足环境稳定性的环境的被绑定对象,NPLA1 实现环境不提供唯一的[环境强引用](#环境引用)可被[用户程序](#程序实现)修改而使环境对象被销毁。   在这个前提下,要通过使环境引用作为子对象被修改而结束环境对象的生存期,首先要求通过[在函数值中保留环境引用的操作](#在函数值中保留环境引用的操作)取得环境引用,得到包含环境引用作为子对象的对象,且保证只有这个对象保存[环境强引用](#环境引用)。   因此,若不存在其它不安全操作,即蕴含不存在在对象语言操作中无效化环境引用的情形。   类似地,派生实现可提供不满足环境生存期中的销毁顺序的环境,而使用户无效化对应的环境对象。   此时,这些操作可能允许无效化环境引用后的环境对象被访问,成为不安全操作。 ### 副作用可能引入循环引用的操作   一些操作不依赖其它不安全操作(保留引用值或环境引用)即可引入循环引用: * [自赋值](#赋值)可能引入循环引用值。 **注释**   通过已有的不安全操作构造的引用值也可能引入循环引用(而引起未定义行为),但不是单一操作的副作用,不属于本节的实例。   例如[循环引用](#循环引用)中使用 `list%` 的例子,`$def!` 或 `list%` 不会因此被视为此处的不安全操作,因为单一操作的语义不引入循环引用值。 ### 可能破坏环境稳定性的操作   通过引用值进行的[修改操作](#对象的修改和改变)可因破坏[环境稳定性](#npla1-环境)而引起[扩展 NPLA 未定义行为](#npla1-未定义行为)(不一定违反[内存安全](#内存安全))。   这包括以下可无效化对象包含的引用值而使可通过环境访问的某个[子对象](#npla1-子对象)的同一性被改变,从而破坏[环境稳定性](#npla1-环境)的操作: * 对可能具有对象语言中可访问的子对象的对象的[赋值操作](#赋值)。 * 可修改[被绑定对象](#环境对象)的操作。 * 包括[重绑定](#重绑定) 。 ### 安全操作子集   作为[对象语言安全性保证](#对象语言接口的安全保证机制)的一部分,用户程序通过限制或避免依赖特定的不安全操作,在特定情形下可实现[对象语言内存安全保证](#对象语言内存安全保证),而不需要分析具体操作的语义: * 不依赖[操作命名](#函数名称约定)约定的带有后缀的操作。 * 若使用保留环境引用的操作,总是保存被依赖的环境以确保相关的环境对象及其中的[被绑定对象](#环境对象)在间接访问对象时不被销毁。 * **注释** 如[在函数值中保留环境引用的操作](#在函数值中保留环境引用的操作)和[在环境中保留环境引用的操作](#在环境中保留环境引用的操作)。 * 不使用引入最终不被保留的[子有序对引用](#子对象引用)以外其它间接值的操作。 ## 参照实现约定   本节约定对 NPLA1 参照实现内部有效,不作用在用户程序。   除非另行指定,NPLA1 参照实现环境作为公开接口提供的变量在[根环境](#npla1-初始求值环境)中绑定。   除非另行指定,以变量绑定的提供接口没有严格的跨版本兼容性保证。 ### 参照实现接口描述   除非显式指定,空环境没有[父环境](#npla1-环境)。   约定的接口通过绑定在根环境中的名称提供,参见以下各节。   描述操作的上下文中,结果指[操作的结果](#规范化中间表示)而非[求值结果](#基本语义概念)。   除非是语义蕴含的操作结果或另行指定,所有取得函数值的[操作](#库接口实体)满足: * [结果](#规范化中间表示)是[未指定值](#基本语义概念)。 * 未指定值可以是 [`#inert`](#npla1-扩展字面量)或其它值,但满足忽略值时不引起[可观察行为](#状态和行为)的改变。 * **注释** 这排除了引入 `volatile` 类型或非平凡析构的[宿主值](#类型映射)。 * 若这些操作在 \[RnRK] 或 klisp 中存在结果是 `#inert` 的对应的操作,且未指定作为函数值的结果,则结果是等于 `#inert` 的右值。 * 这些操作若存在对应的[函数名](#函数名称约定),满足[函数名称约定](#函数名称约定)。 * 这些操作中隐式分配的和按接口约定转移所有权给操作实现的资源不存在[资源泄漏](#资源泄漏)。 **原理**   [`#inert` 是单元类型的值](#npla1-扩展字面量)。但是,未指定值并不依赖单元类型的性质,而只是需要一种可在对象语言中判断是否为实现(如 \[RnRK] 的 `inert?` ),而避免使用特设的规则。   取而代之,一些语言或运行时支持[非一等](#一等类型)的特设返回类型:[`void`](#一等类型) ,在对象语言中无法构造其值。这种特性引起一些实用上的困难而被考虑改进,替换成一等类型,如: * [\[WG21 P0146R1\]](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html) * [\[Proposal\] `System.Void` as a first-class type](https://github.com/dotnet/csharplang/discussions/1603)   即便被改进,这仍只是[单元类型](#类型),而不够清晰地反映未指定的意图。   未指定值蕴含的不确定性在对象语言中难以建模,因此直接通过语言规则约定直至作为内建的支持特性,能使语言的设计更[简单](#简单性)。   未指定值的未指定性在接口意义上仍然不是一等实体,因为当前不提供如 `inert?` 这样的谓词。即便提供 `inert?` 也仅仅是判断值是否为 `#inert` ,而不是任意的未指定值。   相对 `inert?` ,这类谓词可能是有用的,例如元语言可能需要判断未指定性质以简化其派生实现。但这类需求和实现细节相关,且当前缺乏实例显示在对象语言中无条件提供的必要性。更重要的是,若要求提供这种谓词,限制派生实现在未指定值的类型上维持[开放类型](#类型映射)设计。   未指定值作为内建特性时,用户也无法直接提供这类谓词的派生(而不符合[统一性](#统一性))。在需要这类谓词时,派生语言设计可能视具体实现需要满足的条件补充。 ### 操作符合性   除非另行指定,以下关于操作实现的[未指定行为](../Terminology.zh-CN.md#程序设计语言): * 对[运行时错误条件](#运行时错误条件)的[检查](#错误检查)及[诊断](#诊断)的顺序。 * 满足运行时错误条件时,按要求诊断之后的操作内部分配的资源的状态。 * [变量绑定](#基本语义概念)或其它可选地由派生实现定义的可调试实体的引入是否由[本机实现](#npla-互操作支持)的机制提供。 * 是否存在不改变操作语义的附加的可被捕获的续延和这些续延的操作数。 * 操作的实现使用的续延或者其它[实体](../Terminology.zh-CN.md#程序设计语言)为[互操作](#npla-互操作支持)和调试目的保留的名称。 **原理**   以上未指定行为允许同一操作的接口约定和实现之间及不同实现之间允许存在引起调用操作时的可观察行为不同的次要差异。 ### 实体实现约定   在实现的意义上,[库特性](#程序实现)提供的实体的方式分为两类: * 调用本机 API ,提供[本机实现](#npla-互操作支持)。 * 直接调用本机 API 提供绑定和组合本机 API 实现功能的派生实现都是本机实现。 * 通过组合既有对象语言提供的接口实现,即*派生(derivation)* 。   因不预期通过派生实现,一些特性被设计为*基本(primitive)* 特性,总是通过直接调用本机 API 的本机实现提供。   其它特性都是派生特性,可通过*派生(derived)* 实现提供:通过组合基本特性及其它已提供实现的派生特性实现。   典型地,派生实现通常不依赖实现特定的[互操作](../Terminology.zh-CN.md#规范)接口的本机实现,这类派生实现是*非本机的(non-native)* ;其它的派生是本机的。   以下的[派生操作](#基础派生特性)应能以派生的方式提供。派生操作是否以派生的形式提供未指定。 **注释**   派生特性的实现方式可类似 \[RnRK] 。   非本机的派生实现通常以求值特定的对象语言源代码引入。   非本机的实现最终依赖本机实现。 #### 预定义对象   除操作外,实现可定义特定名称的对象以变量绑定的形式在库中初始化,直接提供具有预设目的的可编程特性。 # NPLA1 根环境特性   本章指定在[根环境](#npla1-初始求值环境)提供的 [NPLA1 标准库特性](#标准库),即[根环境特性](#库接口约定)。   [根环境基本特性](#根环境基本特性)是单独的模块。   [基础派生特性](#基础派生特性)起的各节提供其余要求 NPLA1 实现直接支持的各个模块的操作。 **原理**   一些特性参照和类推扩展 \[RnRK] 。   同 \[RnRK] 的设计,[`eval`](#基本派生特性)和 [`$vau`](#基本派生特性)是体现对象语言设计的[光滑性](#统一性)的主要原语。   因为[保留引用值的不安全操作](#在函数值中保留引用值的操作)的支持,类推 [`eval%`](#基本派生特性)和 [`$vau%`](#基本派生特性)。   在本设计中,后者在逻辑意义上更基本,即便不指定为[派生](#实体实现约定)。 **注释**   一些特性[可约定处理的值的宿主类型](#npla1-核心语言)。 ## 不安全函数索引   本节按[不安全操作约定](#不安全操作约定)的分类对提供根环境中的[不安全操作](#不安全操作)的函数进行归类。   [在函数值中保留引用值的不安全操作](#在函数值中保留引用值的操作)已被[命名](#函数名称约定)归纳和[函数分类](#函数分类)枚举,此处从略。   不安全操作中,在参数以外直接引入间接值的操作仅有以下的在函数值中保留引用值的不安全操作: * [`ref&`](#对象基本函数)   [附加调用安全](#安全性附加证明)包括在函数值中保留引用值的不安全操作的调用。   当前,这种操作包括 [`assign!`](#基本派生特性)。   隐藏环境排除可修改对象的引用,通过[冻结](#冻结)环境保证而提供静态的证明。 ### 在函数值中保留环境引用的函数   [在函数值中保留环境引用的操作](#在函数值中保留环境引用的操作)包括: * [基本操作](#环境基本函数): * `make-environment` * `copy-environment` * `lock-environment` * [派生操作](#基本派生特性): * `lock-current-environment` * `derive-current-environment` * `make-standard-environment` * `derive-environment` * `$provide/let!` * `$provide!` ### 在函数值中保留其它间接值的函数   [在函数值中保留其它间接值的操作](#在函数值中保留其它间接值的操作)包括: * [`$defrec!`](#环境基本函数) * [`$setrec!`](#基本派生特性) ### 在环境中保留环境引用的函数   [在环境中保留环境引用的操作](#在环境中保留环境引用的操作)包括[派生操作](#基本派生特性): * `$provide/let!` * `$provide!` ### 无效化被绑定对象的函数   当前不提供[无效化被绑定对象的操作](#无效化被绑定对象或环境引用的操作)。   这可包含直接移除变量绑定的操作。 ### 副作用可能引入循环引用的函数   [副作用可能引入循环引用的操作](#副作用可能引入循环引用的操作)包括可能[自赋值](#赋值)而引入循环引用值的操作: * `assign@!` * `assign%!` ### 可能破坏环境稳定性的函数   [可能破坏环境稳定性的操作](#可能破坏环境稳定性的操作)包括下列两类: * 对可能具有对象语言中可访问的[子对象](#npla1-子对象)的对象的[赋值操作](#赋值),包括: * [简单赋值](#可能使对象中包含引用值的修改操作)。 * **注释** 具体函数参见[可能使对象中包含引用值的修改函数](#可能使对象中包含引用值的修改函数)。 * 可直接修改[被绑定对象](#环境对象)的操作,包括: * [`move!`](#对象基本函数) ## 函数分类   本节对函数按名称和其它不同性质进行分类。   在 NPLA1 参照实现环境提供的函数具体详见根环境基本特性和基础派生特性。   除非另行指定,本节约定的函数属于 NPLA1 参照实现环境。   本节约定的函数提供的部分操作属于[转发](#函数参数和函数值传递约定)。 **注释**   [转发参数或返回值的实现](#转发参数或返回值的实现)中可使用 [`forward!`](#基本派生特性)。 ### 可提供以引用标记字符结尾变体的函数   本节中的[可提供以引用标记字符结尾变体的操作](#可提供以引用标记字符结尾变体的操作)的以下分类不相交,但部分分类中[函数名不带有引用标记字符结尾的操作](#引用标记字符函数名与内存安全的关系)可能和[不提供结尾引用标记字符对应变体的函数](#不提供结尾引用标记字符对应变体的函数)中的操作相交。   除非另行指定,符合以下分类的操作: * 不指定保留引用值时,不保留引用值。 * 指定保留引用值时,同指定[在结果中保留引用值](#被保留的引用值的目标)。 * 指定在结果中保留引用值时,根据结果是否为[未指定值](#基本语义概念),应总能直接区分[被保留的来源](#被保留的引用值的来源)和[目标](#被保留的引用值的目标)。 **注释**   [函数值约定](#函数值约定)已指定函数值默认不保留引用值;这没有涵盖 `` 的求值结果。 #### 可能使结果包含引用值的容器构造器函数   [可能使结果包含引用值的容器构造器](#可能使结果包含引用值的容器构造器)包括: * [`cons`](#有序对基本函数) * [`cons%`](#有序对基本函数) * [`wrap`](#合并子基本函数) * [`wrap%`](#合并子基本函数) * [`list`](#基本派生特性) * [`list%`](#基本派生特性) * [`list*`](#基本派生特性) * [`list*%`](#基本派生特性) * [`box`](#基本派生特性) * [`box%`](#基本派生特性) #### 可能使结果包含引用值的容器元素访问器函数   [可能使结果包含引用值的容器元素访问器](#可能使结果包含引用值的容器元素访问器)包括[基本派生特性](#基本派生特性)中的以下操作: * `first` * `first@` * `first%` * `first&` * `rest%` * `rest&` * `restv`   注意 `restv` 和 `rest%` 总是构造列表,并不直接返回[子对象](#npla1-子对象)的引用(另见[引用值构造函数](#引用值构造函数));其它访问器若带有引用标记字符,可直接返回引用值。   此外,标准库中的[函数值转发操作](#函数值转发函数)中部分函数也符合容器元素访问器的要求,但当前不提供带有后缀标记字符的变体。这些函数包括: * [`unwrap`](#合并子基本函数) * [`unbox`](#基本派生特性) #### 可能使对象中包含引用值的修改函数   [可能使对象中包含引用值的修改操作](#可能使对象中包含引用值的修改操作)包括: * 简单赋值: * [`assign@!`](#对象基本函数) * [`assign%!`](#基本派生特性) * [`assign!`](#基本派生特性) * 列表元素改变器: * [`set-rest!`](#有序对基本函数) * [`set-rest%!`](#有序对基本函数) * [`set-first!`](#基本派生特性) * [`set-first@!`](#基本派生特性) * [`set-first%!`](#基本派生特性) #### 可能间接保留引用值的函数   [可能间接保留引用值的操作](#可能间接保留引用值的操作)包括以求值 `` 作为尾上下文的操作: * 结果是合并子或用于在环境中绑定合并子的构造器操作。 * [核心库函数](#核心库)中的绑定操作。 * 在尾上下文中求值 `` 参数视为的 `` 的函数,包括 [`eval@`](#环境基本函数) 、[`eval`](#基本派生特性) 和 [`eval%`](#基本派生特性)。   参见[环境基本函数](#环境基本函数)和[核心库](#核心库)。 **注释**   以上操作中的求值符合[词法闭包](#函数和函数应用的求值环境)规则。 ### 不提供结尾引用标记字符对应变体的函数   本节列举[不提供结尾引用标记字符对应名称的操作](#不提供结尾引用标记字符对应名称的操作)。 **注释**   派生实现中通常使用 [`forward!`](#基本派生特性)进行[转发](#函数参数转发)实现上述保证。 #### 可直接以引用值作为结果的函数   [可直接以引用值作为结果的操作](#可直接以引用值作为结果的操作)包括: * [`$if`](#控制基本函数) * [`$sequence`](#基本派生特性) * [`$cond`](#基本派生特性) * [`$when`](#基本派生特性) * [`$unless`](#基本派生特性) * [`$and`](#基本派生特性) * [`$or`](#基本派生特性) #### 不以引用值作为结果的函数   [不以引用值作为结果的操作](#不以引用值作为结果的操作)的返回值总是按值传递的操作涉及的容器包括列表、[箱](#基本派生特性)或[其它的封装类型](#封装基本函数)对象,包括: * [`map1`](#基本派生特性) * [`list-concat`](#基本派生特性) * [`append`](#基本派生特性) #### 直接参数转发函数   [直接参数转发操作](#直接参数转发操作)包括: * [合并子基本函数](#合并子基本函数): * `unwrap` * [基本派生特性](#基本派生特性): * `accl` * `accr` * `foldr1` * `map1`   基本派生特性和[核心库函数](#核心库)中的绑定操作的非 `` 的形式参数支持转发。   参数形式上被转发但操作的语义并非总是转发到其它操作的操作不使用本节的名称约定,如以下仅有第二参数支持转发的操作是[提供结尾引用标记字符对应名称的函数](#可提供以引用标记字符结尾变体的函数),有[对象基本函数](#对象基本函数)中的: * `assign%!` * `assign@!` **注释**   `map1` 同时是[不以引用值作为结果的操作](#不以引用值作为结果的函数)。 #### 函数值转发函数   NPLA1 参照实现环境的[函数值转发操作](#函数值转发操作)包括以下访问对象或[被引用对象](#一等引用)自身或[子对象](#npla1-子对象)的函数: * [`unwrap`](#合并子基本函数) * [`id`](#基本派生特性) * [`forward`](#基本派生特性) * [`forward!`](#基本派生特性) * [`unbox`](#基本派生特性)   [基本操作](#根环境基本特性)的不具有名称的相关操作中参数和函数值原生支持的转发操作包括:   使用 [`make-encapsulation-type`](#封装基本函数)返回的访问器合并子。 #### 可能间接保留引用值的函数   [可能间接保留引用值的操作](#可能间接保留引用值的操作)包括: * [`apply`](#基本派生特性) * [`apply-list`](#基本派生特性) ### 引用折叠相关函数   本节列举[引用折叠](#引用折叠)相关操作。   只有[函数名以 `@` 结尾](#引用标记字符的函数名后缀)的函数可能引入[未折叠的引用值](#多重引用),包括: * [`assign@!`](#对象基本函数) * [`set-first@!`](#基本派生特性) * [`first@`](#基本派生特性)   此外,在参数保留引用值的修改操作可能使现有的引用值成为未折叠的引用值,包括: * [`assign@!`](#对象基本函数) * [`assign%!`](#基本派生特性) * [`set-first@!`](#基本派生特性) * [`set-first%!`](#基本派生特性)   函数 [`uncollapsed?`](#对象基本函数)区分未折叠的引用值。   [蕴含左值到右值转换](#间接值作为实际参数)的函数可以[消除引用值](#局部间接值安全保证),如: * [`idv`](#基本派生特性) * [`collapse`](#基本派生特性)   其中,`collapse` 依赖 `uncollapsed?` 而针对未折叠的引用值消除引用值,实现引用折叠。   其它一些函数可能在非[本机实现](#npla-互操作支持)中依赖未折叠引用但不在接口中体现,如 [`rulist`](#基本派生特性) 。 ### 引用值构造函数   当前构造[子对象引用](#子对象引用)的操作有: * [`unwrap`](#合并子基本函数)创建合并子的子对象引用。 * [`rest&`](#基本派生特性) 创建列表的子对象引用。 * 使用带有[省略](#绑定匹配)的[形式参数树](#绑定操作)(如 [``](#未求值的操作数))绑定操作数为[结尾列表](#绑定匹配),[创建列表的子对象引用](#非递归绑定)。 ### 非引用值构造器函数   一些函数构造非引用值,包括: * [基本派生特性](#基本派生特性)转换函数: * `$bindings/p->environment` * `$bindings->environment` * `symbols->imports` * [续延库](#续延库)转换函数: * `continuation->applicative`   其中,明确[转移而不是复制被转换的右值](#函数名称中缀)的函数有: * `continuation->applicative` ## 根环境基本特性   NPLA1 通过[预定义对象](#预定义对象)的形式提供[可选的模块](#npla1-参照实现扩展环境)。   [根环境](#模块)基本特性是除了这些模块的以变量绑定形式提供的不要求可派生实现的特性。   根环境基本特性的[被绑定对象](#环境对象)包括[基础环境](#npla1-初始求值环境)提供的预定义对象和在[基础上下文](#npla1-实现环境初始化)的根环境中初始化的*基础基本操作(grounded primitive operations)* 的实现。   派生实现可以通过提供不公开[不安全操作](#不安全操作)的根环境,但不符合此处的[规格要求](../Terminology.zh-CN.md#非自指)。   若派生实现不提供[在函数值中保留其它间接值的操作](#在函数值中保留其它间接值的函数),可以简化部分[对象基本函数](#对象基本函数)中与之关联的操作的实现。   当前在根环境中的直接提供绑定的特性不依赖 [``](#数值类型) 。 **原理**   和 \[RnRK] 不同,为简化设计,不提供可选(optional) 的合并子。   可选功能不应被必要功能依赖。   根环境的基本特性为组合其它实用特性提供,而数值在设计中不是必要的功能特性。   部分其它原理参见[根环境对象定义](#根环境对象定义)。关于引用值的处理另见[函数分类](#函数分类)。 **注释**   部分可选的 Kernel 合并子被直接提供。   和 \[RnRK] 不同,一些函数显式地操作[引用值](#引用值),包括[未折叠的引用值](#多重引用)。   和 \[RnRK] 不同,[求值算法](#对象语言求值算法)不直接处理对象的引用值。   为简化实现,部分提供 [`%` 等后缀的函数](#可提供以引用标记字符结尾变体的函数)不被派生。   因为设计原因,不提供以下 Kernel 合并子对应的操作: * `copy-es-immutable` * `inert?` * `ignore?`   考虑(可变对象的)一等引用和[绑定构造](#绑定构造)绑定引用的的平摊复杂度,不提供需要同时转换不同层次子项的 `copy-es-immutable` 操作。   其它没有包含在以下节中的 Kernel 合并子对应的操作可能会在之后的版本中被支持。 ### 根环境对象定义   除[为提供根环境特性的模块](#模块)以外,当前根环境不定义对象。   以下各节引入的变量都表示操作。 ### 等价谓词基本函数 **模块约定:**   本节的操作不修改参数对象。   本节的操作的结果是 `` 类型的纯右值。   用户定义的类型提供的等价谓词应满足和 NPLA1 提供的等价谓词的语义一致的等价关系,否则若谓词被求值,行为未指定。   一些具有[项节点](#项的子对象)作为表示的对象的[子对象](#npla1-子对象)具有递归相等性,仅当子对象符合以下递归相等关系: * 对不表示有序对的节点,同 `eqv?` 。 * 否则,同每个元素对应 `eqv?` 对应满足 `eqv?` 。   判断子对象递归相等性的对象相等时,其续延未指定。   若右值之间 `eqv?` 比较结果是 `#t` ,`eq?` 比较结果未指定。 **操作:** `eq? `   判断参数[同一](#实体的同一性)。   当且仅当两个参数是指定同一对象时,比较结果是 `#t` 。   `eq?` 的复杂度是 O(1) 。 `eql? `   判断表示参数的项的[值数据成员](#项的子对象)相等。   忽略表示参数的项的值数据成员以外的子对象:若参数是列表,则视为空列表;若参数是有序对,则视为仅具有最后一个元素。   若参数是引用值,则被比较的项是表示它的[被引用对象](#一等引用)的项。   当且仅当被比较的项的值数据成员相等时,比较结果是 `#t` 。   值数据成员相等蕴含参数的[动态类型](#类型)[相同](#类型等价性)。 `eqr? `   判断表示参数的项的数据成员同一。   当且仅当表示被比较的项的值数据成员指定宿主语言中的同一对象(即引用相等)时,比较结果是 `#t` 。 `eqv? `   判断非[枝节点](#项的子对象)表示的值相等。   若参数是引用值,则被比较的值是它的被引用对象。   根据项的内部表示: * 当表示值的项都是枝节点时,同 `eq?` 。 * 否则,若这两个参数的类型[不同](#类型等价性),则结果是 `#f` 。 * 否则,若这两个参数的 `eql?` 比较结果是 `#t` ,则结果是 `#t` 。   若两个参数的 `eqv?` 比较结果是 `#f` ,则这两个参数以 `eq?` 比较结果总是 `#f` 。   除非[互操作](#npla-互操作支持)(参见以下描述)或派生实现另行指定,不[等价](#合并子)的函数的 `eqv?` 比较结果是 `#f` 。   除以上规则确定的结果外,`eqv?` 对合并子或列表的比较结果未指定。   在互操作的意义上,当前 `eqv?` 定义的合并子的相等性由[宿主类型](#类型映射)的 `==` 或不影响可观察行为的其它宿主环境提供的 `==` 操作通过和 `eql?` 比较相同的方式确定。   除非另行指定,具有本文档引入的类型且不涉及互操作意义上用户自定义值的比较的操作数使用以上 `eq?` 以外的谓词比较的求值应保证能终止。 **原理**   除任何其它类型都可作为 [`` 的子类型](#求值得到的操作数),[开放类型映射](#类型映射)的[类型系统](#类型系统和类型机制)通常要求避免依赖 `` 上的其它的[良序和良基的理论](#实体的等价性),以避免对现有类型系统的扩展时需要修改已有的类型的相关操作。   不需要依赖序的等价谓词可为[名义类型](#类型等价性)提供直接的支持。   NPLA1 提供默认相等为[抽象相等](#实体的等价性),对任意的值适用。   NPLA1 还提供对一等对象保证结果有意义的[引用相等](#实体的等价性)操作。非一等实体的引用相等关系未指定。   当前 NPLA1 不支持 [\[EGAL\]](#实体的等价性) ,因为 \[EGAL] 要求存在分辨任意[对象的值](#基本语义概念)是否可被修改的元数据。   因为[对应等价的不变性](../Terminology.zh-CN.md#自指)关系不具有[唯一性](#实体的等价性),且可能允许不唯一的方式引起副作用(如缓存),和 \[RnRK] 不同,不以基本操作提供 `equal?` 对任意对象提供一般的相等操作。   未指定 `eq?` 的比较结果可允许实现复用存储右值的[驻留](#驻留)对象。   `eql?` 实际比较宿主值的相等。允许 `eqv?` 和 `eql?` 的不同可允许[一对多的类型映射](#类型映射)下比较对象语言的值的相等。(而多对一的类型映射 `eql?` 和 `eqv?` 可一致地比较。)   但是,当前实现中,大多数一对多映射的类型(如环境)都没有引起使 `eql?` 和 `eqv?` 不同的比较实现,因为不同宿主值类型的对象具有足够显著的差异,在大多数上下文不通过一些具有不可忽略开销的[转换机制](#环境基本函数)(如锁定[环境弱引用](#环境引用)转换为[环境强引用](#环境引用)),无法直接相互替换而保证行为差异可被忽略,因此逻辑上不适合定义为相等的。   而基于性能等理由,等其它一对多映射的类型(特别是可能基于宿主类型的值的子集的,如 [NPLA 数值类型](#数值类型)中的 `` )的值的比较也没有特别的处理,而引起 `eqv?` 和 `eql?` 的不同。   这些类型可能需要其它针对特定类型的等价谓词(如 [`=?`](#数学库))进行相等性的比较。   类似 \[RnRS] ,不同类型决定 `eqv?` 的结果是 `#f` ,但此处类型相同的含义不通过[类型分区](#类型分类)定义。   类似 \[RnRS] ,行为不等价的函数的 `eqv?` 结果原则上应为 `#f` ,但这种等价性一般不可证明而无法保证,特别在关于语言实现以外的调用上。   为支持互操作使用[本机实现](#npla-互操作支持)及避免限制合并子的子类型的[开放性](#开放性),允许这些实现另行指定规则,假定引起程序可观察行为差异的函数调用调用名义等价。 **注释**   通常,等价谓词比较的求值应保证能终止且对非列表项和 n 个子项的列表分别具有 O(1)O(n) 平摊复杂度。这是依赖数据结构实现的细节;语言不需要约束这个性质。 ### 控制基本函数 `$if `   条件分支,按条件成立与否返回 `` 或 `` 之一,可能是引用值。 `$if `   省略第三操作数的条件分支,条件成立时返回 `` 。   和 \[RnRK] 不同而和 \[RnRS] 类似,如 `` 的求值结果非 `#f` 即选择 `` ,且支持省略第三参数。   若省略 `` 且 `` 求值为 `#f` ,则[结果未指定](#参照实现接口描述) 。 **注释**   对 `` 的处理的主要原理和 Kernel 的 `$and?` 不要求尾上下文的表达式求值检查类型一致。   若需要检查类型避免误用,可以派生提供其它函数;相反的派生无法利用更[简单](#简单性)的实现。 **原理**   和 \[R7RS] 类似,但和 \[Racket] 及 \[RnRK] 不同,省略 `` 被支持。   和 \[R7RK] 不同,不使用 `#inert` ,参见关于[参照实现接口描述](#参照实现接口描述)的原理。   和 \[RnRK] 中的相关讨论结论不同,是否省略 `` 的形式应是一致的。这是因为: * NPLA1 不假设作为基本控制操作的 `$if` 的作用(仅要 `` 或 `` 求值的结果,或仅为了副作用)。 * `$if` 不假设用户对 `` 和 `` 顺序选择性偏好,以避免限制用户选择否定谓词简化 `` ,从而支持[变化的自由](#变化的自由)。   此外,NPLA1 使用显式的 ``(而不是 `` 和 `` )语法表示顺序求值,这不适合基本的控制操作子:   若分离二操作数和三操作数其它形式,则二操作数可以使用 [`` ,即 `$when`](#基本派生特性) 。   但依赖 `` 的 `$when` 不应是比具有 `` 的二操作数形式更基本的操作。   因此,仍然需要有 `$when` 以外的省略第三参数的基本控制操作子。基于[统一性](#统一性),对应函数名仍然为 `$if` 。(尽管使用了相同的原则,这和 Kernel 的结论恰好相反。)   与此类似,和 [\[Racket\] 的理由](https://stackoverflow.com/questions/10863192)不同,不因为 `$when` 提供只强调副作用的操作而取消 `$if` 的 `` 。   NPLA1 不会如 \[Racket] 一样在此避免遗漏 `` 导致的非预期结果。这并不违反[适用性](#适用性),因为不使用 `` 的结果非常显然,同时选择使用 `$if` 这样的基本控制操作而不是更特定派生控制操作或更高级的抽象已蕴含注意误用的必要性。   一般地,NPLA1 不提供强调只存在副作用的操作。返回未指定(而不要求被使用)的求值结果的情形[并不表示只有副作用](#参照实现接口描述),因为副作用是否存在原则上依赖具体操作。这和 Kernel 的 `#inert` 以及 \[Racket] 的 `#` 值即便在实现上都一致,但含义不同。   另见 `$when` 的说明。 ### 对象基本函数 **模块约定:**   因为[真列表](#npla1-广义列表)的限制,列表左值只能引用完整的列表的对象,而不支持部分列表。   这影响 `set-rest!` 和 `set-rest%!` 的第一个参数。 **操作:** `null? `   判断操作数是否为空列表。 `nullv? `   判断操作数是否为空列表纯右值。   同 `null?` ,但不支持引用值。 `branch? `   判断操作数是否具有[枝节点](#项的子对象)表示。 `branchv? `   判断操作数是否为具有枝节点表示的纯右值。   同 `branch?` ,但不支持引用值。 `pair? `   `` 的[类型谓词](#谓词名称后缀)。 `pairv? `   判断操作数是否为有序对纯右值。   同 `pair?` ,但不支持引用值。 `symbol? `   `` 的类型谓词。 `reference? `   判断操作数是否为引用值。 `unique? `   判断操作数是否为[唯一引用](#引用值的属性)。 `modifiable? `   判断操作数是否为可修改对象或可修改对象的引用值。 `temporary? `   判断操作数是否为[临时对象](#临时对象)或临时对象的引用值。 `bound-lvalue? `   判断操作数是否为被引用的[被绑定对象](#环境对象)左值。   绑定临时对象的引用类型的参数不被视为左值引用。   配合[[`$resolve-identifier`](#环境基本函数)和 `%` 引用标记[绑定](#非递归绑定)的变量,可确定实际参数是否为左值;参见 `$lvalue-identifier?`](#基本派生特性) 。   使用 `bound-lvalue?` 和 `&` 引用标记字符绑定的变量,可确定实际参数是否为引用。 `uncollapsed? `   判断操作数是否为未折叠的引用值。 `deshare `   取指定对象取消共享的值。   同 [`idv`](#基本派生特性) ,但显式转换操作数中具有共享持有者的[值数据成员](#项的子对象)为不共享的值,且不转移[宿主值](#类型映射)。 **原理**   因为提供[在函数值中保留其它间接值的操作](#在函数值中保留其它间接值的函数),这个区别是必要的。否则,使用 `idv` 替代应不影响[可观察行为](#状态和行为)。 `as-const `   取指定对象的[不可修改](#引用值的属性)的引用。   同 [`id`](#基本派生特性) ,但当参数是引用值时,结果是和参数引用相同对象的不可修改的引用值。 `expire `   取指定对象的[消亡值](#值类别)。   同 `id` ,但当参数是引用值时,结果是和参数引用相同对象的唯一引用。   可用于显式地指定之后被[转移](#对象的复制和转移)的对象,而不需要直接转移参数。   特别地,指定列表的引用值被转移时,不需要立即转移列表的每个元素,而允许之后通过[绑定构造](#绑定构造)等方式选择转移的[子对象](#npla1-子对象)。   可能包含立即转移的操作如 [`forward!`](#基本派生特性) 。 **原理**   这不直接转移对象,而不是[修改操作](#对象的修改和改变),[函数名不以 `!` 结尾](#修改函数名称后缀)。 **注释**   这个函数类似宿主语言标准库中作用在对象类型实际参数的 `std::move` ,可能减少没有经过[复制消除](#复制消除)的[复制或转移](#对象的复制和转移)而改变使用这个函数的[结果对象](#求值和对象所有权)的副作用。 `move! `   转移对象。   若参数是不可修改的左值,则以复制代替转移;否则,直接转移[表示参数对象的项](#项的转移)。   结果是不经返回值转换的项。 **注释** 另见[转移的注意事项](#转移导致的修改)。 `transfer! `   转移对象。   同 `move!` ,但使用[对象的转移](#对象的复制和转移),而不是[项的转移](#项的转移),避免宿主对象转移消除而[允许调用宿主对象的转移构造函数](#对象的复制和转移)。 **注释** 参数被转移后,和[返回值转换](#返回值转换)等[引入实质化临时对象](#值类别转换)时可能具有的转移的效果(仅在[互操作](#npla-互操作支持)时可见)可能相同。 `ref& `   取引用。   对引用值同 [`id`](#基本派生特性) ;对具有共享持有者的值数据成员的对象也视为左值。通过后者构造的引用值不被检查。   取得的引用值是[不安全引用值](#不安全引用值)。 **原理**   因为提供在函数值中保留其它间接值的操作,对共享持有者的值数据成员的对象使用不同的处理。否则,对引用值参数的情形,使用 `id` 替代应不影响可观察行为。 `assign@! `   [赋值](#赋值)`` 的[被引用对象](#一等引用)为指定对象的值,且 `` 不[蕴含左值到右值转换](#间接值作为实际参数)且[不被折叠](#引用折叠相关函数)。   赋值被引用对象前首先检查 `` 是可修改的左值。   赋值对象直接[修改](#对象的修改和改变)被引用的对象,但不[无效化](#无效化)参数指定的引用。   支持修改 `` 指定的子对象引用的被引用对象。 **注释** 被赋值替换的子对象的引用可被无效化。Scheme 的 `set!` 在 [\[SRFI-17\]](https://srfi.schemers.org/srfi-17/srfi-17.html) 提供具有类似作用的支持,但第一操作数限于 set! 且为特定的过程调用;Kernel 没有类似的操作。另见[赋值的注意事项](#赋值)。 ### 有序对基本函数 `cons `   构造参数指定的两个元素构成的有序对。   结果是 `` 类型的值。 **注释**   不保留 `` 的引用值,但这不涉及 `` 是有序对或有序对的引用值时其中可能具有的元素。   若 `` 中存在元素,直接被作为结果的元素,不经过返回值转换。 `cons% `   构造参数指定的两个元素构成的有序对,保留引用值。   同 `cons` ,但参数是引用值时,直接以其值作为元素的值,而不以其[被引用对象](#一等引用)的值创建有序对。 **注释** 这允许被构造的结果中存在和参数相等的引用值,而非其被引用对象的副本。 `set-rest! `   修改列表的第一个以外的元素。 **注释** 和 \[RnRK] 的 set-cdr! 类似,但检查列表是左值,且不保留被添加元素中的引用值。 `set-rest%! `   同 `set-rest!` ,但保留引用值。 **注释** 和 \[RnRK] 的 set-cdr! 类似,但检查列表是左值。 **注释**   和 \[RnRK] 不同,NPL 不支持列表中存在环。   不使用相同的对象左值的 `cons%` 调用或[修改操作](#对象的修改和改变)导致[循环引用](#循环引用),用户应自行避免[未定义行为](#npla-未定义行为)。   结果具有的属性不被影响。 ### 符号基本函数 `desigil `   移除符号中的引用标记字符 [`&` 或 `%`](#引用标记字符)。   判断符号非空且以 `&` 或 `%` 起始,结果是移除起始字符的参数。否则,结果是参数。   不处理引用标记字符 [`@`](#引用标记字符)。 ### 环境基本函数 **模块约定:**   为避免引入过于容易引入[循环引用](#循环引用),仅通过个别操作引入[环境强引用](#环境引用): * `make-environment` * `lock-environment` **操作:** `eval@ `   在参数指定的环境中求值,结果作为函数值。   `` 在求值前被视为 `` 。   `` 不[蕴含左值到右值转换](#间接值作为实际参数)。 `eval-string% `   在参数指定的环境中求值作为外部表示的字符串。 **注释**   类似 [klisp 的同名操作](http://klisp.org/docs/Environments.html#Environments),但保留引用值。   不提供类似 `eval@` 的 `eval-string@` ,因为不论参数的值类别,求值总是依赖的参数字符串的值。 `eval-unit `   规约字符串表示的翻译单元以求值。   直接使用当前环境,但其中求值不在[尾上下文](#上下文相关求值),也不改变当前续延。 **注释**   和 `eval-string%` 类似,但不支持指定环境,求值不在尾上下文,也不改变当前续延。   使用的实现环境可以是 [REPL](#诊断) 环境。 `bound? `   判断指定字符串对应的符号是否被绑定。 `$resolve-identifier `   解析[当前环境](#当前环境)中的标识符。   结果是解析结果中的项。 **注释** 参数不按成员访问规则确定[值类别](#npla1-引用值使用约定),也不按[解析名称表达式的规则确保结果总是左值](#npla1-规范求值算法),可[保留消亡值](#npla1-引用值使用约定)。 `$move-resolved! `   转移解析标识符的对象。   和 `$resolve-identifier` 类似,但直接取[被绑定对象](#环境对象)并尝试从环境中转移。   若环境被[冻结](#冻结),则复制被绑定对象;否则,直接[转移对象的项](#项的转移)。   一般应仅用于被绑定的对象不需要再被使用时。 `() copy-environment`   递归复制当前环境。当前忽略特定的[父环境](#npla1-环境)。   结果是新创建的环境的强引用。 **警告** 这个函数仅用于测试时示例构造环境,通常不应被用户程序使用,且可能在未来移除。未确定环境宿主值时可引起未定义行为。 `freeze-environment! `   冻结环境。   这个操作处理操作数指定的一等环境。 **注释** 对[隐藏环境](#隐藏环境)初始化时的相同操作参见[冻结操作](#冻结)。 `lock-environment `   锁定环境:使用[环境弱引用](#环境引用)创建[环境强引用](#环境引用)。   检查参数是环境弱引用,若失败则引起[类型错误](#npla1-类型检查)。结果是对应的环境强引用。   强引用可能引起环境之间的不被检查的[循环引用](#循环引用),用户应自行避免[未定义行为](#npla-未定义行为)。 `make-environment ...`   创建以参数为父环境的环境。   和 \[RnRK] 不同,除对象类型外,没有对列表和绑定的附加检查。   结果是新创建的环境,是环境强引用,具有宿主值类型 `shared_ptr` 。 `weaken-environment `   使用环境强引用创建环境弱引用。   检查参数是环境强引用,若失败则引起类型错误。结果是对应的环境弱引用。 **原理** 因为 NPLA1 需要精确控制所有权而[不依赖 GC](#资源回收策略),这可用于派生实现某些操作(如 [`$sequence`](#基本派生特性) 必要的)。 `$def! `   定义:修改当前环境中的绑定。满足[绑定构造](#绑定构造)的约定。   `$def!` 和 `$defrec!` 在求值 `` 后,进行[类型检查](#npla1-类型检查),确保环境没有被冻结后添加绑定。   对 `` 中已存在的标识符的绑定,保证[直接替换对象的值,对象的引用不失效](#重绑定)。 **注释**   类似 \[RnRK] ,对在 [``] 中某些未被直接求值的子表达式(如 [`$lambda`](#基本派生特性)的 ``),因为其中的求值依赖 `$def!` 表达式求值后的环境,在之后仍可以实现递归。   类似 \[RnRK] 的 `$define!` ,但绑定构造的约定存在不同规则。   求值 `` 后进行类型检查和 \[RnRK] 的 `$define!` 不同。   由于递归调用依赖环境中的绑定,修改以上定义引入的绑定后可影响被递归函数的调用。 `$defrec! `   递归定义:修改绑定,同 `$def!` ,但在绑定时针对 `` 指定的[操作数树](#绑定操作)中的绑定名称有附加的处理以支持直接递归。   除和 `$def!` 相同过程的常规绑定(求值 `` 和绑定符号)外,支持[强递归绑定](#强递归绑定),其操作数树的附加处理分为两阶段;每个阶段深度优先遍历 `` 指定的操作数树,对每个符号进行附加处理: * 在常规绑定前,每个遍历的待绑定符号在目标环境(被定义影响的环境)中预先进行绑定,保证指称一个对默认对象的弱引用,其中默认对象具有调用总是抛出异常的[非真合并子](#npla1-合并子)的值;和这个弱引用的共享的强引用被临时另行保存。 * 在常规绑定后,再次遍历操作数树,对每个实现支持的[合并子](#npla1-合并子)的的值,替换之前在环境中保存的共享定义为默认对象的共享强引用,最后释放先前临时保存的默认对象的强引用。 * **注释** 这里的合并子包括[非真合并子](#npla1-合并子)。   调用[默认对象](#强递归绑定)时: * 若默认对象的强引用存在,[引起错误](#强递归绑定)。 * 否则,违反生存期规则而具有未定义行为。 * **注释** 和 [vau 抽象对环境的检查](#环境生存期)类似,未定义行为不应被依赖。   常规绑定后转移未被 `` 求值影响的绑定中的默认对象的所有权到环境中,但不影响绑定目标在对象语言中指称的值。   在环境中未被 `` 求值替换的绑定,在 `$defrec!` 求值仍指称默认对象(而不会是持有[真合并子](#npla1-合并子)的值),若被作为合并子[调用](#合并子),则显示存在循环递归调用。   和 `$def!` 不同,求值 `$defrec!` 的 `` 前保证 `` 中的名称已存在默认定义,求值 `` 可访问对应的名称而不因[名称解析](#名称解析)失败而[引起错误](#npla1-错误)。 **注释**   和 `$def!` 不同,即使不计绑定修改环境的副作用,常规绑定后的操作使 `` 不在尾上下文求值。   这允许递归定义的名称在绑定完成前指称对象。例如,派生[标准环境](#基本派生特性),当前环境中未绑定变量 `a` 和 `b` 时: * 求值表达式 `$def! (a b) list b ($lambda () 1)` 因为被求值的 `b` 未被绑定而引起错误。 * 求值表达式 `$defrec! (a b) list b ($lambda () 1)` 不需要 `a` 或 `b` 已被绑定(即便 `b` 并不在 `$lambda` 的 `` 中),求值后 `a` 为默认对象。 * 求值表达式 `$defrec! (b &a) list ($lambda () 1) b` 绑定要求同上,但求值后 `a` 可能为默认对象(操作数树中的同级叶节点被未指定的绑定顺序影响)。   这也允许在 [`$vau/e`](#合并子基本函数)等表达式的 `` 指定的静态环境使 `` 不能访问目标环境时,直接定义递归函数。   递归定义的对象中的[值数据成员](#项的子对象)可能具有共享的持有者。若为合并子,直接调用会利用替换的值重新访问所在的环境。复制和转移这样的值不会改变被访问的环境。若访问的环境失效,则抛出异常,或无限递归调用自身。   特定情形使用 [`deshare`](#对象基本函数)可去除共享和避免以上可能非预期的行为。   另见]环境](#npla1-环境)。 ### 合并子基本函数   和 \[RnRS] 及 \[RnRK] 不同,`` 可以是多个项,而不再派生另外的变体支持顺序求值。   引入合并子的操作子不求值 `` ,后者在被调用时替换操作数以后被求值。这允许安全地使用 `$def!` 而不需要 `$defrec!` 进行[递归绑定](#环境基本函数)。   检查失败的[错误](#npla1-错误)是(可能依赖[类型错误](#npla1-类型检查)的)[语法错误](#npla1-错误)。 `$vau/e `   创建指定静态环境的 [vau 抽象](#vau-抽象)。   创建的对象是[操作子](#合并子)。 `$vau/e% `   同 `$vau/e` ,但保留引用值。 `wrap `   [包装](#合并子)合并子为[应用子](#合并子)。   包装应用子可能符合[包装数溢出的错误条件](#npla1-合并子) 。 `wrap% `   同 `wrap` ,但参数不蕴含左值到右值转换,[在结果中保留引用值](#被保留的引用值的目标)。 `unwrap `   [解包装](#合并子)应用子为[底层合并子](#合并子)。   左值参数解包装的[结果是合并子的子对象引用](#引用值构造函数)。 **原理**   指定 `` 作为静态环境可通过被绑定实体的所有权控制一等对象的生存期。同时,在没有 safe-for-space 保证时,仍可有效避免[资源泄漏](#ptr) 。 **注释**   和 \[RnRK] 不同,因为支持保存环境的所有权,提供 `$vau/e` 作为比 [`$vau`](#基本派生特性) 更基本的操作。   不考虑所有权时,[`eval`](#基本派生特性) 和 `$vau` 可派生 `$vau/e` 。   和 \[RnRK] 不同,参数是右值时解包装的[子对象](#npla1-子对象)被复制。由这些合并子创建的操作子当前仍不足以取代内置的一等操作子,因为不支持只能转移而不能复制的对象。传递这些对象作为操作数会引起构造失败的异常。 ### 错误处理和检查基本函数   以下函数提供[错误处理](#错误处理)的相关支持。 `raise-error `   [引发](#npla1-异常)表示错误的[异常](#npla1-异常)。 `raise-invalid-syntax-error `   引发包含参数指定的字符串内容的[语法错误](#npla1-错误)。 `raise-type-error `   引发包含参数指定的字符串内容的[类型错误](#npla1-类型检查)。 `check-list-reference `   检查对象是否是列表引用:若检查通过转发参数作为结果,否则引发错误对象。 `check-pair-reference `   检查对象是否是有序对引用:若检查通过转发参数作为结果,否则引发错误对象。 ### 封装基本函数 `() make-encapsulation-type`   创建封装类型。   和 \[RnRK] 类似,结果是三个合并子组成的列表,其元素分别表示用于构造封装类型对象的*封装(encapsulate)* 构造器、判断封装类型的谓词和用于*解封装(decapsulate)* 的访问器: * 构造器直接使用参数,在结果(构造的封装对象)中保留参数的引用值,类似[可能使结果包含引用值的容器构造器函数](#可能使结果包含引用值的容器构造器函数)中[带有 `%` 的容器构造器](#可能使结果包含引用值的容器构造器)。 * 访问器根据参数的值类别转发被封装的值。   创建的封装类型支持判断相等(参见 [`eqv?`](#等价谓词基本函数)),相等定义为被封装的对象的[子对象](#npla1-子对象)的[递归相等性](#等价谓词基本函数)。 **注释**   和 \[RnRK] 不同,使用构造器初始化封装的对象作为容器,具有作为其子对象的被封装的对象的所有权。   需要注意保存被构造的封装对象。   另见 [Unique Types](https://small.r7rs.org/wiki/UniqueTypesSnellPym/) 和 [\[SRFI-137\]](https://srfi.schemers.org/srfi-137/srfi-137.html) 。   和 \[RnRK] 及 \[RnRS] 的各种实现(如[这里](http://www.r6rs.org/r6rs-editors/2005-August/000831.html)提到的)不同,对相同类型的封装对象,`eqv?` 和 `equal?` 基于被封装对象的子对象(及子对象被引用的对象)递归比较,即使用封装的对象的 equal? 定义 eqv? 结果。   另见[等价谓词的设计用例](#实体的等价性)。 ## 基础派生特性   [根环境特性](#库接口约定)中,除[根环境基本特性](#根环境基本特性)的剩余接口是[派生特性](#实体实现约定)。其中在基础环境中提供的特性是*基础派生特性(grounded derived feature)* 。 ### 基本派生特性   基本派生特性可使用派生实现。这可能蕴含使用根环境基本特性或已在基本派生特性中提供的特性中的部分非派生实现。 **模块约定:**   引入合并子的操作子对 `` 的约定同[合并子基本函数](#合并子基本函数)。   因互相依赖,一些操作实现为派生操作时,不能用于直接派生特定一些其它操作。   和 [`$vau/e` 或 `$vau/e%`](#合并子基本函数)以及 [`$lambda/e` 或 `$lambda/e%`](#基本派生特性)不同,不指定静态环境的合并子构造器隐含总是使用[环境弱引用](#环境引用)形式的静态环境,以避免过于容易引入[循环引用](#循环引用)。   本节约定以下[求值得到的操作数](#求值得到的操作数): * `` :*箱(box)* :可包含[引用](#一等引用)的容器。 **注释**   注意 `$let` 等函数的 `` 形式和 \[RnRK] 不同。 **操作:** `eval `   同 [`eval@`](#环境基本函数) ,但 `` [蕴含左值到右值转换](#间接值作为实际参数)且不保留引用值。 **注释**   若 `` 为元素中有引用值的列表,元素不会被特殊处理,[不蕴含左值到右值转换](#间接值作为实际参数)。   \[RnRK] 中同名合并子的第一参数为 `` ,但这不是[已求值的操作数](#求值得到的操作数)的类型。 `eval% `   同 `eval@` ,但 `` 蕴含左值到右值转换。 **注释** 同 `eval` ,但保留引用值。 `eval-string `   同 [`eval-string`](#环境基本函数) ,但不保留引用值。 `() get-current-environment`   取[当前环境](#当前环境):取当前环境的[弱引用](#环境引用)。 **注释**   派生需要非派生实现的 `$vau/e` 。 `() lock-current-environment`   锁定当前环境:取当前环境的[强引用](#环境引用)。 `$vau `   创建 [vau 抽象](#vau-抽象)。   类似 [`$vau/e`](#合并子基本函数),但以当前环境代替额外的求值环境作为静态环境。 **注释**   和 \[RnRK] 不同,可通过 [`$vau/e`](#合并子基本函数)和(非派生的)`get-current-environment` 派生,不是[基本操作](#根环境基本特性)。 `$vau% `   同 `$vau`,但保留引用值。 `$quote `   求值引用操作。结果是[返回值转换](#返回值转换)后的未被求值的操作数。   考虑通常引用操作对符号类型未被求值的左值操作数使用,保留引用值没有意义,因此不提供对应保留引用值的操作。 `id `   结果是不蕴含左值到右值转换的参数,在结果中保留引用值。   其作用等价返回值转换,[可能引起对象转移](#返回值转换上下文)。 `idv `   同 `id` ,但结果是返回值转换后的值。 **注释** 使用 `idv` 可指定在函数值中[保留引用值的不安全操作](#在函数值中保留引用值的操作) 的个别操作数不再保留引用值。 `list ....`   创建列表(类型为 `` )对象。结果是操作数的元素经蕴含左值到右值转换的值。   `list` 的[底层合并子](#合并子)接受 ``(而不要求是列表)作为函数合并对象,结果的类型是 `` 而不一定是 `` 。 **注释** 除元素的转换,类似 \[RnRK] 的 `list` 。 `$lvalue-identifier? `   解析当前环境中的标识符(同 [`$resolve-identifier`](#环境基本函数))并判断是否为左值(同 [`bound-lvalue?`](#对象基本函数))。 `$expire-rvalue `   解析当前环境中的标识符,判断是否为左值(同 `$lvalue-identifier?` ),取对应的转换操作: * 若判断为左值,则转换操作同 `id` 。 * 否则,则转换操作同 [`expire`](#对象基本函数) 。   结果是在根环境求值转换操作对应的[函数名](#函数名称约定)得到的引用值。 **注释** 结果相同即以 [`eq?`](#等价谓词基本函数) 和对应的值比较结果为 `#t` 。 `forward! `   [转发](#函数值转发)[可能是引用的值](#函数值转发函数)。   转移可修改的右值操作数(包括消亡值和临时对象)。   其中,需转移时,使用使用[项的转移](#项的转移)。这和[对象的转移](#对象的复制和转移)不同,不保证调用宿主环境的转移构造函数。 **原理** 和宿主语言不同,直接转移允许区分消亡值和纯右值,同等地作为一等对象(如作为列表的元素)。 **注释**   被转发的值若是[形式参数树](#绑定操作)中的变量,一般应以[带有标记字符 `&` 的形式绑定](#非递归绑定);否则,转发的不是对应的实际参数,而可能是其按值绑定的副本。   这个函数类似宿主语言以对象类型参数和推断的函数参数类型作为模板参数调用 `std::forward` ,但若需转移,直接转移而非如 [`expire`](#对象基本函数)返回指定结果是[消亡值](#值类别)的[唯一引用](#引用值的属性)。 `list% ....`   同 `list` ,但每个参数都不蕴含左值到右值转换,[在结果中保留参数的引用值](#可能使结果包含引用值的容器构造器)。   `list%` 的底层合并子接受 `` 作为操作数(而不要求是列表),结果的类型是 `` 而不一定是 `` 。 **注释** 类似 \[RnRK] 的 `list` 。 `rlist `   转换参数为引用列表元素的列表。   若参数是左值,则结果是参数的元素的左值引用值构成的列表;否则,结果同 `idv` 。 `$remote-eval `   在动态环境求值第二参数得到的环境中求值第一参数,结果作为函数值。 `$remote-eval% `   同 `$remote-eval` ,但保留引用值。 `$deflazy! `   修改绑定。   同 `$def!` ,但不求值参数;在添加绑定前仍[对冻结环境进行检查](#环境基本函数)。 `$set! `   设置:修改指定环境的变量绑定。   在当前环境求值 `` 和 `` ,再以后者的求值结果修改前者的求值结果指定的环境中的绑定。绑定效果同使用 [`$def!](#环境基本函数)。   类似 \[RnRK] 的 `$set!` ,但明确使用 `` 而不是 `` 。注意 `` 的形式不同。允许的递归操作参见 `$def!` 。   和 \[RnRK] 不同而和 NPLA1 的 `$def!` 等类似,在修改绑定前对冻结环境进行检查。 `$setrec! `   递归设置:修改指定环境的绑定,绑定效果同使用 [`$defrec!`](#环境基本函数)。   同 `$set!` ,但允许不同的递归操作。 **注释** 参见 `$defrec!` 。 `$wvau `   创建包装的 [vau 抽象](#vau-抽象)。   同 `$vau` ,但创建的是调用时对操作数的元素求值一次的[应用子](#合并子)。   参数的作用同 `$vau` 的对应参数。 `$wvau% `   同 `$wvau` ,但允许函数体求值返回引用值。 `$wvau/e `   同 `$wvau` ,但支持显式指定求值环境参数作为静态环境。 `$wvau/e% `   同 `$wvau/e` ,但保留引用值。 `$lambda `   创建 [λ 抽象](#λ-抽象)。   同 `$vau` ,但创建的是调用时对操作数的元素求值一次的应用子,且忽略动态环境。 **注释** 可通过 vau 抽象或 `$lambda/e` 和(非派生的)`get-current-environment` 派生。   除未提供的 `` ,参数的作用同 `$vau` 的对应参数。 `$lambda% `   同 `$lambda` ,但允许函数体求值返回引用值。 `$lambda/e `   同 `$lambda` ,但支持显式指定求值环境参数作为静态环境。 `$lambda/e% `   同 `$lambda/e` ,但保留引用值。 `list? `   `` 的[类型谓词](#谓词名称后缀)。   若参数是列表或非真列表,时间复杂度不大于 O(n) ,其中 n 是其中的元素数。 **注释** 本机实现可实现 O(1) 时间复杂度。 `list* +`   在列表前附加元素创建对象或有序对。   类似 `cons` ,但支持一个和多个参数。   对一个参数的情形结果同参数,否则结果同右结合嵌套调用参数的数量减 1 次的 `cons` 。 **注释** 一个参数的情形结果经[返回值转换](#间接值作为函数值)。因为不需要如 Kernel 支持派生 `$vau` ,可直接使用 `apply` 派生;但因底层合并子还需检查列表,所以可使用 `apply-list` 。而和 `apply-list` 内部共用实现的派生可能更高效。 `list*% +`   同 `list*` ,但创建有序对类似 `cons%` ,且元素保留引用值。 **注释** 一个参数的情形结果不经返回值转换。 `apply ?`   转发第一参数指定的应用子和第二参数指定的参数构成[函数合并](#函数合并),在环境中[应用](#合并子)。其中,环境是: * [新环境](#npla1-环境),若第二参数不存在。 * 否则,第二参数指定的环境。 **注释** 检查 `` 和 \[RnRK] 的参考派生不同。   `apply` 的函数值保留引用值。 `apply-list ?`   转发第一参数指定的应用子和第二参数指定的[参数列表](#npla1-类型检查)构成函数合并,在环境中应用。其中,环境同 `apply` 中的方式指定。   同 `apply` ,但首先检查第二参数的类型,若失败则[引发错误](#npla1-错误)。 `$sequence `   顺序求值。   操作数非空时结果是最后的参数,可能是引用值。 **注释** 类似 \[RnRK] 的同名操作子。   求值每个 `` 的副作用包括其中[临时对象](#临时对象)的销毁都被顺序限制。 **注释** 这类似宿主语言的语句而不是保证子表达式中的临时对象的生存期延迟到完全表达式求值结束的逗号表达式。这也允许实现和 \[RnRK] 同名操作类似的 PTC 要求。 `collapse `   折叠可能是引用的值。 `forward `   转发可能是引用的非临时对象的值。   同 `forward!` ,但对可修改的临时对象操作数,使用复制代替转移。 **注释**   按在所在的环境中解析的操作数的类型[可选地进行返回值转换](#间接值作为函数值)作为结果,其作用 `id` 或 `idv` 之一。   转移(而不是复制)可修改的右值操作数。   若右值操作数不可修改,复制不可复制构造的宿主对象会失败。 `assign%! `   同 [`assign@!`](#对象基本函数),但 `` 是引用值时赋值的源操作数是 `` 折叠后的值。 `assign! `   同 `assign%!` ,但 `` 蕴含左值到右值转换。 **注释**   因为左值到右值转换,即便 `` 指定的值来自 `` ,也可赋值而不因此引起未定义行为。   另见[赋值的注意事项](#赋值)。 `$defv! `   绑定 vau 抽象,等价 `$def! $vau ` 。 `$defv%! `   绑定 vau 抽象,等价 `$def! $vau% ` 。 `$defv/e! `   绑定指定静态环境的 vau 抽象,等价 `$def! $vau/e ` 。 `$defv/e%! `   绑定指定静态环境的 vau 抽象,等价 `$def! $vau/e% ` 。 `$defw! `   绑定包装的 vau 抽象,等价 `$def! $wvau ` 。 `$defw%! `   绑定包装的 vau 抽象,等价 `$def! $wvau% ` 。 `$defw/e! `   绑定包装的指定静态环境的 vau 抽象,等价 `$def! $wvau/e ` 。 `$defw/e%! `   绑定包装的指定静态环境的 vau 抽象,等价 `$def! $wvau/e% ` 。 `$defl! `   绑定 λ 抽象,等价 `$def! $lambda ` 。 `$defl%! `   绑定 λ 抽象,等价 `$def! $lambda% ` 。 `$defl/e! `   绑定指定静态环境的 λ 抽象,等价 `$def! $lambda/e ` 。 `$defl/e%! `   绑定指定静态环境的 λ 抽象,等价 `$def! $lambda/e% ` 。 `forward-first% `   取列表的第一元素并转发给指定的应用子。   设参数列表 `(&appv (&x .))` ,作用同求值: ```npla1 (forward! appv) (list% ($move-resolved! x)) ```   其中,调用 `appv` 的[底层合并子](#合并子)的当前环境同调用 `forward-first%` 的动态环境。 `first `   取有序对的第一个元素的值。   当 `` 是左值时结果是折叠的引用值,否则结果是返回值转换后的值。 **注释** 类似传统 Lisp 及 \[RnRK] 的 `car` 。命名和 [\[SRFI-1\]](https://srfi.schemers.org/srfi-1/srfi-1.html) 及 Clojure 等现代变体一致。 `first@ `   同 `first` ,但结果[不被折叠](#引用折叠相关函数)而总是[未折叠的引用值](#多重引用)。   首先同调用 [`check-pair-reference`](#错误处理和检查基本函数)的方式检查参数是有序对引用,对右值引发错误。 `first% `   同 `first` ,但结果总是转发的值。   转发的值是经过折叠但没有返回值转换的列表元素的值,无论参数是否为引用值。 `first& `   同 `first@` ,但结果总是折叠的引用值。   若元素是引用值,在结果中保留元素中的唯一引用属性。 **原理** 详见 [NPLA1 引用值使用约定](#npla1-引用值使用约定)。 `firstv `   同 `first` ,但结果总是返回值转换后的值。 `rest% `   取有序对的第一个元素以外的元素值经过转发的值构成的有序对。   若结果构成[子有序对](#有序对的子对象和子对象引用),可能引入[子有序对引用](#有序对的子对象和子对象引用)。 `rest& `   取有序对的第一个元素以外的元素值的引用值构成的有序对的[子对象引用](#引用值构造函数)。   首先同调用 `check-pair-reference` 的方式检查参数是有序对引用,对右值引发错误。   若结果构成子有序对,引入子有序对引用。 `restv `   取有序对的第一个元素以外的元素值构成的有序对。   结果是有序对对象。 `set-first! `   修改有序对的第一个元素。   和 \[RnRK] 的 `set-car!` 类似,但可派生,检查列表是左值,且不保留引用值。 `set-first@! `   同 `set-first%!` ,但[保留未折叠的引用值](#引用折叠相关函数)。 `set-first%! `   同 `set-first!` ,但保留引用值。   不保证检查[修改操作](#对象的修改和改变)导致循环引用。 **注释** 用户应自行避免[未定义行为](#npla-未定义行为)。 `equal? `   判断一般相等。   类似 [`eqv?`](#等价谓词基本函数),但同时支持表示中具有子项作为子对象的对象。   判断的相等定义为子对象的递归[相等性](#等价谓词基本函数)。 **注释**   类似 \[RnRK] 和 \[RnRS] 的同名的二元谓词,但在此保证可通过 `eqv?` 直接构造。:   因为[列表的性质](#npla1-广义列表),不需要支持[循环引用](#循环引用),可以直接派生。后者被视为基本的抽象而非实现细节。   和 [`make-encapsulation-type`](#封装基本函数)创建的对象的相等比较不同,本机实现和派生实现都依赖当前上下文,允许捕获续延,尽管续延是未指定的。 `check-environment `   检查[环境](#npla1-环境)。   若参数是 `` 则检查通过,结果是转发的参数;否则,[引发错误对象](#npla1-异常)。 **注释** 当前实现中其它要求 `` 参数的操作中[类型检查](#npla1-类型检查)失败和 `check-environment` 失败的行为一致。 `check-parent `   检查作为环境的[父环境](#npla1-环境)的对象。   若参数是可以作为合并子环境的 `` 则检查通过,结果是转发的参数;否则,引发错误对象。   检查环境通过的条件同[创建合并子时的检查](#合并子基本函数)。   引发错误对象的作用同[创建合并子时环境检查失败引起错误或引发其依赖](#npla1-错误)的错误对象(后者保证不是[语法错误](#npla1-错误))。 `$cond `   条件选择。   类似 \[RnRK] 的同名操作子,但 `` 的判断条件和 `` 形式不同。 `$when `   条件成立时顺序求值。   类似 klisp 的同名操作子,但若 `` 被求值,结果是 `` 的最后一个 `` 的求值结果,而不是 `#inert` 。 **注释** 这类似 \[Racket] 的 `when` 而和 \[R7RS] 的 `when` 或 klisp 的同名操作不同,因为 `$when` 被作为和 `$sequence` 类似的操作处理(对应 Racket 中的 `when` 和 `begin` 并列)。 `$unless `   条件不成立时顺序求值。   类似 klisp 的同名操作子,但若 `` 被求值,结果和设计原理同上。 `not? `   逻辑非。   被求值的参数同 `` ,进行左值到右值转换。   若参数非 `#f` 时结果是 `#f` ,否则结果是 `#t` 。 **注释** 和 \[RnRK] 不同而和 Scheme 类似,视所有非 `#f` 的值为 `#t` 。 `$and ...`   逻辑与。   顺序短路求值。操作数为空时结果是 `#t` ;参数求值存在 `#f` 时结果是 `#f` ;否则结果是最后一个参数的值。   结果保留引用值。 **原理**   \[RnRK] 的 `$and?` 和 `$or?` 的实现使用 `apply` 和 `wrap` ,这没有必要: * 按 \[RnRK] 的 `apply` 的原理,这种对任意合并子适用的操作 `combine` 容易实现且干扰意图的理解。 * 对 NPLA1 的 `apply` ,还保证在第一参数是空列表时,为适应合[求值函数合并(前缀 `()` )](#函数合并求值),被继续求值的对象仍是有序对(即函数合并,而不是单独的函数),但这在 NPLA1 的 `$and` 和 `$or` 中不必要,因为对应的情形(即 `$and` 或 `$or` 没有参数时)应被单独处理。 * 对派生实现,`apply` 通常比 `eval%` 更低效(因为包含了无用的检查和更多的非本机实现)。 **注释**   和 \[RnRK] 的 `$and?` 不同,不检查类型,也不保证结果类型是 `` ,所以命名[不以 `?` 结尾](#谓词名称后缀)。   和 \[RnRK] 中的原理描述的不同,这同时允许直接的满足 [PTC](#尾调用和-ptc) 要求的派生实现。 `$or ...`   逻辑或。   顺序短路求值。操作数为空时结果是 `#f` ,参数求值存在不是 `#f` 的值时结果是第一个这样的值;否则结果是 `#t` 。   结果保留引用值。 **原理**   参见以上 `$and` 的原理。 **注释**   和 \[RnRK] 的 `$or?` 不同,具体差异参见以上 `$and` 的注释。 `accl `   在抽象列表的元素上应用左结合的二元操作。   对 `` 指定的抽象列表进行处理,取得部分和。   当谓词 `` 成立时结果是 `` ,否则继续处理抽象列表中余下的元素。   处理抽象的列表的操作通过余下的应用子分别定义:取列表头、取列表尾和部分和的二元合并操作。   参数 `` 和参数参数 `` 应接受两个参数,否则[引起错误](#npla1-错误)。   参数 `` 应接受两个参数,否则引起错误。   调用参数中的应用子的 `` 实际参数在不同的应用子调用中可能[同一](#npla1-对象同一性)。   调用参数中的应用子的底层合并子的当前环境同调用 `accl` 的动态环境。 `accr `   在抽象列表的元素上应用右结合的二元操作。   操作方式同 `accl` 。   和 `accl` 不同,可保证合并操作是尾调用;相应地,递归调用不是尾上下文而无法满足 [PTC](#尾调用和-ptc) 要求。 `foldr1 `   作用同符合以下要求的 `accr` 调用: * 指定 `accr` 的参数为 `` 、`null?` 、`(forward! )` 、`first%` 、`rest%` 和 `` 。 * 调用应用子 `rest%` 时不复制 `` 或其子对象。   参数指定的应用子的调用不添加或移除列表元素,否则[行为未定义](#npla1-未定义行为)。 **注释**   类似 [\[SRFI-1\]](https://srfi.schemers.org/srfi-1/srfi-1.html)) 的 `fold-right` ,但只接受一个[真列表](#npla1-广义列表)。   `foldr1` 名称中的 `1` 指 `` 参数的个数。(更一般的其它形式可接受多个 `` 。) `map1 `   单列表映射操作:使用指定应用子对列表中每个参数进行调用,结果是其[返回值](#函数调用)的列表。   参数 `` 应接受一个参数,否则引起错误。   操作中的应用子和列表构造的结果的确定满足过程调用的[因果性](#过程);其余任意 `` 调用的求值、列表构造操作和销毁列表中的元素的操作的相对顺序未指定。   `` 的调用不添加或移除列表元素,否则行为未指定。 **注释**   类似 \[RnRK] 的 `map` ,但只接受一个[真列表](#npla1-广义列表)。   `map1` 名称中的 `1` 的含义类似 `fold1` 。 `first-null? `   复合 `first` 和 `null?` 操作。 `rulist `   转换参数为可带有唯一引用的引用列表元素的列表。   同 `rlist` ,但在参数是左值时,参数中的非引用值元素在结果中对应转换为其唯一引用。 **注释** 消亡值处理和 `rlist` 不同。 `list-concat `   顺序连接列表和对象。 **注释** 当且仅当 `` 实际参数是 `` 值时,结果是 `` 值。 `append ...`   顺序连接零个或多个列表。 **注释** 若没有参数,结果是空列表。 `list-extract-first `   以 `first` 在参数指定的 `` 的列表中选取并合并内容为新的列表。   设参数列表 `(&l)` ,结果同在新环境中求值表达式 `map1 first l` ,其中 `map1` 和 `first` 是标准库函数。 `list-extract-rest%! `   以 `rest%` 在参数指定的 `` 的列表中选取并合并内容为新的列表。   设参数列表 `(&l)` ,结果同在新环境中求值表达式 `map1 rest% l` ,其中 `map1` 和 `rest%` 是标准库函数。 `list-push-front! `   在列表前插入元素。   要求 `` 可修改,否则[类型检查](#npla1-类型检查)失败。   参数 `` 被转发。 `$let `   局部绑定求值:创建以当前环境为父环境的空环境,在其中添加 `` 指定的变量绑定,再求值 `` 。   在添加绑定前,`` 中的[初值符](#初始化)被求值。   [返回非引用值](#间接值作为函数值)。 **注释** 类似 \[RnRK] 的同名操作子,但返回非引用值。 `$let% `   同 `$let` ,但保留引用值。 `$let/e `   指定静态环境并局部绑定求值。 **原理**   显式控制 `` 以允许传递引用值并在外部确保环境(可以是[环境强引用](#环境引用))被正确传递作为求值的父环境,而无需支持[扩展](#生存期扩展) `` 中的环境为右值时其中的环境临时对象的[生存期](#基本语义概念)。   注意此时 `` 中的环境中创建的环境对象在表达式求值后仍会因引入的合并子生存期结束而被销毁。 **注释**   类似 \[RnRK] 的 `$let-redirect` ,但使用 `$lambda/e` 而非 `$lambda` 作为抽象且支持 `` ,并返回非引用值。 `$let/e% `   同 `$let/e` ,但使用 `$lambda/e%` 而非 `$lambda/e` 创建抽象,保留引用值。 `$let* `   顺序局部绑定求值。   同 `$let` ,但 `` 中的被用于绑定的表达式从左到右顺序求值,被用于初始化变量的表达式在求值时可访问 `` 中之前绑定的符号。 **注释** 类似 \[RnRK] 的同名操作。 `$let*% `   同 `$let*` ,但保留引用值。 `$letrec `   允许递归引用绑定的顺序局部绑定求值。 **注释** 类似 \[RnRK] 的同名操作。   和 `$let` 及 `$let*` 不同,操作求值 `` 的初值符时保证使用和求值 `` 时的同一环境作为当前环境,因此可配合 `lock-current-environment` 传递具有所有权的环境。 `$letrec% `   同 `$letrec` ,但保留引用值。 `derive-current-environment ...`   创建当前环境的派生环境:以参数指定的环境和当前环境为[父环境](#npla1-环境)的[空环境](#求值环境)。   当前环境以外的父环境顺序同参数顺序。当前环境是最后一个父环境。 `() make-standard-environment`   创建[新](#新环境)*标准环境(standard environment)* :以[基础环境](#npla1-初始求值环境)为父环境的空环境。   类似 \[RnRK] 的 `make-standard-kernel-environment` ,但创建的环境基于 NPLA1 基础环境。 **注释** 标准环境同 \[RnRK] 约定的定义。 `derive-environment ...`   创建基础环境的派生环境:以参数指定的环境和基础环境为父环境的空环境。   当前环境以外的父环境顺序同参数顺序。基础环境是最后一个父环境。   创建的环境是标准环境,当且仅当没有实际参数。 **注释** 类似 `make-standard-environment` ,但具有参数指定的环境作为其它的父环境。 `$as-environment `   求值表达式以构造环境。   创建以当前环境为父环境的空环境,并在其中求值参数指定的表达式。   结果是创建的环境强引用。 `$bindings/p->environment (...) ...`   转换绑定列表为以指定的环境列表中的环境为父环境的具有这些绑定的环境。   类似 \[RnRK] 的 `$binding->environment` ,但指定父环境,且具有适当的所有权。   使用 [`make-environment`](#环境基本函数)而不是 `$let/e` 等绑定构造实现。 `$bindings->environment ...`   转换绑定列表为没有父环境的具有这些绑定的环境。 **注释** 类似 \[RnRK] 的同名操作子,但因为要求对内部父环境环境所有权,使用 `$bindings/p->environment` 而不是 `$let/e` 等绑定构造派生。 `symbols->imports ...`   转换符号列表为未求值的适合初始化符号[导入](#导入符号)列表的初值符列表。   结果是包含同 [`desigil`](#符号基本函数)的方式移除[标记字符](#文法元素补充约定)后的参数作为间接子项的列表。   求值这个列表,结果是同 `forward!` 的方式转发每个符号的列表,其元素顺序和 ``... 中的值的顺序对应。   结果的结构和使用满足以下约定: * 结果中可能存在合并子作为其子对象,其[包装数](#npla1-合并子)未指定。 * 取结果中的子对象进行求值的行为未定义。 * 若结果被修改(如被转移),再求值时行为未定义。 * 若结果中的合并子在求值整个结果外的上下文被调用,行为未定义。 **原理** 这些约定可允许更有效的本机实现。 **注释**   类似 \[RnRK] 的 `$provide!` 和 `$import!` 提供符号列表的方式,但有以下不同: * 支持移除引用标记字符。 * 支持转发参数。 * 不带有引用标记字符和符号指称的对象不是[临时对象](#临时对象)的默认情形复制值而不是初始化引用。 `$provide/let! `   指定局部绑定后在当前环境中提供绑定。   蕴含 `$let ` ,在求值 `` 后以结果作为操作数绑定到 `` 的符号。   `` 应能被作为 `` 使用。   结果是对这些绑定具有所有权的环境强引用。   需要[导入符号](#导入符号),即 `...` 具有至少一个实际参数时,以同 `symbols->imports` 的方式确定初值符。其中,等效的 `symbols->imports` 的调用次数未指定。 **注释** 绑定后的符号可通过作为 vau 抽象的[父环境](#环境生存期)等形式依赖这个环境,因此用户需适当保存[返回值](#函数调用)使[其生存期](#环境生存期)覆盖在被使用的绑定符号指称的对象生存期。 `$provide! `   在当前环境中提供绑定。   同 `$provide/let!` ,但不指定单独的 `` 。   作用同 `` 为空列表的 `$provide/let!` 。   结果是创建的环境的强引用。   需要导入符号时,以同 `symbols->imports` 的方式确定初值符。其中,等效的 `symbols->imports` 的调用次数未指定。 **注释** 类似 \[RnRK] 的同名操作子,但结果是创建的环境的强引用,且确定初值符的方式被显式要求。   仅当 `` 类型检查通过时求值 `` 。   检查当前[环境可修改](#冻结)失败时的副作用和以上任一等效求值 `symbols->imports` 应用子的结果可能具有的副作用[非决定性有序](#规约顺序)。 `$import! ...`   从指定的环境导入指定的符号。   对第一参数之后的其余参数指定的符号列表中的每个符号,修改第一参数指定的环境,创建和指定的符号具有相同的名称和值的变量绑定。   类似 \[RnRK] 的同名操作子,但需要导入符号时,以同求值 `symbols->imports` 应用子的结果的方式确定初值符。其中,等效的 `symbols->imports` 的调用次数未指定。   当指定的环境中的指定符号对应的绑定以临时对象创建时,导入符号可修改指定的源环境的被绑定对象。 **注释** 由于求值 `symbols->imports` 应用子的结果蕴含的转发语义,这和 \[RnRK] 不同。   检查 `` 可修改失败时的副作用和以上任一等效求值 `symbols->imports` 应用子的结果可能具有的副作用非决定性有序。 `$import&! ...`   从指定的环境以引用绑定导入指定的符号。   同 `$import!` ,但以 [`ensigil`](#标准派生特性)的方式指定绑定的符号。 `nonfoldable? `   判断参数是否不可被继续折叠映射:存在空列表。   参数是同 \[RnRK] 的 `map` 操作可接受的列表参数或空列表,但排除[非真列表](#npla1-广义列表)。   若参数是空列表,结果是 `#f` 。 `assq `   取关联列表中和参数的引用相同的元素。   第二参数指定的列表中的元素应为有序对。   以 `eq?` 依次判断第二参数指定的列表中的每个元素的第一个元素是否和第一参数指定的元素等价。   若不存在等价的元素,结果为空列表右值;否则是同 `first%` 访问得到的等价的列表的值。 **原理**   和 \[RnRK] 不同,NPLA1 只支持[真列表](#npla1-广义列表),因此可以要求顺序,提供关于等价的元素的更强的保证。   尽管和 \[RnRK] 相同而和 \[RnRS] 不同,[`` 支持非布尔值](#控制基本函数),不存在元素时的 `#f` 结果可以简化比较,但和 \[RnRK] 的原理类似,这不利于提供清晰的[类型错误](#类型检查),且没有如空列表值这样作为求值算法的特殊值的自然推论。使用空列表值和传统 Lisp 也一致。 **注释**   类似 \[RnRK] 的同名应用子,但保证顺序且转发参数。   类似 \[RnRS] 的同名过程,但失败的结果不是 `#f` 。 `assv `   取关联列表中和参数的值相等的元素。   第二参数指定的列表中的元素应为有序对。   以 `eqv?` 依次判断第二参数指定的列表中的第一个元素是否和第一参数指定的元素等价。   若不存在等价的元素,结果为空列表右值;否则是同 `first%` 访问得到的等价的列表的值。 **原理** 参见 `assq` 。 **注释**   类似 \[RnRK] 的 `assoc` ,但使用 `eqv?` 而不是 `equal?` ,保证顺序且转发参数。   类似 \[RnRS] 的 `assv` ,但失败的结果不是 `#f` 。 `box `   装箱:构造参数对象经左值到右值转换的箱(类型为 `` 的对象)。 `box% `   同 `box` ,但参数不蕴含左值到右值转换,[在结果中保留参数的引用值](#可能使结果包含引用值的容器构造器)。 `box? `   `` 的[类型谓词](#谓词名称后缀)。 `unbox `   拆箱:从箱中还原对象。   作为[函数值转发操作](#函数值转发函数),保留引用值。 **注释**   以上 4 个函数除[引用标记字符](#函数名称约定)对应处理引用值的差异外,功能和使用方式对应类似 [\[SRFI-111\]](https://srfi.schemers.org/srfi-111/srfi-111.html) 的 3 个过程 `box` 、`box?` 和 `unbox` 。   [类型分区](#类型分类)使 `box?` 对 `` 类型的参数的结果总是 `#f` 。若没有这个限制,不考虑[封装性](#封装)时,用 `` 的相关操作可整体替换进行功能等价的代替:`list` 、`list%` 和 `first` 可代替 `box` 、`box%` 和 `unbox` 。   和 [关于 Scheme 的装箱的描述](http://community.schemewiki.org/?scheme-faq-language)不同,这样的代替不一定保证有更好的性能。   以上这些函数可使用 [`make-encapsulation-type`](#封装基本函数)实现。   和 Scheme 等不同,箱具有被装箱对象的所有权,因此使用 `box%` 和 `unbox` 时,需注意保存被构造的箱或被箱中引用值引用的对象。 ### 标准派生特性   *标准派生特性(standard derived feature)* 同[基本派生特性](#基本派生特性),但其派生依赖[标准库其它模块](#npla1-根环境特性)。 `ensigil `   修饰引用字符。   若参数非空且没有 `&` 前缀,则结果是添加 `&` 引用标记字符作为前缀的符号;否则是参数值。 `$binds1? `   判断指定符号是否在指定表达式求值后指称的环境中绑定。 **注释** 类似 \[RnRK] 的 `$binds?` ,但只支持判断一个 `` 操作数。 ### 核心库   [核心库](#标准库)提供以下操作,即*核心库函数(core library function)* : `map-reverse ...`   映射并反转结果。   参数 `` 应满足以下要求,否则[引起错误](#npla1-错误): * `...` 中的参数的元素数都相等。 * `...` 中的参数的元素数量等于 `` 接受的形式参数的元数。 **注释** 类似 \[RnRK] 的 `map` ,但支持空的 `...` 且保证顺序。 `for-each-ltr ...`   从左到右映射取副作用。 **注释** 类似 \[RnRK] 的 `for-each` ,但支持空的 `` 且保证顺序。 # NPLA1 参照实现扩展环境   类似 [NPLA1 根环境特性](#npla1-根环境特性),NPLA1 以[根环境](#npla1-初始求值环境)的形式提供其它一些[模块](#模块)的操作,但默认不以根环境中的绑定而是以其中的[环境子对象](#模块)中的绑定提供。   除非另行指定,这些环境子对象作为[库特性](#程序实现)的一部分时,是以 `std.` 为前缀的名称命名的[库环境](#npla1-初始求值环境)。   除非派生实现另行指定,这些模块都应被提供。   这些模块和 [NPLA1 参照实现环境](#npla1-参照实现环境)提供的特性一同构成[标准库](#标准库)。   除非派生实现定义,每个标准库模块都不是可选的。否则,作为被加载的模块的环境的名称由派生实现定义。   [修改](#对象的修改和改变)这些环境的程序[行为未定义](#npla1-未定义行为)。   默认加载使用 `.` 分隔标识符得到的符号作为名称。   加载的模块依赖[初始化的根环境](#npla1-初始求值环境)。   当前实现中部分加载的环境依赖之前加载的环境,这些环境的名称是固定的。用户程序需要保证这些环境在加载时的静态环境中可用。   在调用其中的合并子时,可能求值符号引用依赖的环境。其中的环境可能在求值定义时不依赖而不作为对应的本机 API 的前置条件。   环境是否具有依赖的环境的绑定绑定是未指定的。   用户程序需保持加载为环境的模块具有适当的[生存期](#环境生存期),以避免其中的[合并子调用](#合并子)引起未定义行为。   本章的特性可使用[本机实现](#npla-互操作支持)或[非本机的派生实现](#实体实现约定),分别符合[根环境基本特性](#根环境基本特性)和[基础派生特性](#基础派生特性)的规则。具体使用何种实现是未指定的。   派生实现可定义更具体的实现要求,以便[互操作](#npla-互操作支持)的兼容性。 **原理**   这些绑定不需要被直接引入[基础上下文](#npla1-实现环境初始化)的根环境中,因为: * 同时满足以下关于接口依赖的约束,而不必要以[基础环境](#npla1-初始求值环境)可访问的名称提供: * 它们不是使用[作为环境的模块](#模块)时被依赖的主要操作。 * 为了使用非根环境的模块,需要绑定在根环境的函数引入其中的绑定。这样的接口应在根环境中提供而保证默认可访问,避免引入绑定这样的功能的在逻辑上的循环依赖。 * **注释** 这样的操作如 [`$import!`](#基本派生特性)。 * 它们不被[根环境特性](#npla1-根环境特性)的在接口意义上依赖。 * **注释** 在实现上仍可选依赖,参见[标准派生特性](#标准派生特性)。 * 接口的功能不对一般的实体具有足够普遍性,而不需要以基础环境默认可访问的名称提供。   判定上述的足够普遍性的具体规则包括: * 普遍性以实体类型体现为接口的功能对非特定类型的对象适用,最终不依赖具有更特定的类型特有的性质。 * **注释** 一般的实体作为一等对象,即具有 [``](#求值得到的操作数)的值。`` 是足够普遍的类型。 * **注释** [``](#求值得到的操作数)和 [``](#基本派生特性)等由 `` 构造的值最终依赖 `` 的值,而非其它更特定类型特有的性质。 * 在[求值算法](#对象语言求值算法)中出现决定具体步骤的具体实体类型,被认为是足够普遍的。 * **注释** 这包括 [``](#未求值的操作数)、[``](#求值得到的操作数)和 [``](#未求值的操作数)及其[子类型](#未求值的操作数)。 * **注释** [``](#数值类型)或 [``](#求值得到的操作数)等其它比 `` 更具体类型的值不在此列。 * 若接口的功能仅依赖比一般的实体更具体的特定类型的值,则不以基础环境默认可访问的名称提供。 * **注释** 功能上的依赖包含区分这些特定类型的值,如[类型谓词](#谓词名称后缀)。 * 接口的功能描述涉及的类型的足够普遍性对以基础环境默认可访问的名称提供是必要非充分条件。 * 这些类型仅决定接口功能的一部分。 * **注释** 若接口的功能仅依赖足够普遍的类型,但功能不足以涵盖它的任何的[子类型](#类型序)或者值,也可在参照实现扩展环境中提供。 * **注释** 一个主要特例:足够普遍的具体类型的类型谓词涵盖所有值,因此类型的足够普遍性可直接作为对应的类型谓词的足够普遍性的充分必要条件。   具有足够普遍性而应在根环境而非参照实现扩展环境提供的操作具体包括以下几类: * 创建非特定的不同[名义类型](#类型等价性)的对象使用的普遍机制的主要操作。 * **注释** 使用 [`make-encapsulation-type`](#封装基本函数)可创建不同名义类型。 * 不改变一般的实体可能蕴含的[对象同一性](#一等对象的同一性)而同时附加非特定[种类](#作用使用原则)的[副作用](#基本语义概念)的操作。 * 同一性是所有对象的属性。显示同一性不依赖具体副作用的种类,因此要求特定种类的接口削弱普遍性。另见[可变状态和普遍性](#可变状态和普遍性)。 * **注释** 根环境中这样的操作如 [`box%` 和 `unbox`](#基本派生特性)。 * 不依赖特定对象类型,直接引入副作用的操作。 * 因为引入副作用可能是接口的关键功能及主要目的,此处的普遍性不限制非特定种类。 * **注释** 仅具有控制作用为副作用的操作仍被视为是普遍的。因此,可具有[控制作用](#基本语义概念)的 [`$if`](#控制基本函数)等函数仍在根环境中提供。 * **注释** 依赖[一等续延](#续延的捕获和调用)的[控制作用](#基本语义概念)因续延类型而不视为足够普遍,因此根环境不直接提供一等续延关联的操作。   在此基础上,这些绑定被设计为[环境子对象](#npla1-环境)提供的[子模块](#模块),因为以下的一个或多个原因: * 它们可能具有非全局含义的名称而更适合隔离在不同[命名空间](#命名空间)中以避免使用时的歧义。 * 它们可能仅关注(如作为操作的参数或返回类型)特定的[求值得到的操作数](#求值得到的操作数)类型。 * 它们中的每一个模块具有足够或内聚性而不和其它子模块耦合,且绑定提供的实体关注相同的功能集合,适合被派生实现直接配置为可选的(#根环境基本特性)特性分组。 * 允许实现使用特殊的环境子类型[延迟加载](#模块)。 * **注释** 当前 NPLA1 没有提供支持。 **注释**   类似[根环境特性](#npla1-根环境特性),一些特性可[约定处理的值的宿主类型](#npla1-核心语言)。   使用 `.` 分隔标识符得到的符号类似 [CHICKEN Scheme 的转换 R7RS 的标准模块名](http://wiki.call-cc.org/eggref/4/r7rs#import)。 ## 续延库   提供[续延](#续延)支持。   默认加载为根环境下的 `std.continuations` 环境。 **模块约定:**   本节约定以下求值得到的操作数: * `` :[一等续延](#续延的捕获和调用)。 **原理**   类似 Kernel 语言,NPLA1 续延不是合并子。这一设计的基本原理参见 \[RnRK] §7(以及[续延的捕获和调用](#续延的捕获和调用)),但理由不充分。更完整的分析如下: * 使用*守卫选择子(guard selector)* 是 Kernel 的具体特性的设计,不是一般求值算法中蕴含需要实现的选项,因此仅在 \[RnRK] 内部生效。 * 这和类似的其它机制(如 \[RnRS] 的 `dynamic-wind` thunk )事实上都同以下关于续延组合性的理由类似,不需要单独列出。 * 如 \[RnRK] ,一般的合并子确实无法保证作为续延的父续延(parent continuation) 。 * \[RnRK] 在此没有进一步明确: * 一般的合并子无法作为父续延的原因是因为(作为函数)不保证能接受操作数。 * 父续延对续延组合(接受两个续延结果是和两者连续调用等效的续延)操作是必要的输入。 * 更一般地,即便不支持续延组合操作,在抽象机语义描述续延的捕获得到一等续延依赖这种语义(即便父续延不是一等对象)。 * 只要描述依赖可组合的求值算法,除了最后一个[无界续延](#续延),其它续延都对应可组合的求值步骤,因此这隐含续延的可组合性。 * 其它在求值算法以外的机制的描述也可能依赖这种组合性,如:https://docs.racket-lang.org/reference/eval-model.html#(part._mark-model) 。 * 最后一个无界续延可不考虑可组合性。但只要它不是唯一的,为作为一等续延被直接捕获和调用,它仍应当接受程序指定的操作数。 * 仅当这个续延唯一时可不提供单独的续延类型。 * 此时,这个续延被隐含而不当作一等续延,其中的操作数直接通过非续延的函数直接表示,如 \[ISO C] 标准库的 `exit` 函数。 * 然而这样的设计要求其它续延是可组合的,否则根本不支持一等续延。 * 可组合续延时,这个唯一的续延会被这作为被组合的其它续延的公共后缀。 * 此时仍然需要支持指定操作数,以便最后一个续延能表现程序指定的不同行为;否则,显式提供这样的续延缺少实际意义。 * \[RnRK] 要求 `root-continuation` 和 `error-continuation` 这两个不同的一等无界续延能作为后缀,因此不是唯一的。 * 所以,为符合[正确性](#正确性),一般的合并子不是续延的[子类型](#类型序)。 * 因为[包装](#合并子)是独立在具体合并子类型外的操作,\[RnRK] 说明了一般续延不应作为应用子的子类型,而这并没有有效说明续延无法作为[操作子](#合并子)的子类型。 * 一些观点认为无界续延不能作为函数。但这实际依赖具体对象语言的规则,同样无法说明一般的续延无法作为操作子的子类型。 * 典型的分析如[参见这里](https://okmij.org/ftp/continuations/undelimited.html#introduction)。 * 这仅论述了[续延界限](#续延)和续延(调用)的某种可组合性(composablility) 的要求之间的关系。 * 特别地,此处的可组合性局限于取得函数值。 * 因为这种可组合性的限制,这隐含一个前提:函数不能不返回。这对一般的[函数](#函数)并不成立,因为一般的函数允许存在副作用,这种副作用不一定局限于蕴含此处可组合的[控制作用](#基本语义概念)。 * 在类型系统中,不返回的函数仍可能是[良型的](#类型系统和类型机制),因为[函数类型的构造器](#类型种类)是否接受[空类型](#类型边界元素)和具体类型系统的设计相关。 * 类型系统规则能保证编码可组合的函数的机制是确保语法上可构造可组合的嵌套函数调用(函数值可作为另一个函数的实际参数),以此构成传统数学函数复合的自然扩展,并保证作用可复合,但这不保证函数值可复合。 * 对总是不返回的函数,非终止(non-terminization) 是其作为后缀的一种单一副作用,吸收(absorb) 任何返回函数值的这一计算作用。这破坏函数调用具有返回值的预期,但并非在作用上不可组合。 * 事实上,不提供一等控制作用机制的语言也可能允许这种类型规则。 * 如 \[ISO C] 的 `_Noreturn` 函数实质上返回类型就是空类型(因为没有任何值可作为[返回值](#函数调用)的[居留](#类型系统和类型机制)),尽管 C 的类型系统编码中 `_Noreturn` 函数返回类型并不唯一(返回类型这在转换等其它类型检查中仍然有效)且空类型不被检查(违反 _Noreturn 约束是未定义行为)而可能显得不典型。 * **注释** \[ISO C] 仅提供 `setjmp`/`longjmp` 改变通常的控制状态。此外,\[ISO C++] 的类似的 `[[noreturn]]` 用于标注总是抛出异常的函数,而提供异常的(替代)实现是一等控制操作符的一个典型使用场景。 * 根本地,使用这种值而非一般计算作用的可组合性定义的理由,仅来自某种描述语言规则的元语言上推理的要求或约定。 * **注释** 例如,为了便于使用等式理论(equational theory) 进行推理,证明被描述的对象语言总是符合某种静态的性质。 * 一般地,基于目的的不确定性,元语言不总是遵循这种约定。 * 对象语言更没有必要遵循这种约定,因为这蕴含对对象语言功能完整性的任意地、不必要的限制。 * 避免非预期的终止性可很容易通过对维护外部语言实现环境的运行时的互操作实现:添加一个破坏维护非终止的运行时条件的操作(例如,撤除硬件电源),即便需要按某种方式保留运行时状态,通常仍远远比提供静态的证明更容易。 * 此外,不论对象语言是否有必要表达,普遍的组合性不总是有益的,自身可能不被预期。 * 易于排除非终止这种计算作用的实现方法,正好是得益于语言实现和外部系统之间的计算作用不能自发维持组合性的直接应用。 * 实际决定对象语言中区分续延和合并子(且续延明确不使用合并子表示)的设计有不同的其它理由: * 求值算法对合并子的处理决定合并子对[非正常控制](#程序的控制执行条件)透明。作为引入非正常控制的机制,续延调用和遵循 [β-规约](#函数合并)的合并子调用具有不同来源的语义规则,即便后者在元语言中可能实现前者。 * 因此,是否把续延表示为传统的过程或合并子等其它蕴含 β-规约的实体是实现细节。抽象上,这支持保持续延的表示不透明而和合并子相互独立。 * 对一等续延相关的控制操作的一种一般的语义描述参见[这里](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.8645&rep=rep1&type=pdf)。 * 避免把续延作为合并子还有语用因素。具体地,续延调用通常使用[续延应用](#续延的捕获和调用)的形式,而非操作子调用。续延应用不直接复用[函数合并](#函数合并)的语法。 * 以操作子的方式类似进行续延调用可能依赖对象的内部表示而暴露不必要公开的抽象,并不常用,也并不被当前语言普遍支持。 * **注释** 大多数语言根本不支持操作子。 * 但禁用这种调用,在一等续延的调用规则中添加偶然耦合,阻碍功能[完整性](#完整性)和设计的[简单性](#简单性) 。 * 考虑到捕获的一等续延通过变量绑定提供,因为合并子不是应用子的子类型,若一等续延是合并子,只能是指称操作子的变量。 * 于是,程序中的续延应用总是要求对其包装后使用。 * 这种设计使程序的实现和简单性冲突,并违反多个[结构设计规则](#结构和依赖原则),而削弱提供这些绑定的续延捕获特性的[可用性](#可用性)。 * 因此,一般的续延不是应用子的子类型,程序中一等续延的应用需转换续延为应用子。 * 推论:一般的续延不是合并子的子类型。 * 综上,结合以上合并子不是续延的子类型以及续延不是合并子的子类型,一般的续延和合并子类型是正交的。 * 但[和 \[RnRK\] 要求的类型分区不同](#类型分类),派生实现仍可能提供同时是某个合并子的[严格子类型](#类型序)的续延。   NPLA1 首先提供关于一等续延而不是[一等协程](#过程调用的计算顺序)的控制操作,因为: * 协程要求支持具有更多的原语,包括基于(而非并行于)一般的例程的创建操作([构造器](#创建和访问对象的函数))。 * 具有相等的一等协程的内部表示逻辑上更加复杂(总是涉及可变状态),即便具体实现并不一定更复杂(和内部表示相关)。 * 使用非对称协程派生对称协程比使用[有界续延](#续延)派生无界续延在逻辑上依赖暴露更多的细节,如包含分支的跳板循环。   尽管逻辑上有界续延能不依赖其它[副作用](#基本语义概念)而表达状态,提供有界续延或无界续延作为原语的差异相对次要,因为: * NPLA1 不是[纯函数式语言](#实体的不可变性)而支持[一等状态](#一等状态)在内的基本设计,在实现中不需要排除可变状态。 * NPLA1 不提供特设的和续延并行的[异常](#异常)或者其它替代的[非正常控制](#程序的控制执行条件)机制,而不具有[控制作用之间的互操作难以组合的问题](https://okmij.org/ftp/continuations/undelimited.html#delim-vs-undelim)。 * 异常基于续延提供,能确保可预测的互操作行为,尽管在基本操作中仍然可能使用异常作为实现。 * 这类似 [\[Racket\] 的“异常是派生的概念”的描述](https://docs.racket-lang.org/reference/eval-model.html#\(part._exn-model\)),但 NPLA1 异常并非如 \[Racket] 内建,因为[抛出异常](#错误处理)只是[引起错误](#npla1-错误)的实现,而不是接口要求。 * 尽管逻辑上引入[续延界限](#续延)可能是有益的,且有微妙的语义上的效果,这并没有简化用户程序和语言实现,因此当前不要求。 * 当前不支持[多次续延](#续延的捕获和调用)和续延复制。 * 当续延仅在同一个上下文中捕获时,缺少续延界限不会是一个明显的问题,因为被调用的续延总是会重新引入共享的子续延。 * 一些当前语言设计在 \[RnRS] 的操作上扩展了界限。 * 例如,不同于 \[RnRS] ,[\[Racket\] 的 `call/cc` 也检查提示(prompt) 的存在](https://docs.racket-lang.org/reference/cont.html#\(def._\(\(quote._~23~25kernel\)._call-with-current-continuation\)\))。 * 缺少续延界限可能引起控制操作符关联的一些模型之间的语义不等价问题而[难以使用其中的一种严格准确地表达其中的另一种](http://ix.cs.uoregon.edu/~ariola/tpdc11.pdf)。 * 在 SML/NJ 等语言中,因为不存在顶层的续延界限,这些问题容易成为在程序中真正阻碍使用有界续延的困难。 * 在 \[RnRK] ,正常程序运行的顶层续延界限以 `root-continuation` 提供。此外,提供 `error-continuation` 处理错误。 * 在完善的设计中,提供一个顶层的续延界限并非困难。因此,可能缺少取得续延界限的方法的问题不是核心困难。此外,如 \[RnRK] 的 `error-continuation` 显示了其它类似的续延的实用性。 * 类似的特性可能会被加入此处的设计中。 **注释**   类似 \[RnRK] 而和 \[RnRS] 不同,续延具有单独的类型,续延应用也不是蕴含过程调用的函数应用。 **操作:** `call/1cc `   捕获[一次续延](#续延的捕获和调用),[具现](../Terminology.zh-CN.md#程序设计语言)为一等续延作为参数调用合并子。   续延首先在[尾上下文中](#尾上下文约定)被[按引用捕获](#函数内部的变量),再作为操作数,调用 `` 。   来自同一具现的一次续延的任何副本只允许一次显式(续延应用)或者隐式(如被函数调用的返回蕴含)地成功调用;否则,调用被[重入](#λ-求值策略),[引起错误](#npla1-错误)。   捕获的续延之后允许被复制。 **注释**   `call1/cc` 的名称[来源](https://legacy.cs.indiana.edu/~dyb/pubs/call1cc.pdf)同 Chez Scheme 。 `continuation->applicative `   转换续延为[关联的等效](#续延的捕获和调用)应用子。   结果是转换的 `` 类型的值。   在构造结果时,`` 被转发。   结果的底层合并子被调用时,传递操作数树给 `` 。 `apply-continuation `   应用续延。   以第二参数作为参数,以 `apply` 应用第一参数指定的续延转换的应用子。 **原理**   同 \[RnRK] ,`apply-continuation` 不接受可选的环境,因为非正常控制忽略动态环境。 **注释**   即同求值:`apply (apply-continuation ) ` 。   `apply-continuation` 同 \[RnRK] 。取得非 `` 结果依赖 `apply` 对 `` 的支持,这在 Scheme 中无法实现。 ## 代理求值   代理求值支持保存求值为代理对象以实现延迟求值。   默认加载为根环境下的 `std.promises` 环境。 **模块约定:**   本节约定以下求值得到的操作数: * `` :求值代理:表示可被求值而取得[结果对象](#求值和对象所有权)的对象。 * 保存待求值的表达式和这个表达式[求值时的动态环境](#函数和函数应用的求值环境),或已求值的结果对象。 **原理**   代理求值的原语可实现惰性求值和透明的记忆化。   和一些流行的误解不同,尽管这些原语的原始设计是关于并行处理的,这不必然蕴含并发的投机执行(speculative execution) ,只是因为解析(resolve) 内部状态并不在用户程序中可见,而蕴含必要的最小同步。   由于[当前语言不支持并发访问](#不安全操作),即使对 `` 的[修改操作](#对象的修改和改变)导致变化,在语言中其状态也不可见,没有要求支持这种同步;未来可能会附加要求以提供更完善的并发支持。   关于 [API](../Terminology.zh-CN.md#程序设计语言) 的设计,参见 \[RnRK] §9 和 [\[SRFI-45\]](https://srfi.schemers.org/srfi-45/srfi-45.html) 。 **注释**   在 `` 上的[并发访问](#npla-并发访问)并不具有特别的同步保证和要求。并发访问[可引起宿主语言的未定义行为](#npla1-程序外部环境)。   除 `$lazy/d` 外,同 \[RnRK] 的 promises 模块。   和 \[RnRK] 不同,通过 `force` 引起 `` 对象的求值可能修改这个对象自身而使其中的状态失效(如通过 [`assign!`](#基本派生特性)对这个对象赋值)。   因此,实现中需要重新访问状态,而重新进行类型检查。 **操作:** `promise? `   `` 的[类型谓词](#谓词名称后缀)。 `memoize `   记忆化求值:以参数作为已被求值的结果创建 `` 对象。   [在结果中保留参数的引用值](#保留引用值的约定)。 `$lazy `   惰性求值:以参数为待求值的表达式,以[当前环境](#当前环境)作为这个表达式被求值的动态环境,创建 `` 对象。   当前环境的[环境弱引用](#环境引用)的副本被保存到结果。 `$lazy% `   同 `$lazy` ,但保留引用值。 `$lazy/d `   同 `$lazy` ,但以参数指定环境替代动态环境。 `$lazy/d% `   同 `$lazy%` ,但以参数指定环境替代动态环境。   参数指定的一等环境值的副本被保存到结果。 `force `   若参数是 `` 值,立即求值指定的 `` 对象,得到的结果对象作为结果;否则,[转发参数](#函数参数转发)作为结果。   若参数在求值时修改为非 `` 类型的值,需要继续迭代求值时,引起[类型错误](#类型检查)。 ## 数学库   数学库提供[数值类型](#数值类型)的操作和其它数学功能。   默认加载为根环境下的 `std.math` 环境。 **模块约定:**   以下操作中: * 除非另行指定,对应的函数调用的求值是[纯求值](#求值性质)。 * 涉及数值操作数的操作符合[数值操作约定](#数值操作约定)。 * 所有除法和取余数的计算符合[除法约定](#除法约定)。 **原理** 关于比较操作:   同 \[RnRK] 而不同于 \[R5RS] 、\[R6RS] 和 \[R7RS] ,比较操作不明确要求传递性(但精确数仍然因真值的数学性质而保证传递性),以允许操作数存在不精确数时,转换不精确数 flonum 的简单实现。   \[R6RS] 继承了 \[R5RS] 要求过程 `=` 具有传递性(注释指出传统类 Lisp 语言的实现不要求),而 \[R7RS] 的对应注释指出这不能通过把所有操作数都转换为不精确数实现。   不要求不精确数的传递性和除法约定对除数为不精确数 0 值的处理兼容。   不同的语言在此[可能有不同的规则](https://codewords.recurse.com/issues/one/when-is-equality-transitive-and-other-floating-point-curiosities),可见: * 一些现代 Lisp 语言可能满足(即便不是 Scheme 实现,如 SBCL )或不满足(即便是 Scheme 实现的派生,如 Racket )此要求。 * 一些语言的不同版本的实现可能使用不同的规则(如 Ruby 1.8.7 和 Ruby 2.0 )。   使用以上链接中的测试用例,可发现一些 \[RnRS] 的实现实际可能不符合 `=` 的传递性要求,如 x86-64 Linux 上: * Chez Scheme 9.5.6 、Chibi Scheme 0.10.0 和 Gauche 0.9.11 不符合要求。 * Chicken 5.3.0 、Guile 2.2.7 和 Gambit 4.9.4 符合要求。   上述符合性问题已在以下实现中报告并被修复: * [Chibi Scheme issue 812](https://github.com/ashinn/chibi-scheme/issues/812) * [Chez Scheme issue 606](https://github.com/cisco/ChezScheme/issues/606) * [Gauche issue 805](https://github.com/shirok/Gauche/issues/805) **注释**   和数值操作约定不同,幂等操作要求超过一次应用时,结果和参数的宿主类型也相同。 **操作:** `number? `   `` 的[类型谓词](#谓词名称后缀)。 `complex? `   `` 的类型谓词。 **注释** 同 `number?` ,因为当前 `` 值都是 `` 值。 `real? `   `` 的类型谓词。 **注释** 同 `complex?` ,因为当前 `` 值都是 `` 值。 `rational? `   `` 的类型谓词。 **注释** 当前实现仅需排除[无限大和 NaN 值](#数值类型)。 `integer? `   `` 的类型谓词。 `exact-integer? `   判断参数是否为 `` 类型的[精确数](#数值类型)对象。 `fixnum? `   判断参数是否为 [fixnum](#数值类型)对象。 `flonum? `   判断参数是否为 [flonum](#数值类型)对象。 `exact? `   判断参数是否为精确数。 **注释** 当前精确数都是 fixnum 。 `inexact? `   判断参数是否为[不精确数](#数值类型)。 **注释** 当前不精确数都是 flonum 。 `finite? `   判断参数是否为[有限值](#数值类型)。 `infinite? `   判断参数是否为无限大值。 `nan? `   判断参数是否为 NaN 值。 `=? `   比较相等。 ` `   比较小于。 `>? `   比较大于。 `<=? `   比较小于等于。 `>=? `   比较大于等于。 `zero? `   判断参数是否为零值。 `positive? `   判断参数是否为正数。 `negative? `   判断参数是否为负数。 `odd? `   判断参数是否为奇数。 `even? `   判断参数是否为偶数。 `max `   计算参数中的最大值。 `min `   计算参数中的最小值。 `add1 `   计算参数加 1 的值。 `sub1 `   计算参数减 1 的值。 `+ `   加法:计算参数的和。 `- `   减法:计算参数的差。 `* `   乘法:计算参数的积。 `/ `   除法:计算参数的商。 `abs `   计算参数的绝对值。   `abs` 是[幂等操作](#基本语义概念)。 **注释** 同 \[RnRS] 和 \[RnRK] ,当前仅支持 `` ,尽管数学上这对 `` 也有意义。 `floor/ `   数论除法:计算参数向下取整的整除的商和余数。 `floor-quotient `   数论除法:计算参数向下取整的整除的商。 `floor-remainder `   数论除法:计算参数向下取整的整除的余数。 `truncate/ `   数论除法:计算参数截断取整的整除的商和余数。 `truncate-quotient `   数论除法:计算参数截断取整的整除的商。 `truncate-remainder `   数论除法:计算参数截断取整的整除的余数。 `inexact `   取和参数值最接近的不精确数: * 若参数是不精确数,则结果是参数值。 * 否则,若参数超过任意不精确数内部表示的有限值的范围,则结果是未指定宿主类型的对应符号的无限大值。 * 否则,若不存在和参数数值相等(以 `=?` 判断)的不精确数,则引起错误。 * 否则,结果是和参数数值相等的不精确数。   `inexact` 是幂等操作。 **原理**   这里明确约定了[错误条件](#错误),这和 \[RnRK] 及 \[RnRS] 宽松地允许引起错误(也允许不引起[要求诊断的错误](#错误))不同。   允许返回无限大值使程序容易判断非预期值的原因。使用浮点数作为不精确数,无限大值的结果符合 \[IEC 60559] 的定义。实际 \[RnRS] 实现及 klisp 普遍使用这种实现。   对其它情形保证 `=?` 比较的后置条件允许用户定义严格相等的转换函数(通过对结果应用 `infinite?` 可发现并排除无限大值)。   典型实现中,当参数是 [fixnum](#数值类型)值时,结果通常不超过不精确数能表示的范围,也不需要引起错误。 **注释** 同 \[R6RS] 和 \[R7RS] 的同名过程,及 \[R5RS] 的 `exact->inexact` ;因为当前实现不支持不是 `` 的 `` ,也同 \[RnRK] 的 real->inexact 。关于 \[RnRS] 中的命名,另见 [R7RS ticket 328](https://small.r7rs.org/ticket/328/) 和 \[R7RS] 的相关注释。 `string->number `   转换字符串为数值。   若转换成功,结果是对应参数作为外部表示的数值。否则,结果是 `#f` 。   不因转换失败引起错误。   数值包括所有实现支持的值。 **注释** 同 \[R7RS] 的同名过程,但不支持附加的可选的进位制参数。 `number->string `   转换数值为字符串。 **注释** 同 \[R7RS] 的同名过程,但不支持附加的可选的进位制参数且不因特定的参数值出错。 ### 除法约定   二元除法或者取余数的操作中,第一个参数是被除数,第二个参数是除数。   数论除法的结果中的数值具有整数类型。   当除数是不精确数 0 时: * 若被除数是非零有限数值或无限大值,则商的符号同被除数的符号。 * 否则,商的符号未指定。   当被除数是不精确数时,若除数是精确数 0 ,则结果除符号外同除数是不精确数 0 的情形。   同时计算商和余数的操作的结果是商和余数构成的列表。 **原理**   Scheme 方言及实现中普遍存在不同的除零错误的条件。   \[R5RS] 的过程 `/` 除以零的条件没有被明确约定。   \[R6RS] 则明确要求过程 `/` 中: * 在所有参数为精确数,若除数存在零值时,引发条件类型为 `&assertion` 的异常。 * 否则,以例子指定除数存在零值时的结果: * 若被除数为非零有限数值,结果为符号同被除数的。 * 否则,结果为正的 NaN 值。   \[R7RS] 中修订 \[R5RS] 的过程 `/` 中存在精确数 0 作为除数的除法结果是错误,但这不要求引起错误。实现可以引起错误,或结果具有未指定的错误的值。   \[R7RS] 实际维持 \[R5RS] 的条件不变,而非 \[R6RS] 附加更多的要求;参见 [R7RS ticket 367](https://small.r7rs.org/ticket/367/) ,但其中关于 \[R6RS] 实现可转换操作数为不精确数和其它操作一致有误,因为 \[R6RS] 的过程 = 不能以此方式实现(参见以上关于比较操作的原理的讨论)。   不同的 Scheme 实现对零值的处理(包括除零错误的条件)另见[这里](https://small.r7rs.org/wiki/Zero/) 。   \[RnRK] 的合并子 `/` ,存在任意除数为零值则引起错误。   因为 \[RnRK] 的不精确数是可选模块,不讨论除数的零值是否精确值而保持简单性是合理的。   \[RnRS] 明确要求支持精确数和不精确数(存在不同的字面量),但是只有 \[R6RS] 要求存在不精确值时的确切结果。   [NPLAMath](#npla-数学功能)的数值操作约定和 \[R7RS] 类似,但在此附加和 \[R6RS] 类似的要求而覆盖数值操作约定,因为: * 不精确数 0 往往来自表示[下溢](#数值操作约定)的计算结果,而不是精确的真值 0 ,因此数学上商应存在定义。 * 参见 [IEEE-754 的分析](https://people.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF)。 * 另见[这里](https://stackoverflow.com/a/14684474)。 * 同 \[RnRS] ,NPLAMath 支持的不精确数允许自然地定义除数为不精确数 0 时的结果: * NPLAMath 明确要求支持不精确数(而非 \[RnRK] 作为可选的特性提供),能区分不精确数 0 和精确数 0 的不同结果。 * NPLAMath 的不精确数 0 允许支持符号据此确定作为商的无限值的符号(而非 \[RnRK] 无法区分符号而无法按数学定义确定极限值的符号)。 * 这些规则允许更优化的实现: * 避免要求实现检查特定的精确数(在此是精确数 0 )的存在,而允许更简单高效的直接使用 [flonum](#数值类型)内部操作的实现。 * 不在此转换不精确数到精确数,和当前实现[没有提供精确数可能替换计算结果中的不精确数的机制](#数值操作约定)具有更好的一致性。 * 不存在 \[RnRS] 需要关心继续使用 \[R6RS] 规则的[一些问题](https://small.r7rs.org/ticket/367/): * 在被除数和除数都存在零值时,没有同 \[R6RS] 的 / 的例子要求结果的符号,逻辑上仍然能保持一致。 * NPLA1 不需要考虑不完全支持不精确数的(不满足 \[RnRS] 符合性)的实现的兼容性等问题。 **注释**   同 \[R7RS] ,若被除数是零值,且除数不是精确数 0 ,计算结果可能是精确数 0 。但当前实现没有提供精确数替换计算结果中的这种机制。   NPLA1 `std.math` 中名称以 `floor` 和 `truncate*` 起始的函数的语义同 \[R7RS] 的定义,要求参数是整数。一些 Scheme 实现不严格要求整数。   \[R6RS] 的整除基本操作(过程 `div` 、`mod` 、`div0` 、`mod0` )没有严格定义,仅通过例子说明一些差异,但明确不要求整数。   \[RnRK] 的整除同 \[R6RS] ,但描述和 \[R6RS] 矛盾;klisp 实现行为同 \[R6RS] 。   更一般的定义参见 [\[SRFI-141\]](https://srfi.schemers.org/srfi-141/srfi-141.html) 和其它相关参考文献: * http://dl.acm.org/citation.cfm?id=128862 * http://people.csail.mit.edu/riastradh/tmp/division.txt * https://www.gnu.org/software/guile/manual/html_node/Arithmetic.html * https://github.com/johnwcowan/r7rs-work/blob/master/WG1Ballot3Results.md#185-add-sixth-centered-division-operator * https://wiki.call-cc.org/eggref/5/srfi-141 ## 字符串库   提供字符串和正则表达式的相关操作。   默认加载为根环境下的 `std.strings` 环境。 **模块约定:**   本节约定以下求值得到的操作数: * `` :正则表达式。   为提供[宿主语言互操作](#npla-互操作支持)支持,正则表达式以 `std::regex` 类型表示,实现保证可通过 `` 对应的 [`string`](#类型映射) 值初始化。   除非另行指定,以上所有正则表达式的操作使用 \[ISO C++11] 指定的默认选项,即: * `std::regex_constants::ECMAScript` 。 * `std::regex_constants::match_default` 。 * `std::regex_constants::format_default` 。 **操作:** `string? `   `` 的[类型谓词](#谓词名称后缀)。 `++ ...`   字符串串接。 `string-empty? `   判断字符串是否为空。 **注释** 同 [\[SRFI-152\]](https://srfi.schemers.org/srfi-152/srfi-152.html) 的过程 `string-null?` 。 `string<- `   字符串[赋值](#赋值)。   以第二参数为源,修改第一参数指定的目标。 `string-trim `   删除字符串中指定的连续前缀空白符。   空白符是 C++ 字符串中 `" \n\r\t\v"` 的字符之一。 **注释** 同 \[SRFI-152] 的过程 `string-trim-both` ,但不支持可选参数,且默认指定的空白符不同。 `string-trim-left `   删除字符串中指定的连续后缀空白符。   空白符同 `string-trim` 中的定义。 **注释** 同 \[SRFI-152] 的过程 `string-trim` ,但不支持可选参数,且默认指定的空白符不同。 `string-trim-right `   删除字符串中指定的连续前缀和后缀空白符。   空白符同 `string-trim` 中的定义。 **注释** 同 \[SRFI-152] 的同名过程,但不支持可选参数,且默认指定的空白符不同。 `string-prefix? `   判断第一参数是否包含第二参数作为前缀子串。 **注释** 同 \[SRFI-152] 的同名过程,但不支持可选参数。 `string-suffix? `   判断第一参数是否包含第二参数作为后缀子串。 **注释** 同 \[SRFI-152] 的同名过程,但不支持可选参数。 `string-contains? `   判断第一参数是否包含第二参数作为子串。 **注释**   一个串总是包含相等的串。空串被任何串包含,且总是不包含任何非空串。   同 \[SRFI-152] 的过程 `string-contains` ,但不支持可选参数,且结果是 `#t` 或 `#f` 。 `string-contains-ci? `   判断第一参数是否包含第二参数作为子串,忽略大小写。   只在单字节字符集内的字符中区分大小写。 **注释** 除不区分大小写外同 `string-contains?` 。 `string-split `   取第二参数分隔第一参数得到的字符串的列表。 **注释** 同 \[SRFI-152] 的同名过程,但不支持可选参数。 `string->symbol `   转换字符串为符号。 `symbol->string `   转换符号为字符串。   不检查值是否符合符号要求。 `string->regex `   转换字符串为以这个字符串作为串的正则表达式。   若正则表达式无效,则[引起错误](#npla1-错误)。 `regex-match? `   判断字符串中是否匹配正则表达式的模式串。   若 `` 匹配 `` 指定的模式串,结果是 `#t` ,否则结果是 `#f` 。 `regex-replace `   替换字符串中的模式串,构造新字符串。   在 `` 的副本中搜索正则表达式指定的模式串的所有匹配,替换为 `` 指定的格式字符串。   结果是替换后的字符串。 **注释**   当前实现不处理实现抛出的 `std::regex_error` 异常。 ## 输入/输出库   提供输入/输出操作。   默认加载为根环境下的 `std.io` 环境。 **模块约定:**   文件系统中创建或移除项的函数的[返回值](#函数调用)是表示操作是否成功的 `` 值。   除非另行指定: * 对创建目录的操作,在若目录已存在,则视为操作失败且无作用;否则,若创建失败,则[引起错误](#npla1-错误)。 * 对创建多个目录的操作,仅在所有目录都创建成功时操作成功。操作失败可能仍存在部分目录被创建成功。 * 对访问文件的操作,若读或写失败,则引起错误。 * 对访问文件系统路径的操作,除非另行指定,若[实现环境](../Terminology.zh-CN.md#规范)支持跟随文件系统对象的链接,隐含跟随链接(即解析指定链接的文件系统路径到最终的非链接对象作为路径指定的资源)。 * 解析链接可能引起错误。 * **注释** 文件系统对象链接的实例如 POSIX 符号链接(symbolic link) 和 Windows 重解析点(reparse point) 。 **原理**   文件系统解析文件名可支持链接,但不是所有环境有同等支持。   [Scsh 的 `file-not-readable?`](https://scsh.net/docu/html/man-Z-H-4.html#node_idx_318) 等明确不支持 `chase?` 指定跟随链接,理由是不检查符号链接权限,但这并不充分。   事实上: * 对 POSIX 实现: * [POSIX.1-2008](https://pubs.opengroup.org/onlinepubs/9699919799.2008edition/) 和[后续修订的 `faccessat`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html) 不支持 [`AT_SYMLINK_NOFOLLOW`](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/fcntl.h.html) 。 * [FreeBSD 的 `faccessat`](https://man.freebsd.org/cgi/man.cgi?query=faccessat&sektion=2&n=1) 也不支持 `AT_SYMLINK_NOFOLLOW` 。 * 但是,[Linux (glibc)](https://man7.org/linux/man-pages/man2/access.2.html) 和 [bionic](https://android-review.googlesource.com/c/platform/bionic/+/128582) 可支持 `AT_SYMLINK_NOFOLLOW` 。 * 于是,至少在特定的 POSIX 实现中,对链接自身的权限判断是有意义的。 * 此外,Windows 也允许单独判断链接和不同链接目标的权限。   因此,一般的 API 应当允许区分访问文件是否跟随链接。 **注释**   当前派生实现依赖可用的 [`std.strings`](#字符串库)环境。   当前实现的文件系统操作失败可能修改宿主环境的 `errno` 。 **操作:** `file-exists? `   判断参数指定的文件名对应的文件是否存在。 **注释**   同: * \[R7RS] 的同名过程,但明确跟随链接。 * [Chez Scheme 的同名过程](https://cisco.github.io/ChezScheme/csug9.5/io.html#./io:s141),但不支持可选参数。 * Chez Scheme(至少在截至版本 9.5.6 )在 Windows 上[并没有实现可选参数,实际总是不跟随链接](https://github.com/cisco/ChezScheme/issues/716)。 `file-directory? `   判断参数指定的文件名对应的文件是否存在且为目录文件。 **注释**   同: * [Chez Scheme 的同名过程](https://cisco.github.io/ChezScheme/csug9.5/io.html#./io:s143),但不支持可选参数。 * 参见 `file-exists?` 的注释。 * [Gauche](https://practical-scheme.net/gauche/man/gauche-refe/System-interface.html#index-file_002dis_002ddirectory_003f) 和 [STklos](https://stklos.net/Doc/html/stklos-ref-4.html#--index-entry-32817) 的 `file-is-directory?` ,但明确跟随链接。 `file-regular? `   判断参数指定的文件名对应的文件是否存在且为常规文件。 **注释**   同: * [Chez Scheme 的同名过程](https://cisco.github.io/ChezScheme/csug9.5/io.html#./io:s142),但不支持可选参数。 * 参见 `file-exists?` 的注释。 * [Gauche](https://practical-scheme.net/gauche/man/gauche-refe/System-interface.html#index-file_002dis_002dregular_003f) 和 [STklos](https://stklos.net/Doc/html/stklos-ref-4.html#--index-entry-32813) 的 `file-is-regular?` ,但明确跟随链接。 `readable-file? `   判断参数指定的文件名对应的文件是否存在且可读。 **注释**   同: * [Scsh 的 `file-readable?`](https://scsh.net/docu/html/man-Z-H-4.html#node_idx_324)。 * [Gauche](https://practical-scheme.net/gauche/man/gauche-refe/Filesystem-utilities.html#index-file_002dis_002dreadable_003f) 和 [STklos](https://stklos.net/Doc/html/stklos-ref-4.html#--index-entry-32805) 的 `file-is-readable?` ,但明确跟随链接。 `readable-nonempty-file? `   判断参数指定的文件名对应的文件是否存在、可读且文件内容非空。 `writable-file? `   判断参数指定的文件名对应的文件是否存在且可写。 **注释**   同: * [Scsh 的 `file-writable?`](https://scsh.net/docu/html/man-Z-H-4.html#node_idx_326)。 * [Gauche](https://practical-scheme.net/gauche/man/gauche-refe/Filesystem-utilities.html#index-file_002dis_002dwritable_003f) 和 [STklos](https://stklos.net/Doc/html/stklos-ref-4.html#--index-entry-32809) 的 `file-is-writable?` ,但明确跟随链接。 `() newline`   输出换行并刷新缓冲。   若无法输出,则没有作用。 `put `   输出字符串。 `puts `   输出字符串和换行并刷新缓冲。 **注释** 同 `put` 和 `newline` 的组合。 `load `   加载参数指定的翻译单元作为[源](#模块)的[模块](#模块)。   加载时在[当前环境](#当前环境)读取翻译单元后求值,以求值后的这个环境对象作为调用的结果。   参数是指定源的确切位置的[加载路径](#npla1-参照实现扩展环境)。当前实现中,参数为文件系统路径。   被加载的翻译单元视为对象的[外部表示](#表示),经读取翻译为 NPLA1 对象。 **原理**   和 \[R7RS] 不同,`load` 不支持指定环境,而总是使用当前环境。   类似 Kernel ,当前环境可通过不同机制改变,而不需由 `load` 提供特设的支持。例如,可使用 [`eval`](#基本派生特性)指定蕴含 `load` 的调用的求值使用的环境。   和其它一些语言的类似命名的功能(如 Lua 的 `loadfile` )不同,`load` 的语义隐含从外部来源取得求值构造(evaluation construct) 后在当前环境求值,其中的求值明确允许隐含副作用。在此,`load` 的求值被视为初始化加载的模块过程中的一部分。   因为当前不提供取得求值构造的读取(read) 等函数,不要求 `load` 具有非本机的派生实现。并且,取得求值构造可能有其它方式,如从二进制映像映射(map) 到内部表示等替代,这些实现通常不应被要求为总是具有本机派生实现而降低实现质量。 **注释**   参数一般指定视为外部翻译单元的文件名。   类似 klisp 的同名操作。类似地,不使用 klisp 的 `find-required-filename` 机制,直接以宿主的运行环境为基准使用路径。   和 klisp 不同,在尾上下文中求值被加载后读取的对象,并以其求值结果作为操作的结果,且错误不影响操作的结果。   \[Shu09] 缺少 `load` 的详细描述而仅有标题。   另见以下关于 `get-module` 的说明。 `get-module ?`   创建标准环境并在其中加载模块。   创建[新](#新环境)标准环境并以这个环境为当前环境加载 `` 指定的翻译单元作为源的模块。   若第二参数非空,则在加载前首先绑定创建的环境中的 `module-parameters` 变量为第二参数的值。   结果在加载完成后取得,是先前被创建的标准环境。 **注释**   第一参数的作用同 `load` 的参数。   类似 klisp 和 \[Shu09] 中的同名操作。   [klisp 文档中的 `load`](http://klisp.org/docs/Ports.html#Ports) 描述中求值环境有误: * 按 \[Shu09] 一致的描述和 klisp 的实际实现,调用 `load` 时应在当前环境求值,而不同于 \[Shu09] 的 `get-module` 中描述的使用创建的新标准环境进行求值。 * 否则,使用 \[Shu09] 的 `get-module` 的派生不能实现 klisp 和 \[Shu09] 中描述的 `get-module` 的预期语义。 `absolute-path? `   判断参数是否指定绝对路径。 **注释**   同: * [Gauche 的同名过程](https://practical-scheme.net/gauche/man/gauche-refe/Filesystem-utilities.html#index-absolute_002dpath_003f)。 * [Chez Scheme 的 `path-absolute?`](https://cisco.github.io/ChezScheme/csug9.5/io.html#./io:s154) 。 * [Chicken 的 `absolute-pathname?`](http://api.call-cc.org/5/doc/chicken/pathname#sec:absolute-pathname.3f) 。 * [Guile 的 `absolute-file-name?`](https://www.gnu.org/software/guile/manual/html_node/File-System.html) 。 `path-parent `   参数字符串视为路径,结果是字符串指定的路径所在的父目录(当路径非根目录),或路径自身转换到的字符串值。   结果的路径不带有结尾分隔符。不检查路径实际存在。 **注释**   同: * [Chez Scheme 的同名过程](https://cisco.github.io/ChezScheme/csug9.5/io.html#./io:s154)。 * [Chicken 的 `pathname-directory`](http://api.call-cc.org/5/doc/chicken/pathname#sec:pathname-directory) ,但对不包含目录的参数,结果是空串而不是 `#f` 。 * **注释** 文档没有显示指出结果是 `#f` 。 * [Gauche 的 `sys-dirname`](https://practical-scheme.net/gauche/man/gauche-refe/System-interface.html#index-sys_002ddirname) ,但对不包含目录的参数,结果是空串而不是 `.` 。 * **注释** 文档没有显示指出结果是 `.` 。 * [Guile 的 `dirname`](https://www.gnu.org/software/guile/manual/html_node/File-System.html) ,但对不包含目录的参数,结果是空串而不是 `.` 。 `remove-file `   移除参数指定的路径命名的文件。若成功结果为 `#t` ,否则结果为 `#f` 。 **注释**   同: * \[STklos 的同名过程](https://stklos.net/Doc/html/stklos-ref-4.html#delete-file) ,但通过函数值指定调用失败而非引起错误。 * \[R7RS] 的 `delete-file` ,但通过函数值指定调用失败而非引起错误。 `create-directory `   创建参数指定的名称的文件系统目录。 **原理**   `create-directory` 在一些其它语言的实现中命名为 `make-directory` 。   NPLA1 中,作为非正式约定,`make` 前缀被预留给通过函数值得到创建的对象的过程。   这和 [Gauche 不区分两者](https://practical-scheme.net/gauche/man/gauche-refe/Filesystem-utilities.html)不同。   使用 `create-directory` 的命名也和 [\[SRFI-170\]](https://srfi.schemers.org/srfi-170/srfi-170.html) 一致,但 NPLA1 中,目录存在时不引起错误而以函数值 `#f` 指示。   此外,\[ISO C++] 中存在类似名称的[函数 `std::filesystem::create_directory`](https://eel.is/c++draft/fs.op.funcs#fs.op.create.directory) 。 **注释**   类似: * [Checken 的同名过程](http://api.call-cc.org/5/doc/chicken/file#def:create-directory),但不支持可选参数且函数值不同。 * [Scsh 的同名过程](https://scsh.net/docu/html/man-Z-H-4.html#node_sec_3.3),但不支持包括权限的可选参数且函数值不同。 * [Bigloo 的 `make-directory`](http://www-sop.inria.fr/mimosa/fp/Bigloo/manual-chapter5.html#G17247) ,但可能引起错误。 * [Chez Scheme](https://cisco.github.io/ChezScheme/csug9.5/io.html#./io:s146) 和 [Guile](https://www.gnu.org/software/guile/manual/html_node/File-System.html) 的 `mkdir` ,但不支持可选的权限参数且函数值不同。 * [\[Racket\] 的 `make-directory`](https://docs.racket-lang.org/reference/Filesystem.html#%28def._%28%28quote._~23~25kernel%29._make-directory%29%29) ,但不支持非字符串参数和可选的权限参数,且通过函数值指定调用失败而非引起错误。 `create-directory* `   创建参数指定的名称的文件系统目录及其必要的父目录。   `create-directory` 和 `create-directory*` 只创建一级目录时的行为确保一致。 **注释**   类似: * [Bigloo 的 `make-directories`](http://www-sop.inria.fr/mimosa/fp/Bigloo/manual-chapter5.html#G17256) ,但可能引起错误。 * [Gauche 的同名过程和 `make-directory*`](https://practical-scheme.net/gauche/man/gauche-refe/Filesystem-utilities.html#Directory-utilities) ,但不支持可选的权限参数。 * [\[Racket\] 的 `make-directory*`](https://docs.racket-lang.org/reference/Filesystem.html#%28def._%28%28lib._racket%2Ffile..rkt%29._make-directory%2A%29%29) ,但不支持非字符串参数和可选的权限参数。 `create-parent-directory* `   创建参数指定的名称对应的父目录及其必要的父目录。 **注释**   类似 [\[Racket\] 的 `make-parent-directory*`](https://docs.racket-lang.org/reference/Filesystem.html#%28def._%28%28lib._racket%2Ffile..rkt%29._make-parent-directory%2A%29%29) ,但不支持非字符串参数和可选的权限参数。   当前实现中,若创建目录失败而引起错误,抛出 `std::system_error` 异常。 ## 系统库   提供和[实现环境](../Terminology.zh-CN.md#规范)的交互功能。   默认加载为根环境下的 `std.system` 环境。 **对象:** `version-string`   当前实现的版本字符串。   类型为 `` 。 `build-number`   当前实现的构建版本号。   类型为 `` ,值为正整数。 `revision-description`   实现的版本说明。   类型为 `` 。 **操作:** `() get-current-repl`   取表示当前 REPL 环境的引用值。 `() cmd-get-args`   返回[宿主环境](#嵌入宿主语言实现)程序接受的命令行参数列表。   其中参数数组保存在实现内部访问的对象中。   传递给 REPL 的命令行参数通常是宿主程序中主函数的 `argv` 参数数组中处理后去除特定参数后的程序。   宿主程序复制 `argv` 到这个数组作为副本后,作为[返回值](#函数调用)的列表的来源。 `env-get `   取宿主环境的环境变量字符串。   字符串参数指定环境变量的名称。 `env-set `   设置宿主环境的环境变量字符串。   两个字符串参数分别指定环境变量的名称和设置的值。   使用 `env-get` 和 `env-set` 及对应宿主环境的操作不保证线程安全。 `env-empty? `   判断字符串指定名称的环境变量是否为空。 `system `   以 `std::system` 兼容的方式调用外部命令。   结果是宿主环境命令返回的命令退出状态。 **注释**   类似 [Chez Scheme 的外部接口](https://cisco.github.io/ChezScheme/csug9.5/foreign.html) ,但更接近 \[ISO C] 和 \[ISO C++] 的原始含义,当前不提供关于信号等依赖特定实现环境的保证。   使用 \[ISO C] 和 [\[ISO C++\] 的宿主环境的命令退出状态是 `int` 值](https://eel.is/c++draft/basic.start.main#2)。由 [NPLA 实现环境](#npla-实现环境),这里的结果的[宿主类型](#类型映射)是确定的。而 [NPLA1 数值](#npla1-数值)使用 [NPLA 数学功能](#npla-数学功能)确保其被[映射](#类型映射)为[确定的数值类型](#数值类型)。 `system-get `   调用命令,返回命令调用结果。 **注释** 在典型的 C++ 宿主实现中,命令的调用结果是来自标准输出的数据。   返回一个两个元素的列表,分别是管道输出字符串和宿主环境命令返回的命令退出状态。   调用命令和 `system` 的方式类似。 `make-temporary-filename `   取适合命名新创建的临时文件的随机文件名。   参数分别指定前缀和后缀。参数可能为空。随机文件名以生成的有限长度的随机字符串添加前缀和后缀组成。   生成的字符串只包含 C++ 基本字符串的可打印字符,且不使用大写字母,以兼容大小写不敏感的文件系统。   随机的文件名被打开以检查是否可访问。若失败则重试,若最终失败,则引起错误。 **注释**   功能类似 [POSIX `::tmpnam` 和 `::mkstemp`](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html) ,以及 [Guile 对应的过程 `tmpnam` 和 `mkstemp`](https://www.gnu.org/software/guile/manual/html_node/File-System.html) ,有以下不同: * 和 `tmpname` 类似,结果是文件名,而不是打开的文件或端口。 * 和 `mkstemp` 类似,使用模板字符串作为文件名,但模板不在参数中指定。    保证生成的文件名在不同[宿主实现](#需求概述)(文件系统)中的兼容性。   当前实现中的随机字符串长度为 6 。当前实现中重试的上限是 16 次(宿主平台)或 1 次(非宿主平台)。若最终失败,抛出 `std::system_error` 异常。 ## 模块管理   提供使用模块和其中符号的相关功能。   默认加载为根环境下的 `std.modules` 环境。 **模块约定:**   *需求字符串(requirement string)* 是具有 `` 类型的非空字符串。   若操作的形式参数是需求字符串,实际参数是空字符串,则[引起错误](#npla1-错误)。   本模块共享[可变管理状态](#对象的修改和改变),以支持操作访问确定的模块字符串集合。   本模块隐含一个字符串的序列,其中的每个元素都是可[指定搜索起点的模块的加载路径](#npla1-参照实现扩展环境),称为*需求字符串模板(requirement string template)* 。   除非派生实现另行指定,需求字符串模板序列在[实现环境初始化](#npla1-实现环境初始化)后不可变。   初始化决定的需求字符串模板序列的内容由实现定义。   程序可通过本模块*注册(register)* 需求字符串。本模块维护已被注册的需求字符串的集合。若一个字符串的值和集合中的某个值相等,则这个字符串作为需求字符串已被注册。   注册新的需求字符串时,同时创建和保存: * 待加载结果的对象,其初始值为 `()` 。 * 可供加载使用的[新](#新环境)[标准环境](#基本派生特性)。   本模块确保对不同的被维护的需求字符串,对应的上述资源不同一。 **原理**   参数的值和结果不需要保证一一对应。通过指定需求字符串模板序列,可以指定在文件系统不同位置的相同文件名模式的多个文件作为需求字符串对应的候选模块的源,按需选取其中之一指定其确切位置。   为避免名称污染等问题,不提供显式指定命名环境的模块创建操作,如 [Lua 5.1 的 `module` 函数](http://www.lua.org/manual/5.1/manual.html#pdf-module)等[存在一些问题](http://lua-users.org/wiki/LuaModuleFunctionCritiqued)。   不提供访问创建的环境的操作,以避免污染外部的访问。若需公开其中的变量绑定,可使用返回或模块参数。   提供较少的操作以简化规则,避免[多个运行时可能造成的混乱](https://xkcd.com/1987/)。关于搜索规则,另见以下 `find-required-filename` 的说明。 **注释**   对内部共享状态的[并发访问](#npla-并发访问)并不具有特别的同步保证和要求。并发访问[可引起宿主语言的未定义行为](#npla1-程序外部环境)。   当前实现中,若环境变量 `NPLA1_PATH` 的值非空,则需求字符串模板序列是这个值以单字符子串 `;` 分隔后的结果串接组成的序列;否则,默认值是字符串 `./?` 和 `./?.txt` 构成的序列。   关于使用需求字符串模板序列搜索模块,参见以下 `find-required-filename` 的说明。   当前派生实现依赖可用的 [`std.strings`](#字符串库)、[`std.io`](#输入输出库)和 [`std.system`](#系统库)环境。   通过不经过本模块的操作(如[互操作](#npla-互操作支持))、重复字符串模板的重复项、符号链接和字符串大小写不敏感的文件名等可能绕过本模块的注册机制而重复加载同一个外部文件。本模块的操作不对这些情形进行任何检查。 **操作:** `registered-requirement? `   判断参数是否是已在本模块注册的需求字符串。 `register-requirement! `   在本模块注册参数为需求字符串。   若参数指定的需求字符串已被注册,则引起错误;否则,创建并保存新标准环境。   结果是保存的环境的[弱引用](#环境引用)。 `unregister-requirement! `   在本模块解除注册参数为需求字符串。   若参数指定的需求字符串没有被注册,则引起错误;否则,移除与参数的值相等的需求字符串以及保存的关联的资源。 `find-required-filename `   查找需求字符串对应的文件名。   在需求字符串模板序列中顺序地检查每一个元素: * 使用参数的值替换元素中的每个单字符子串 `?` 。 * 判断经替换得到的字符串是否构成指定一个可读文件的文件名。   若不存在这样的文件名,则引起错误;否则,结果是第一个符合条件的文件名。   替换字符串时,每一个子串被同时一次替换;不对替换结果进一步递归地替换。 **原理**   类似 klisp 而和 Lua( [`require` 的实现](https://www.lua.org/source/5.3/loadlib.c.html);没有[在此](https://www.lua.org/pil/8.1.html)明确)及 [Haskell](https://www.haskell.org/hugs/pages/users_guide/module-commands.html) 等不同,不替换名称中的 `.` 为路径分隔符,以简化对实现环境的假设。 **注释**   若需求字符串模板的元素中没有通配符 `?` ,则不需要替换,需求字符串模板直接是指定确切位置的结果文件名。 `require ?`   按需在新标准环境加载需求字符串对应的模块。   若第二参数非空,则在加载前首先绑定创建的环境中的 `module-parameters` 变量为第二参数的值。   按需加载包含以下步骤: * 若参数指定的需求字符串没有注册,则: * 以同调用 `register-requirement?` 等价的方式注册需求字符串,确保注册时的环境被创建。 * 以同调用 `find-required-filename` 等价的方式确定待加载的源的文件名。 * 以同模块 [`std.io` 中的 `load`](#输入输出库)相同的方式在注册时创建的环境加载上述文件名指定的源。 * 保存加载的结果。 * 否则,不进行加载。 * 结果是已保存的加载的结果。 **原理**   先注册需求字符串允许重入调用 `require` ,即加载模块时求值的源代码包含和先前的字符串参数相同的 `require` 调用,而不至于无限递归。 **注释**   类似 klisp 的 ports 模块中的同名应用子,但有以下不同: * 同时保存创建的环境,以避免因程序没有保存环境引用而[无效化](#无效化),使访问变量绑定的程序具有未定义行为。 * 结果是加载结果(同模块 `std.io` 中的 `load` )而不是 `#inert` 。 * 支持同 [`std.io` 中的 `get-module`](#输入输出库) 的可选参数。 # 附录 ## 进一步阅读   关于 NPL 语言[派生实现](#略称)的具体实现,参见 YSLib 项目文档 [`doc/NPL.txt`](https://osdn.net/projects/yslib/scm/hg/YSLib/blobs/tip/doc/NPL.txt) 。 ## Kernel 实现   NPL 文法是 S-表达式语法和 Kernel 兼容语义的简化,某些 NPLA1 程序可以原封不动地作为 Kernel 程序运行。因为参考文献可用性以及相似性几乎独一无二,建议深入使用前参照 Kernel 相关的文档并实际使用,以和 NPL 进行比较(当前 NPL 开发文档也引用包括 \[RnRK] 在内的一些规格说明)。现有较完整可用的一个 Kernel 实现是 [Klisp](http://klisp.org/) 。 ## 计划支持特性   在 Kernel 中已存在但当前 NPLA1 未支持,而计划在未来添加的特性: * 一等续延(first-class continuation) * [基于一些显著的理论和实现可用性的理由](http://okmij.org/ftp/continuations/against-callcc.html),需要考察[有界续延](#续延)(如 `shift`/`reset` )而不是传统的 `call/cc` 的支持。 * 和 Klisp 的异步实现方式类似但不同,NPLA1 核心已使用一种同 [CESK-style 的抽象机](http://www.ccs.neu.edu/home/dvanhorn/pubs/vanhorn-might-icfp10.pdf)的异步风格独立重新实现([原理参考](http://www.brics.dk/RS/05/16/BRICS-RS-05-16.pdf)),并兼容混合原有实现。并且状态支持已提供基本操作,但仍待更多测试。 * [Promises](http://klisp.org/docs/Promises.html#Promises) * 虽然 NPLA1 之前有部分原生实现,但计划改用基于封装类型派生。 * [Keyed Variables](http://klisp.org/docs/Keyed-Variables.html#Keyed-Variables) * 有效的 keyed dynamic varabile 实现可能依赖一等续延数据结构。 * 有效的 keyed static varabile 实现需要调整环境数据结构。 * [Ports](http://klisp.org/docs/Ports.html#Ports) * 需要调查内存映射文件和其它系统支持。 * [Libraries](http://klisp.org/docs/Libraries.html#Libraries) * 可能需要提供多种不同的接口。   已提供但考虑可能优化实现的特性: * [封装类型](http://klisp.org/docs/Encapsulations.html#Encapsulations) * 需要调查互操作相关的特性和持久化支持。   计划考虑提供其它设计替代选项的 Klisp 特性: * [Characters](http://klisp.org/docs/Characters.html#Characters) * 满足互操作要求的前提下,使用设计更合理的类型替代。 * [Vectors](http://klisp.org/docs/Vectors.html#Vectors) * 使用更完善的用户自定义派生类型替代。   其它 NPL 计划实现特性: * 模块化框架 * 支持类型系统的子语言 * 类似 [Typed Racket](https://docs.racket-lang.org/ts-reference/index.html) 。 * 测试框架 * 基于 `$let-safe` 的静态环境操作替换优化 * 用于编译的中间代码格式和二进制代码生成框架 * 类似 [Chez Scheme](https://www.scheme.com/) 对 [Nanopass](http://nanopass.org/) 的使用。 * [元编译(metacompilation)(en-US)](https://en.wikipedia.org/wiki/Metacompilation)