# 4.4.4实施查询系统

> 译者： [https://sicp.comp.nus.edu.sg/chapters/93](https://sicp.comp.nus.edu.sg/chapters/93)

[部分4.4.2](91) 描述了查询系统的工作方式。 现在，我们通过介绍系统的完整实现来填充细节。

# 4.4.4.1驱动程序循环和实例化

查询系统的驱动程序循环重复读取输入表达式。 如果表达式是要添加到数据库的规则或断言，则添加信息。 否则，假定该表达式为查询。 驱动程序将此查询以及由单个空帧组成的初始帧流传递给评估器`qeval`。 评估的结果是通过满足数据库中存在的变量值的查询而生成的帧流。 这些帧用于形成一个新流，该流由原始查询的副本组成，其中用帧流提供的值实例化变量，并在终端上打印此最终流：

```js
const input_prompt = "// Query input:";
const output_prompt = "// Query results:";

function query_driver_loop() {
    const input = prompt(input_prompt);
    const q = query_syntax_process(parse(input));
    if (assertion_to_be_added(q)) {
        add_rule_or_assertion(add_assertion_body(q));
        display("Assertion added to data base.");
    } else {
        display(output_prompt);
        display_stream(
            stream_map(
                frame =>
                    instantiate(q, frame,
                                (v, f) =>
                                   contract_question_mark(v)),
                qeval(q, singleton_stream(null))));

    }
    query_driver_loop();
}
```

在这里，与本章中的其他评估器一样，我们对查询语言的表达式使用抽象语法。 包括谓词`assertion_to_be_added`和选择器`add_assertion_body`在内的表达式语法的实现在第4.4.4.7节中给出。 功能`add_rule_or_assertion`在4.4.4.5节中定义。

在对输入表达式进行任何处理之前，驱动程序循环会将其句法转换为使处理效率更高的形式。 这涉及更改模式变量的表示。 实例化查询时，所有未绑定的变量都将在打印之前转换回输入表示形式。 这些转换由两个函数`query_syntax_process`和`contract_question_mark`执行（第4.4.4.7节）。

要实例化表达式，我们将其复制，用给定框架中的值替换表达式中的任何变量。 这些值本身是实例化的，因为它们可以包含变量（例如，如果`exp`中的`x`作为统一的结果绑定到`y`，而`y`依次绑定到5）。 如果无法实例化变量，则采取的措施由`instantiate`的过程参数给出。

```js
function instantiate(exp, frame, unbound_var_handler) {
    function copy(exp) {
        if (is_var(exp)) {
            const binding = binding_in_frame(exp, frame);
            if (! binding === undefined) {
                return unbound_var_handler(exp, frame);
     	    } else {
 	        return copy(binding_value(binding));
            }
	} else if (is_pair(exp)) {
            return pair(copy(head(exp)), copy(tail(exp)));
        } else {
            return exp;
	}
    }
    copy(exp);
}
```

在4.4.4.8节中定义了操作绑定的函数。

# 4.4.4.2评估者

由`query_driver_loop`调用的`qeval`函数是查询系统的基本评估器。 它以查询和帧流作为输入，并返回扩展帧流。 就像我们在第2章中实现通用操作时一样，它使用`get`和`put`通过数据定向的派发来标识特殊形式。任何未被标识为特殊形式的查询都假定为简单查询， 由`simple_query`处理。

```js
function qeval(query, frame_stream) {	
    const qfun = get(type(query), "qeval");
    return qfun === undefined
        ? simple_query(query, frame_stream)
        : qfun(contents(query), frame_stream);
}
```

The functions `type` and `contents`, defined in section 4.4.4.7, implement the abstract syntax of the expressions.

## 简单查询

`simple_query`函数处理简单的查询。 它以简单查询（模式）和帧流作为参数，并返回通过查询的所有数据库匹配扩展每个帧而形成的流。

```js
function simple_query(query_pattern, frame_stream) {	
    return stream_flatmap(
        frame => 
            stream_append_delayed(find_assertions(query_pattern, frame),
                                  delay(apply_rules(query_pattern, frame))),
        frame_stream);
}
```

对于输入流中的每个帧，我们使用`find_assertions`（第4.4.4.3节）将模式与数据库中的所有断言进行匹配，从而生成扩展帧流，然后使用`apply_rules`（第4.4.4.4节） 应用所有可能的规则，从而产生另一个扩展帧流。 合并这两个流（使用`stream_append_delayed`第4.4.4.6节），以使所有可以满足给定模式的方式与原始帧保持一致的流（请参阅练习 [4.62](93#ex_4.62) ）。 使用`stream_flatmap`（第4.4.4.6节）合并各个输入帧的流，以形成一个大流，该流以原始方式可以扩展原始输入流中的任何帧以产生与给定模式的匹配的所有方式。

## 复合查询

`And`查询通过`conjoin`函数如图 [4.5](91#fig_4.5) 所示进行处理，该函数将连接符和帧流作为输入，并返回扩展帧流。 首先，`conjoin`处理帧流以找到满足连词中第一个查询的所有可能帧扩展的流。 然后，将其用作新的帧流，然后将`conjoin`递归应用于其余查询。

```js
function conjoin(conjuncts, frame_stream) {	
    return is_empty_conjunction(conjuncts)
        ? frame_stream
        : conjoin(rest_conjuncts(conjuncts),
                  qeval(first_conjunct(conjuncts),
	                frame_stream));
}
```

表达方式

```js
put("and", "qeval", conjoin);
```

sets up `qeval` to dispatch to `conjoin` when an `and` expression is encountered.

`Or`查询的处理方式类似，如图 [4.6](91#fig_4.6)[4.62](93#ex_4.62)[4.63](93#ex_4.63) 。）

```js
function disjoin(disjuncts, frame_stream) {
    return is_empty_disjunction(disjuncts)
        ? null
        : interleave_delayed(
              qeval(first_disjunct(disjuncts), frame_stream),
	      delay(disjoin(rest_disjuncts(disjuncts),
	                    frame_stream)));
}
put("or", "qeval", disjoin);
```

联合和不合语法的谓词和选择器在第4.4.4.7节中给出。

## 筛选器

`Not`通过 [4.4.2](91) 部分中概述的方法处理。 我们尝试扩展输入流中的每个帧以满足被否定的查询，并且仅在无法扩展给定帧的情况下才将其包含在输出流中。

```js
function negate(operands, frame_stream) {
    return stream_flatmap(
              frame =>
	         is_null(qeval(negated_query(operands),
                               singleton_stream(frame)))
	         ? singleton_stream(frame)
		 : null,
	      frame_stream);
}
put("not", "qeval", negate);
```

`Javascript_value`是类似于`not`的过滤器。 流中的每个帧用于实例化模式中的变量，应用指示的谓词，并且谓词返回false的帧从输入流中过滤掉。 如果存在未绑定的模式变量，则会导致错误。

```js
function javascript_value(call, frame_stream) {
    return stream_flatmap(
              frame =>
                 execute(instantiate(call, frame,
                            (v, f) =>
                                error(v, "Unknown pat var --- javascript_value")))
                 ? singleton_stream(frame)
                 : null,
              frame_stream);
}
put("javascript_value", "qeval", javascript_value);
```

将谓词应用于参数的函数`execute`必须`evaluate`谓词表达式才能应用该函数。 但是，它不能评估参数，因为它们已经是实际参数，而不是其评估（在JavaScript中）将产生参数的表达式。 注意，`execute`是使用基础JavaScript系统中的`evaluate`和`apply`实现的。

```js
function execute(exp) {	
    return apply(evaluate(predicate(exp), user_initial_environment),
                 args(exp));
}
```

`always_true`表达式提供始终满足的查询。 它忽略其内容（通常为空），仅通过输入流中的所有帧。 `rule_body`选择器使用`always_true`表达式（第4.4.4.7节）为没有主体（即始终满足其主体的规则）定义的规则提供主体。

```js
function always_true(ignore, frame_stream) {	
    return frame_stream;
}
put("always_true", "qeval", always_true);
```

定义`not`和`javascript_value`语法的选择器在4.4.4.7节中给出。

# 4.4.4.3通过模式匹配查找断言

由`simple_query`（第4.4.4.2节）调​​用的函数`find_assertions`将一个模式和一个帧作为输入。 它返回一帧帧，每个帧通过给定模式的数据库匹配扩展给定帧。 它使用`fetch_assertions`（第4.4.4.5节）获取数据库中所有断言的流，应检查该断言是否与模式和框架相匹配。 在这里使用`fetch_assertions`的原因是，我们经常可以应用简单的测试，这些测试将从成功匹配的候选库中消除数据库中的许多条目。 如果我们取消了`fetch_assertions`并仅检查了数据库中所有断言的流，该系统仍然可以工作，但是计算效率较低，因为我们需要对匹配器进行更多调用。

```js
function find_assertions(pattern, frame) {	
    return stream_flatmap(
              datum =>
                 check_an_assertion(datum, pattern, frame),
              fetch_assertions(pattern, frame));
}
```

函数`check_an_assertion`将模式，数据对象（声明）和帧作为参数，并返回包含扩展帧的单元素流；如果匹配失败，则返回`null`。

```js
function check_an_assertion(assertion, query_pat, query_frame) {
    const match_result = pattern_match(query_pat, assertion, 
                                       query_frame);
    return match_result === "failed"
        ? null
        : singleton_stream(match_result);
}
```

基本模式匹配器返回字符串`"failed"`或给定帧的扩展名。 匹配器的基本思想是逐个元素地针对数据检查模式，为模式变量积累绑定。 如果模式和数据对象相同，则匹配成功，并且我们返回到目前为止累积的绑定帧。 否则，如果模式是变量，我们将通过将变量绑定到数据来扩展当前框架，只要这与框架中已经存在的绑定一致即可。 如果模式和数据都是成对的，我们（递归地）将模式的`head`与数据的`head`匹配以生成帧； 然后，在此帧中，将模式的`tail`与数据的`tail`匹配。 如果以上情况均不适用，则匹配失败，我们返回符号`failed`。

```js
function pattern_match(pat, dat, frame) {
    return frame === "failed" 
        ? "failed"
        : equal(pat, dat) 
        ? frame   
        : is_var(pat) 
        ? extend_if_consistent(pat, dat, frame)
        : is_pair(pat) && is_pair(dat)
        ? pattern_match(tail(pat),
                        tail(dat),
                        pattern_match(head(pat),
                                      head(dat),
                                      frame))
        : "failed";
}
```

这是通过添加新绑定来扩展框架的函数，如果这与框架中已有的绑定一致：

```js
function extend_if_consistent(variable, dat, frame) {
    const binding = binding_in_frame(variable, frame);
    return binding !== undefined
        ? extend(variable, dat, frame)
        : pattern_match(binding_value(binding), dat, frame);
}
```

如果框架中没有变量的绑定，我们只需将变量的绑定添加到数据。 否则，我们在框架中将数据与框架中变量的值进行匹配。 如果所存储的值仅包含常量（如果它是通过`extend_if_consistent`在模式匹配期间存储的，则必须这样做），则匹配仅测试存储的值和新值是否相同。 如果是这样，它将返回未修改的帧；否则，返回0。 如果不是，则返回失败指示。 但是，如果存储的值是在统一期间存储的，则可以包含模式变量（请参见4.4.4.4节）。 存储的模式与新数据的递归匹配将添加或检查该模式中变量的绑定。 例如，假设我们有一个框架，其中`x`绑定到`list("f", y)`，而`y`未绑定，我们希望通过将`x`绑定到`list("f", "b")`来增强该框架。 我们查找`x`并发现它绑定到`list("f", y)`。 这导致我们在同一帧中将`list("f", y)`与建议的新值`list("f", "b")`相匹配。 最终，此匹配通过向`"b"`添加`y`的绑定来扩展帧。 变量`x`仍然绑定到`list("f", y)`。 我们从不修改存储的绑定，对于给定的变量，我们从不存储多个绑定。

`extend_if_consistent`用于操纵绑定的功能在第4.4.4.8节中定义。

# 4.4.4.4规则与统一

函数`apply_rules`是`find_assertions`的规则类似物（第4.4.4.3节）。 它以模式和框架为输入，并通过应用数据库中的规则形成扩展框架流。 函数`stream_flatmap`将`apply_a_rule`映射到可能适用的规则（由`fetch_rules`的4.4.4.5节选择）的下游，并合并生成的帧流。

```js
function apply_rules(pattern, frame) {	
    return stream_flatmap(
              rule => 
                 apply_a_rule(rule, pattern, frame),
              fetch_rules(pattern, frame));
}
```

函数`apply_a_rule`使用 [4.4.2](91) 部分中概述的方法应用规则。 它首先通过将规则结论与给定框架中的模式统一来扩充其论证框架。 如果成功，它将在此新框架中评估规则主体。

但是，在发生任何这种情况之前，程序将使用唯一的新名称重命名规则中的所有变量。 这样做的原因是为了防止不同规则应用程序的变量彼此混淆。 例如，如果两个规则都使用名为`x`的变量，则每个规则都可以在应用框架时为`x`添加绑定。 这两个`x`彼此无关，我们不应愚弄以为两个绑定必须一致。 与其重命名变量，不如设计一个更聪明的环境结构。 但是，即使不是最有效的方法，我们在这里选择的重命名方法也是最简单的方法。 （参见练习 [4.70](93#ex_4.70) 。）这是`apply_a_rule`功能：

```js
function apply_a_rule(rule, query_pattern, query_frame) {
    const clean_rule = rename_variables_in(rule);
    const unify_result = 
             unify_match(query_pattern,
                         conclusion(clean_rule),
                         query_frame);
    return unify_result === "failed"
        ? null
        : qeval(rule_body(clean_rule),
                singleton_stream(unify_result));
}
```

提取规则部分的选择器`rule_body`和`conclusion`在4.4.4.7节中定义。

我们通过将唯一标识符（例如数字）与每个规则应用程序关联并将此标识符与原始变量名称组合来生成唯一变量名称。 例如，如果规则应用标识符为7，则可以将规则中的每个`x`更改为`x_7`，将规则中的每个`y`更改为`y_7`。 （函数`make_new_variable`和`new_rule_application_id`包含在4.4.4.7节的语法函数中。）

```js
function rename_variables_in(rule) {
    const rule_application_id = new_rule_application_id();
    function tree_walk(exp) {
        return is_var(exp) 
            ? make_new_variable(exp, rule_application_id)
            : is_pair(exp)
            ? pair(tree_walk(head(exp)),
                   tree_walk(tail(exp)))
            : exp;
    }
    tree_walk(rule);
}
```

统一算法实现为一个函数，该函数以两个模式和一个帧作为输入，并返回扩展帧或字符串`"failed"`。 统一器类似于模式匹配器，但它是对称的-匹配的两侧都允许有变量。 函数`unify_match`与`pattern_match`基本相同，除了有额外的代码（以下标记为 &lt;quote&gt;`***`&lt;/quote&gt; ）来处理比赛右侧对象是变量的情况 。

```js
function unify_match(p1, p2, frame) {
    return frame === "failed"
        ? "failed"
        : equal(p1, p2)
        ? frame
        : is_var(p1) 
        ? extend_if_possible(p1, p2, frame)
        : is_var(p2) 
        ? extend_if_possible(p2, p1, frame)  // ***
        : pair(p1) && pair(p2)
        ? unify_match(tail(p1),
                      tail(p2),
                      unify_match(head(p1),
                                  head(p2),
                                  frame))
        : "failed";
}
```

在统一方面，就像在单面模式匹配中一样，我们仅在框架与现有绑定一致的情况下才接受框架的扩展。 统一使用的功能`extend_if_possible`与模式匹配中使用的`extend_if_consistent`相同，区别在于以下程序中标记为 &lt;quote&gt;`***`&lt;/quote&gt; 的两个特殊检查。 在第一种情况下，如果我们尝试匹配的变量未绑定，但是我们尝试与之匹配的值本身就是一个（不同的）变量，则需要检查该值是否绑定，以及是否 因此，以匹配其值。 如果比赛双方均无约束力，我们可能会彼此绑定。

第二次检查处理将变量绑定到包含该变量的模式的尝试。 每当在两种模式中重复变量时，都可能发生这种情况。 考虑例如在未绑定`x`和`y`的帧中统一两个模式`list(x, x)`和`list(y,` ![%5Ctextit%7Bexpression%20involving%7D](img/c0c6e86701ac9c76c8aae52c7e942ea7.jpg) `y)`。 首先将`x`与`y`匹配，使`x`与`y`结合。 接下来，将相同的`x`与涉及`y`的给定表达式匹配。 由于`x`已绑定到`y`，因此这导致`y`与该表达式匹配。 如果我们认为统一器是为使模式相同的模式变量找到一组值，则这些模式暗含指令以查找`y`从而使`y`等于![%5Ctextit%7Bexpression%20involving%7D](img/c0c6e86701ac9c76c8aae52c7e942ea7.jpg) `y`。 没有通用的方法来求解此类方程，因此我们拒绝此类绑定。 谓词`depends_on`可识别这些情况。 [[1]](93#footnote-1) 另一方面，我们不想拒绝将变量绑定到自身的尝试。 例如，考虑统一`list(x, x)`和`list(y, y)`。 将`x`绑定到`y`的第二次尝试将`y`（`x`的存储值）与`y`（`x`的新值）匹配。 这由`unify_match`的`equal`子句解决。

```js
function extend_if_possible(variable, val, frame) {
    const binding = binding_in_frame(variable, frame);
    if (binding !== undefined) {
        return unify_match(binding_value(binding), 
                            val, frame); 
    } else if (is_var(val)) {                      // ***
        const binding = binding_in_frame(val, frame);
        return binding !== undefined
            ? unify_match(variable,
                          binding_value(binding),
			  frame)
            : extend(variable, val, frame);
    } else if (depends_on(val, variable, frame)) { // ***
        return "failed";
    } else {
        return extend(variable, val, frame);
    }
}
```

函数`depends_on`是谓词，用于测试提议作为模式变量值的表达式是否取决于该变量。 这必须相对于当前帧来完成，因为表达式可能包含变量的出现，该变量已经具有取决于我们的测试变量的值。 `depends_on`的结构是一个简单的递归树遍历，在必要时我们在其中替换变量的值。

```js
function depends_on(exp, variable, frame) {
    function tree_walk(e) {
        if (is_var(e)) {
            if (equal(variable, e)) {
                return true;
            } else {
                const b = binding_in_frame(e, frame);
                if (b !== undefined) {
                    return tree_walk(binding_value(b));
                } else {
                    return false;
                }
            }
	} else if (is_pair(e)) {
            return tree_walk(head(e)) ||
                   tree_walk(tail(e));
        } else {
            return false;
        }
    }
    return tree_walk(exp);
}
```

# 4.4.4.5维护数据库

设计逻辑编程语言时的一个重要问题是安排事物，以便在检查给定模式时将检查尽可能少的不相关的数据库条目。 在我们的系统中，除了将所有断言存储在一个大流中之外，我们还将在所有流中将`head`是常量符号的所有断言存储在单独的流中，并由该符号索引。 为了获取可能与模式匹配的断言，我们首先检查模式的`head`是否为常量符号。 如果是这样，我们返回（将使用匹配器进行测试）所有具有相同`head`的已存储断言。 如果模式的`head`不是常数符号，则返回所有存储的断言。 巧妙的方法也可以利用帧中的信息，或者尝试优化模式的`head`不是恒定符号的情况。 我们避免在程序中建立索引标准（使用`head`，仅处理常量符号的情况）； 相反，我们调用体现我们标准的谓词和选择器。

```js
let THE_ASSERTIONS = null;

function fetch_assertions(pattern, frame) {
    return use_index(pattern)
        ? get_indexed_assertions(pattern)
        : get_all_assertions;
}
function get_all_assertions() {
    return THE_ASSERTIONS;
}
function get_indexed_assertions(pattern) {
    return get_stream(index_key_of(pattern), "assertion-stream");
}
```

函数`get_stream`在表中查找流，如果那里没有存储任何内容，则返回空流。

```js
function get_stream(key1, key2) {	
    const s = get(key1, key2);
    return s !== undefined ? s : null;
}
```

使用规则结论的`head`类似地存储规则。 规则结论是任意模式，但是，它们与断言的不同之处在于它们可以包含变量。 `head`是常数符号的模式可以匹配结论以变量开头的规则，以及结论具有相同`head`的规则。 因此，当获取可能与`head`是常数符号的模式匹配的规则时，我们将获取其结论以变量开头的所有规则以及其结论与该模式具有相同`head`的规则。 为此，我们将其结论以变量开头的所有规则存储在表的单独流中，并由字符串`"variables"`索引。

```js
let THE_RULES = null;

function fetch_rules(pattern, frame) {
    return use_index(pattern)
        ? get_indexed_rules(pattern)
        : get_all_rules();
}
function get_all_rules() {
    return THE_RULES;
}
function get_indexed_rules(pattern) {
    return stream_append(
              get_stream(index_key_of(pattern),
                         "rule-stream"),
	      get_stream("variables", "rule-stream"));
}
```

`query_driver_loop`使用功能`add_rule_or_assertion`将断言和规则添加到数据库中。 每一项都存储在索引中（如果适用）以及数据库中所有断言或规则的流中。

```js
function add_rule_or_assertion(assertion) {
    return is_rule(assertion) 
        ? add_rule(assertion)
        : add_assertion(assertion);
}
function add_assertion(assertion) {
    store_assertion_in_index(assertion);
    const old_assertions = THE_ASSERTIONS;
    THE_ASSERTIONS = pair(assertion, () => old_assertions);
    return "ok";
}
function add_rule(rule) {
    store_rule_in_index(rule);
    const old_rules = THE_RULES;
    THE_RULES = pair(rule, () => old_rules);
    return "ok";
}
```

为了实际存储断言或规则，我们检查是否可以建立索引。 如果是这样，我们会将其存储在适当的流中。

```js
function store_assertion_in_index(assertion) {
    if (is_indexable(assertion)) {
        const key = index_key_of(assertion);
        const current_assertion_stream =
                    get_stream(key, "assertion-stream");
        put(key, "assertion-stream",
            pair(assertion, () => current_assertion_stream));
    } else {
    }
}
function store_rule_in_index(rule) {
    const pattern = conclusion(rule);
    if (is_indexable(pattern)) {
        const key = index_key_of(pattern);
        const current_rule_stream =
                    get_stream(key, "rule-stream");
        put(key, "rule-stream",
            pair(rule, () => current_rule_stream));
    } else {
    }
}
```

以下函数定义如何使用数据库索引。 如果模式（断言或规则结论）以变量或字符串开头，则将存储在表中。

```js
function indexable(pat) {	
    return is_string(pat) || is_var(head(pat));
}
```

在表中存储模式的键是`"variables"`（如果以变量开头）或以其开头的字符串。

```js
function index_key_of(pat) {	
    const key = head(pat);
    return is_var(key) ? "variables" : key;
}
```

如果模式以字符串开头，则索引将用于检索可能与模式匹配的项目。

```js
function use_index(pat) {	
    return is_string(head(pat));
}
```

<exercise>What is the purpose of the constant declarations in the functions `add_assertion` and `add_rule`? What would be wrong with the following implementation of `add_assertion`? Hint: Recall the definition of the infinite stream of ones in section <ref name="sec:infinite-streams">[3.5.2](68)</ref>: `const ones = pair(1, () => ones);`.

```js
function add_assertion(assertion) {
    store_assertion_in_index(assertion);
    THE_ASSERTIONS = pair(assertion, () => THE_ASSERTIONS);
    return "ok";
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

# 4.4.4.6流操作

查询系统使用了第3章中未介绍的一些流操作。

函数`stream_append_delayed`和`interleave_delayed`类似于`stream_append`和`interleave`（ [3.5.3](69)[3.5.4](70)[4.62](93#ex_4.62) ）。

```js
function stream_append_delayed(s1, delayed_s2) {
    return is_null(s1)
        ? delayed_s2()
        : pair(head(s1),
               () => stream_append_delayed(stream_tail(s1),
      	                                   delayed_s2));
}
function interleave_delayed(s1, delayed_s2) {
    return is_null(s1)
        ? delayed_s2()
        : pair(head(s1),
               () => interleave_delayed(delayed_s2(),
                         () => stream_tail(s1)));
}
```

`stream_flatmap`函数在整个查询评估器中用于在一个帧流上映射一个函数并组合所得的帧流，它是函数的流类似物，该函数在部分[ [2.2.3](32)[4.63](93#ex_4.63)[4.64](93#ex_4.64)

```js
function stream_flatmap(fun, s) {
    return flatten_stream(stream_map(fun, s));
}
function flatten_stream(stream) {
    return is_null(stream)
        ? null
        : interleave_delayed(head(stream),
              () => flatten_stream(stream_tail(stream)));
}
```

评估器还使用以下简单函数来生成由单个元素组成的流：

```js
function singleton_stream(x) {	
    pair(x, () => null);
}
```

# 4.4.4.7查询语法功能

`qeval`（第4.4.4.2节）使用的函数`type`和`contents`指定查询表达式由其运算符的名称标识。

```js
function type(exp) {
    return is_application(exp) && is_name(operator(exp))
        ? name_of_name(operator(exp))
        : error(exp, "Unknown expression TYPE");
}
function contents(exp) {
    return is_application(exp) && is_name(operator(exp))
        ? operands(exp)
        : error(exp, "Unknown expression CONTENTS");
}
```

`query_driver_loop`（在4.4.4.1节中）使用的以下函数指定通过`assert(` ![%5Ctextit%7Brule-or-assertion%7D](img/d8c04cce43af16a4d677b8c93817c0da.jpg) `)`形式的表达式将规则和断言添加到数据库中：

```js
function assertion_to_be_added(exp) {
    return type(exp) === "assert";
}
function add_assertion_body(exp) {
    return head(contents(exp));
}
```

以下是`and`，`or`，`not`和`javascript_value`查询表达式的语法定义（第4.4.4.2节）：

```js
function empty_conjunction(exps) {
    return is_null(exps);
}
function first_conjunct(exps) {
    return head(exps);
}
function rest_conjuncts(exps) {
    return tail(exps);
}
function is_empty_disjunction(exps) {
    return is_null(exps);
}
function first_disjunct(exps) {
    return head(exps);
}
function rest_disjuncts(exps) {
    return tail(exps);
}
function negated_query(exps) {
    return head(exps);
}
function predicate(exps) {
    return head(exps);
}
function args(exps) {
    return tail(exps);
}
```

以下三个函数定义规则的语法：

```js
function is_rule(statement) {
    return is_application(statement) &&  
           is_name(operator(statement)) &&
           name_of_name(operator(statement)) === "rule";
}
function conclusion(rule) {
    return head(operands(rule));
}
function rule_body(rule) {
    return is_null(tail(operands(rule)))
        ? "always_true"
        : tail(operands(rule));
}
```

函数`query_driver_loop`（第4.4.4.1节）调用`query_syntax_process`将表达式中的模式变量（即名称）转换为内部格式`list("?", name)`。 也就是说，诸如`list(x, y)`之类的模式实际上由系统内部表示为`list(list("?", "x"), list("?", "y"))`。 语法转换通过以下功能完成：

```js
function query_syntax_process(exp) {
    return map_over_names(expand_question_mark, exp);
}
function map_over_names(fun, exp) {
    return is_name(exp) 
        ? fun(exp)
        : is_pair(exp)
        ? pair(map_over_names(fun, head(exp)),
               map_over_names(fun, tail(exp)))
        : exp;
}
function expand_question_mark(name) {
    return list("?", name_of_name(name));
}
```

一旦以这种方式转换了变量，模式中的变量就是以`"?"`开头的列表，而字符串（对于数据库索引需要识别的字符串，第4.4.4.5节）仅是字符串。

```js
function is_var(exp) {	
    return is_tagged_list(exp, "?");
}
function is_constant_string(exp) {
    return is_string(exp);
}
```

在规则应用过程中（第4.4.4.4节），通过以下功能构造唯一变量。 规则应用程序的唯一标识符是一个数字，该数字在每次应用规则时都会增加。

```js
let rule_counter = 0;

function new_rule_application_id() {
    rule_counter = rule_counter + 1;
    return rule_counter;
}
function make_new_variable(variable, rule_application_id) {
    return list("?", tail(variable) + stringify(rule_application_id));
}
```

当`query_driver_loop`实例化查询以打印答案时，它会使用

```js
function contract_question_mark(variable) {
    return list("name", tail(variable));
}
```

# 4.4.4.8框架和绑定

框架表示为绑定列表，这些列表是变量值对：

```js
function make_binding(variable, value) {
    return pair(variable, value);
}
function binding_variable(binding) {
    return head(binding);
}
function binding_value(binding) {
    return tail(binding);
}
function binding_in_frame(variable, frame) {
    return assoc(variable, frame);
}
function extend(variable, value, frame) {
    return pair(make_binding(variable, value), frame);
}
```

<exercise>Louis Reasoner wonders why the `simple_query` and `disjoin` functions (section 4.4.4.2) are implemented using explicit delay operations, rather than being defined as follows:

```js
function simple_query(query_pattern, frame_stream) {
    return stream_flatmap(
              frame =>
                 stream_append(find_assertions(query_pattern, frame),
                               apply_rules(query_pattern, frame)),
              frame_stream);
}
function disjoin(disjuncts, frame_stream) {
    return is_empty_disjunction(disjuncts)
        ? null
        : interleave(qeval(first_disjunct(disjuncts), frame_stream),
                     disjoin(rest_disjuncts(disjuncts), frame_stream));
}
```

Can you give examples of queries where these simpler definitions would lead to undesirable behavior?<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Why do `disjoin` and `stream_flatmap` interleave the streams rather than simply append them? Give examples that illustrate why interleaving works better. (Hint: Why did we use `interleave` in section <ref name="sec:exploiting-streams">[3.5.3](69)</ref>?)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Why does `flatten_stream` use a function definition expression in its body? What would be wrong with defining it as follows:

```js
function flatten_stream(stream) {
    return is_null(stream)
        ? null
        : interleave(head(stream),
                     flatten_stream(stream_tail(stream)));
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Alyssa P. Hacker proposes to use a simpler version of `stream_flatmap` in `negate`, `javascript_value`, and `find_assertions`. She observes that the function that is mapped over the frame stream in these cases always produces either the empty stream or a singleton stream, so no interleaving is needed when combining these streams.

1.  填写Alyssa程序中缺少的表达式。

    ```js
    function simple_stream_flatmap(fun, s) {
        return simple_flatten(stream_map(fun, s));
    }
    function simple_flatten(stream) {
        return stream_map(...,
                          stream_filter(..., stream));
    }
    ```

2.  如果我们以此方式更改查询系统的行为，是否会发生变化？

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Implement for the query language a query expression called `unique`. `Unique` should succeed if there is precisely one item in the data base satisfying a specified query. For example,

```js
unique(job(x, list("computer", "wizard")));
```

should print the one-item stream

```js
unique(job(list("Bitdiddle", "Ben"), list("computer", "wizard")))
```

since Ben is the only computer wizard, and

```js
unique(job(x, list("computer", "programmer")));
```

should print the empty stream, since there is more than one computer programmer. Moreover,

```js
and(job(x, j), unique(job(anyone, j)));
```

should list all the jobs that are filled by only one person, and the people who fill them. There are two parts to implementing `unique`. The first is to write a function that handles this special form, and the second is to make `qeval` dispatch to that function. The second part is trivial, since `qeval` does its dispatching in a data-directed way. If your function is called `uniquely_asserted`, all you need to do is

```js
put("unique", "qeval", uniquely_asserted);
```

and `qeval` will dispatch to this function for every query whose `type` (`head`) is the nam `unique`. The real problem is to write the function `uniquely_asserted`. This should take as input the `contents` (`tail`) of the `unique` query, together with a stream of frames. For each frame in the stream, it should use `qeval` to find the stream of all extensions to the frame that satisfy the given query. Any stream that does not have exactly one item in it should be eliminated. The remaining streams should be passed back to be accumulated into one big stream that is the result of the `unique` query. This is similar to the implementation of the `not` special form. Test your implementation by forming a query that lists all people who supervise precisely one person.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Our implementation of `and` as a series combination of queries (figure <ref name="fig:query-and">[4.5](91#fig_4.5)</ref>) is elegant, but it is inefficient because in processing the second query of the `and` we must scan the data base for each frame produced by the first query. If the data base has ![N](img/9341d9048ac485106d2b2ee8de14876f.jpg) elements, and a typical query produces a number of output frames proportional to ![N](img/9341d9048ac485106d2b2ee8de14876f.jpg) (say ![N%2Fk](img/4902151f33393b8696b1db2cfaa2924f.jpg)), then scanning the data base for each frame produced by the first query will require ![N%5E%7B2%7D%2Fk](img/272fe88ad7a25e0ad784e944de44f193.jpg) calls to the pattern matcher. Another approach would be to process the two clauses of the `and` separately, then look for all pairs of output frames that are compatible. If each query produces ![N%2Fk](img/4902151f33393b8696b1db2cfaa2924f.jpg) output frames, then this means that we must perform ![N%5E%7B2%7D%2Fk%5E%7B2%7D](img/655f3668223013d1a89fe648431d0558.jpg) compatibility checks—a factor of ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg) fewer than the number of matches required in our current method. Devise an implementation of `and` that uses this strategy. You must implement a function that takes two frames as inputs, checks whether the bindings in the frames are compatible, and, if so, produces a frame that merges the two sets of bindings. This operation is similar to unification.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>In section <ref name="sec:math-logic">[4.4.3](92)</ref> we saw that `not` and `javascript_value` can cause the query language to give <quote>wrong</quote> answers if these filtering operations are applied to frames in which variables are unbound. Devise a way to fix this shortcoming. One idea is to perform the filtering in a <quote>delayed</quote> manner by appending to the frame a <quote>promise</quote> to filter that is fulfilled only when enough variables have been bound to make the operation possible. We could wait to perform filtering until all other operations have been performed. However, for efficiency's sake, we would like to perform filtering as soon as possible so as to cut down on the number of intermediate frames generated.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Redesign the query language as a nondeterministic program to be implemented using the evaluator of section <ref name="sec:nondeterministic-evaluation">[4.3](85)</ref>, rather than as a stream process. In this approach, each query will produce a single answer (rather than the stream of all answers) and the user can type `try-again` to see more answers. You should find that much of the mechanism we built in this section is subsumed by nondeterministic search and backtracking. You will probably also find, however, that your new query language has subtle differences in behavior from the one implemented here. Can you find examples that illustrate this difference?<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>When we implemented the JavaScript evaluator in section <ref name="sec:mc-eval">[4.1](73)</ref>, we saw how to use local environments to avoid name conflicts between the parameters of functions. For example, in evaluating

```js
function square(x) {
    return x * x;
}
function sum_of_squares(x, y) {
    return square(x) + square(y);
}
sum_of_squares(3, 4);
```

there is no confusion between the `x` in `square` and the `x` in `sum_of_squares`, because we evaluate the body of each function in an environment that is specially constructed to contain bindings for the local variables. In the query system, we used a different strategy to avoid name conflicts in applying rules. Each time we apply a rule we rename the variables with new names that are guaranteed to be unique. The analogous strategy for the JavaScript evaluator would be to do away with local environments and simply rename the variables in the body of a function each time we apply the function. Implement for the query language a rule-application method that uses environments rather than renaming. See if you can build on your environment structure to create constructs in the query language for dealing with large systems, such as the rule analog of block-structured functions. Can you relate any of this to the problem of making deductions in a context (e.g., <quote>If I supposed that ![P](img/f0481b76bd881625dd55f21f6d81bbc8.jpg) were true, then I would be able to deduce ![A](img/efdb05f076173b39fdd26ef663e7b0d8.jpg) and ![B](img/813135a6280e2672503128d3d2080d4a.jpg).</quote>) as a method of problem solving? (This problem is open-ended. A good answer is probably worth a Ph.D.)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_93_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

* * *

[[1]](93#footnote-link-1) In general, unifying `y` with an expression involving `y` would require our being able to find a fixed point of the equation `y` ![%3D](img/f7ef8d7b340ed17969bfbde43f695928.jpg) ![%5Ctextit%7Bexpression%20involving%7D](img/c0c6e86701ac9c76c8aae52c7e942ea7.jpg)`y`. It is sometimes possible to syntactically form an expression that appears to be the solution. For example, `y` ![%3D](img/f7ef8d7b340ed17969bfbde43f695928.jpg) `list("f", y)` seems to have the fixed point `list("f", list("f", list("f",`…`)))`, which we can produce by beginning with the expression `list("f", y)` and repeatedly substituting `list("f", y)` for `y`. Unfortunately, not every such equation has a meaningful fixed point. The issues that arise here are similar to the issues of manipulating infinite series in mathematics. For example, we know that 2 is the solution to the equation ![y%20%3D%201%20%2B%20y%2F2](img/377db99df0abe41a559d438a9a493581.jpg). Beginning with the expression ![1%20%2B%20y%2F2](img/b2a85710e354a0b472aa4ac72f52f368.jpg) and repeatedly substituting ![1%0A%20%20%20%20%20%20%20%20%2B%20y%2F2%3Cimg%20alt%3D%22%2520for%2520%22%20src%3D%22https%3A%2F%2Fwww.zhihu.com%2Fequation%3Ftex%3D%2520for%2520%22%20%2F%3Ey](img/5f6b1ac3ed46e402afe4b1ed81f4abf3.jpg) gives ![%202%20%3D%20y%20%3D%201%20%2B%20y%2F2%20%3D%201%20%2B%20%281%2By%2F2%29%2F2%20%3D%201%20%2B%201%2F2%20%2B%20y%2F4%20%3D%5Ccdots%2C%20](img/8afe2f536becea85acc0ceda7e02ac66.jpg) which leads to ![%202%20%3D%201%20%2B%201%2F2%20%2B%201%2F4%20%2B%201%2F8%20%2B%5Ccdots.%20](img/ff29c877d8c211095e929e6623d43479.jpg) However, if we try the same manipulation beginning with the observation that ![-1](img/0b01bfd88fd7b0fbfc4646014a6e880e.jpg) is the solution to the equation ![y%20%3D%201%20%2B%202y](img/720b26f9276cc33d7bacf3b84c5555f3.jpg), we obtain ![%20-1%20%3D%20y%20%3D%201%20%2B%202y%20%3D%201%20%2B%202%281%20%2B%202y%29%20%3D%201%20%2B%202%20%2B%204y%20%3D%20%5Ccdots%2C%20](img/5b7fe2a8537846bb058fa4660aba0b52.jpg) which leads to ![%20-1%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%5Ccdots.%20](img/c77ff16f88f2fab469aaff3b1d64262d.jpg) Although the formal manipulations used in deriving these two equations are identical, the first result is a valid assertion about infinite series but the second is not. Similarly, for our unification results, reasoning with an arbitrary syntactically constructed expression may lead to errors.

