Skip to content

pscm_cc Values 实现分析

概述

本文档分析 pscm_cc 中 values 和 call-with-values 的实现方式,对比 Guile 1.8 的实现,并讨论其优劣以及改进方向。

pscm_cc 的实现

核心函数

1. scm_c_values - values 函数

c
SCM *scm_c_values(SCM_List *args) {
  // Return the arguments as a list
  if (!args) {
    return scm_nil();
  }
  // args is a list where each node's data is an argument
  // We need to extract the data from each node to build the result list
  SCM_List dummy = make_list_dummy();
  SCM_List *tail = &dummy;
  SCM_List *current = args;
  while (current) {
    tail->next = make_list(current->data);
    tail = tail->next;
    current = current->next;
  }
  return dummy.next ? wrap(dummy.next) : scm_nil();
}

实现特点:

  • 将所有参数打包成一个列表返回
  • 如果没有参数,返回 nil
  • 直接使用列表结构,没有特殊的 values 对象

2. eval_call_with_values - call-with-values 特殊形式

c
SCM *eval_call_with_values(SCM_Environment *env, SCM_List *l) {
  // 1. 验证参数
  if (!l->next || !l->next->next) {
    eval_error("call-with-values: requires 2 arguments (producer and consumer)");
    return nullptr;
  }
  
  // 2. 求值 producer
  SCM *producer = eval_with_env(env, l->next->data);
  if (!is_proc(producer) && !is_func(producer)) {
    eval_error("call-with-values: first argument must be a procedure");
    return nullptr;
  }
  
  // 3. 调用 producer 获取值
  SCM *values_result;
  if (is_proc(producer)) {
    SCM_Procedure *proc = cast<SCM_Procedure>(producer);
    values_result = apply_procedure(env, proc, nullptr);
  } else {
    SCM_Function *func = cast<SCM_Function>(producer);
    SCM_List func_call;
    func_call.data = producer;
    func_call.next = nullptr;
    values_result = eval_with_func(func, &func_call);
  }
  
  // 4. 处理返回值:期望是一个列表
  SCM_List *values_list;
  if (is_pair(values_result)) {
    values_list = cast<SCM_List>(values_result);
  } else if (is_nil(values_result)) {
    values_list = nullptr;
  } else {
    // 单值情况:包装成列表
    values_list = make_list(values_result);
  }
  
  // 5. 求值 consumer
  SCM *consumer = eval_with_env(env, l->next->next->data);
  if (!is_proc(consumer) && !is_func(consumer)) {
    eval_error("call-with-values: second argument must be a procedure");
    return nullptr;
  }
  
  // 6. 将值作为参数传递给 consumer
  if (is_proc(consumer)) {
    SCM_Procedure *proc = cast<SCM_Procedure>(consumer);
    return apply_procedure(env, proc, values_list);
  } else {
    SCM_Function *func = cast<SCM_Function>(consumer);
    SCM_List func_call;
    func_call.data = consumer;
    func_call.next = values_list;
    return eval_with_func(func, &func_call);
  }
}

实现特点:

  • 调用 producer 获取返回值
  • 如果返回值是列表,直接使用
  • 如果返回值是单值,包装成单元素列表
  • 将值列表作为参数传递给 consumer

优势

  1. 实现简单:直接使用列表,不需要特殊的 values 对象
  2. 易于理解:逻辑清晰,没有复杂的类型检查
  3. 兼容性好:与 Scheme 的列表操作兼容

劣势

  1. 无法区分多值和列表:无法区分 (values 1 2 3)(list 1 2 3)
  2. 语义不准确:不符合 R5RS/R6RS 规范,values 应该返回特殊的多值对象
  3. 性能开销:总是创建列表,即使只有一个值
  4. 类型检查缺失:没有检查 producer 是否真的返回了 values 的结果
  5. 单值处理不一致:单值需要包装成列表,增加了开销

Guile 1.8 的实现

核心机制

1. Values 对象(Struct)

Guile 使用特殊的 struct 对象来表示多值:

c
// values 对象是一个 struct,包含一个列表
SCM scm_values_vtable;  // values 对象的 vtable

#define SCM_VALUESP(x) (SCM_STRUCTP (x)\
                        && scm_is_eq (scm_struct_vtable (x), scm_values_vtable))

2. scm_values 函数

c
SCM_DEFINE (scm_values, "values", 0, 0, 1, (SCM args),
            "Delivers all of its arguments to its continuation...")
{
  long n;
  SCM result;

  SCM_VALIDATE_LIST_COPYLEN (1, args, n);
  if (n == 1)
    result = SCM_CAR (args);  // 单值:直接返回
  else
    {
      // 多值:创建 values struct
      result = scm_make_struct (scm_values_vtable, SCM_INUM0,
                                scm_list_1 (args));
    }

  return result;
}

关键特性:

  • 单值优化:如果只有一个参数,直接返回该值(不是 values 对象)
  • 多值封装:如果有多个参数,创建 values struct 对象
  • 类型区分:使用 SCM_VALUESP 可以区分 values 对象和普通值

3. call-with-values 处理

c
case (ISYMNUM (SCM_IM_CALL_WITH_VALUES)):
{
  SCM producer;
  
  x = SCM_CDR (x);
  producer = EVALCAR (x, env);
  x = SCM_CDR (x);
  proc = EVALCAR (x, env);  /* proc is the consumer. */
  
  // 调用 producer
  arg1 = SCM_APPLY (producer, SCM_EOL, SCM_EOL);
  
  // 检查是否是 values 对象
  if (SCM_VALUESP (arg1))
    {
      // 提取 values 对象中的列表
      /* The list of arguments is not copied.  Rather, it is assumed
       * that this has been done by the 'values' procedure.  */
      arg1 = scm_struct_ref (arg1, SCM_INUM0);
    }
  else
    {
      // 单值:包装成单元素列表
      arg1 = scm_list_1 (arg1);
    }
  
  // 将值列表作为参数传递给 consumer
  PREP_APPLY (proc, arg1);
  goto apply_proc;
}

关键特性:

  • 类型检查:使用 SCM_VALUESP 检查是否是 values 对象
  • 单值优化:单值直接返回,不需要创建 values 对象
  • 多值提取:从 values struct 中提取列表
  • 性能优化:不复制列表,直接使用 values 对象中的列表

Guile 1.8 的优势

  1. 语义正确:符合 R5RS/R6RS 规范
  2. 类型区分:可以区分多值和单值
  3. 性能优化:单值不需要创建对象
  4. 内存效率:不复制列表,直接引用
  5. 类型安全:使用类型检查确保正确性

Guile 1.8 的劣势

  1. 实现复杂:需要特殊的 struct 类型
  2. 类型检查开销:需要检查是否是 values 对象
  3. 内存管理:需要管理 values struct 对象

对比总结

特性pscm_ccGuile 1.8
多值表示列表Values struct 对象
单值处理包装成列表直接返回值
类型区分❌ 无法区分✅ 可以区分
语义正确性❌ 不符合规范✅ 符合规范
性能较低(总是创建列表)较高(单值优化)
实现复杂度简单复杂
内存效率较低较高
类型安全较低较高

关键差异

1. 多值表示方式

pscm_cc:

scheme
(values 1 2 3)  ; => (1 2 3)  ; 普通列表
(list 1 2 3)    ; => (1 2 3)  ; 普通列表
; 无法区分!

Guile 1.8:

scheme
(values 1 2 3)  ; => #<values (1 2 3)>  ; values 对象
(list 1 2 3)   ; => (1 2 3)            ; 普通列表
; 可以区分!

2. 单值处理

pscm_cc:

c
// 总是返回列表
(values 42)  ; => (42)  ; 单元素列表

Guile 1.8:

c
// 单值直接返回
(values 42)  ; => 42  ; 直接返回值,不是 values 对象

3. call-with-values 行为

pscm_cc:

c
// 期望 producer 返回列表
// 无法区分是 values 返回还是普通列表返回

Guile 1.8:

c
// 检查是否是 values 对象
// 如果是,提取列表;如果不是,包装成列表

pscm_cc 的改进方向

1. 短期改进(高优先级)

1.1 实现 Values 对象类型

问题:无法区分多值和普通列表。

改进:添加 values 对象类型。

c
// 在 pscm.h 中添加
struct SCM_Values {
  SCM_List *values_list;  // 值的列表
};

// 添加类型检查
inline bool is_values(SCM *data) {
  return data && data->type == SCM::VALUES;
}

// 修改 scm_c_values
SCM *scm_c_values(SCM_List *args) {
  if (!args) {
    return scm_nil();
  }
  
  // 计算参数数量
  int count = 0;
  SCM_List *current = args;
  while (current) {
    count++;
    current = current->next;
  }
  
  // 单值优化:直接返回
  if (count == 1) {
    return args->data;
  }
  
  // 多值:创建 values 对象
  SCM_Values *values_obj = new SCM_Values();
  // 构建列表(与当前实现相同)
  SCM_List dummy = make_list_dummy();
  SCM_List *tail = &dummy;
  current = args;
  while (current) {
    tail->next = make_list(current->data);
    tail = tail->next;
    current = current->next;
  }
  values_obj->values_list = dummy.next;
  
  SCM *result = wrap(values_obj);
  result->type = SCM::VALUES;
  return result;
}

1.2 修改 call-with-values 处理

问题:无法正确识别 values 对象。

改进:检查并提取 values 对象。

c
SCM *eval_call_with_values(SCM_Environment *env, SCM_List *l) {
  // ... 前面的代码相同 ...
  
  // 调用 producer 获取值
  SCM *values_result = /* ... */;
  
  // 检查是否是 values 对象
  SCM_List *values_list;
  if (is_values(values_result)) {
    // 提取 values 对象中的列表
    SCM_Values *values_obj = cast<SCM_Values>(values_result);
    values_list = values_obj->values_list;
  } else if (is_pair(values_result)) {
    // 普通列表:直接使用
    values_list = cast<SCM_List>(values_result);
  } else if (is_nil(values_result)) {
    // 空值
    values_list = nullptr;
  } else {
    // 单值:包装成列表
    values_list = make_list(values_result);
  }
  
  // ... 后面的代码相同 ...
}

1.3 添加打印支持

问题:values 对象打印时应该显示特殊格式。

改进:在 print.cc 中添加 values 对象的打印。

c
// 在 print.cc 中
void print_ast(SCM *ast, ...) {
  if (is_values(ast)) {
    SCM_Values *values_obj = cast<SCM_Values>(ast);
    printf("#<values ");
    print_list(values_obj->values_list);
    printf(">");
    return;
  }
  // ... 其他类型 ...
}

1.4 单值优化

问题:单值也创建列表,浪费内存。

改进:单值直接返回,不创建 values 对象。

c
SCM *scm_c_values(SCM_List *args) {
  if (!args) {
    return scm_nil();
  }
  
  // 检查参数数量
  int count = 0;
  SCM_List *current = args;
  while (current) {
    count++;
    current = current->next;
  }
  
  // 单值优化:直接返回
  if (count == 1) {
    return args->data;
  }
  
  // 多值:创建 values 对象
  // ...
}

2. 中期改进(中优先级)

2.1 优化列表构建

问题:构建列表时效率可以更高。

改进:使用更高效的列表构建方式。

c
SCM *scm_c_values(SCM_List *args) {
  if (!args) {
    return scm_nil();
  }
  
  // 计算参数数量
  int count = 0;
  SCM_List *current = args;
  while (current) {
    count++;
    current = current->next;
  }
  
  // 单值优化
  if (count == 1) {
    return args->data;
  }
  
  // 多值:直接使用 args,不需要重新构建
  // 但需要确保 args 不会被修改
  SCM_Values *values_obj = new SCM_Values();
  values_obj->values_list = args;  // 直接引用
  // 或者复制列表以确保安全
  values_obj->values_list = copy_list(args);
  
  SCM *result = wrap(values_obj);
  result->type = SCM::VALUES;
  return result;
}

2.2 添加类型检查

问题:缺少对 values 对象的类型检查。

改进:添加完善的类型检查。

c
// 检查是否是有效的 values 对象
bool is_valid_values(SCM *data) {
  if (!is_values(data)) {
    return false;
  }
  SCM_Values *values_obj = cast<SCM_Values>(data);
  if (!values_obj || !values_obj->values_list) {
    return false;
  }
  // 检查列表是否有效
  return is_valid_list(values_obj->values_list);
}

// 在 call-with-values 中使用
if (is_values(values_result)) {
  if (!is_valid_values(values_result)) {
    eval_error("call-with-values: invalid values object");
    return nullptr;
  }
  // ...
}

2.3 支持 values 对象的操作

问题:缺少对 values 对象的操作函数。

改进:添加 values 对象的操作函数。

c
// 获取 values 对象的数量
int values_count(SCM *values_obj) {
  if (!is_values(values_obj)) {
    return -1;
  }
  SCM_Values *obj = cast<SCM_Values>(values_obj);
  return list_length(obj->values_list);
}

// 获取 values 对象的第 n 个值
SCM *values_ref(SCM *values_obj, int n) {
  if (!is_values(values_obj)) {
    return nullptr;
  }
  SCM_Values *obj = cast<SCM_Values>(values_obj);
  return list_ref(obj->values_list, n);
}

// 将 values 对象转换为列表
SCM_List *values_to_list(SCM *values_obj) {
  if (!is_values(values_obj)) {
    return nullptr;
  }
  SCM_Values *obj = cast<SCM_Values>(values_obj);
  return copy_list(obj->values_list);  // 返回副本
}

3. 长期改进(低优先级)

3.1 实现值接收机制

问题:当前实现中,continuation 只能接收一个值。

改进:实现真正的多值接收机制。

c
// 修改 continuation 以支持多值
struct SCM_Continuation {
  // ... 现有字段 ...
  bool accepts_multiple_values;  // 是否接受多值
};

// 在 call-with-values 中标记 continuation
SCM *eval_call_with_values(SCM_Environment *env, SCM_List *l) {
  // 创建特殊的 continuation,标记为接受多值
  // ...
}

3.2 优化内存管理

问题:values 对象的内存管理可以优化。

改进

  • 使用对象池
  • 或集成 GC
  • 或使用引用计数
c
// 使用智能指针
class ValuesObject {
  std::shared_ptr<SCM_List> values_list;
public:
  ValuesObject(SCM_List *list) : values_list(list) {}
  SCM_List *get_list() const { return values_list.get(); }
};

3.3 性能优化

问题:可以进一步优化性能。

改进

  • 缓存 values 对象
  • 优化列表操作
  • 使用更高效的数据结构
c
// 使用更高效的数据结构
struct SCM_Values {
  SCM **values_array;    // 值数组
  size_t count;          // 值数量
  // 或者
  SCM_List *values_list; // 列表(当前方式)
};

3.4 错误处理改进

问题:错误处理可以更完善。

改进

  • 添加更详细的错误信息
  • 支持错误恢复
  • 添加调试信息
c
SCM *eval_call_with_values(SCM_Environment *env, SCM_List *l) {
  try {
    // ... 实现 ...
  } catch (const std::exception &e) {
    eval_error("call-with-values: error in producer: %s", e.what());
    return nullptr;
  }
}

实现示例

改进后的 values 实现

c
// 在 pscm.h 中添加
enum Type {
  // ... 现有类型 ...
  VALUES,  // 新增
};

struct SCM_Values {
  SCM_List *values_list;
};

// 修改 scm_c_values
SCM *scm_c_values(SCM_List *args) {
  if (!args) {
    return scm_nil();
  }
  
  // 计算参数数量
  int count = 0;
  SCM_List *current = args;
  while (current) {
    count++;
    current = current->next;
  }
  
  // 单值优化:直接返回
  if (count == 1) {
    return args->data;
  }
  
  // 多值:创建 values 对象
  SCM_Values *values_obj = new SCM_Values();
  
  // 复制列表(确保安全)
  SCM_List dummy = make_list_dummy();
  SCM_List *tail = &dummy;
  current = args;
  while (current) {
    tail->next = make_list(current->data);
    tail = tail->next;
    current = current->next;
  }
  values_obj->values_list = dummy.next;
  
  SCM *result = wrap(values_obj);
  result->type = SCM::VALUES;
  return result;
}

// 修改 eval_call_with_values
SCM *eval_call_with_values(SCM_Environment *env, SCM_List *l) {
  // ... 验证参数 ...
  
  // 调用 producer
  SCM *values_result = /* ... */;
  
  // 处理返回值
  SCM_List *values_list;
  if (is_values(values_result)) {
    // values 对象:提取列表
    SCM_Values *values_obj = cast<SCM_Values>(values_result);
    values_list = values_obj->values_list;
  } else if (is_pair(values_result)) {
    // 普通列表
    values_list = cast<SCM_List>(values_result);
  } else if (is_nil(values_result)) {
    // 空值
    values_list = nullptr;
  } else {
    // 单值:包装成列表
    values_list = make_list(values_result);
  }
  
  // 调用 consumer
  // ...
}

结论

pscm_cc 的 values 实现是一个简化版本,使用列表来表示多值。虽然实现简单,但不符合 Scheme 规范,也无法区分多值和普通列表。

与 Guile 1.8 相比,主要差异在于:

  1. 多值表示:pscm_cc 使用列表,Guile 使用特殊的 values 对象
  2. 单值处理:pscm_cc 总是返回列表,Guile 单值直接返回
  3. 类型区分:pscm_cc 无法区分,Guile 可以区分

建议优先实现:

  1. Values 对象类型(区分多值和列表)
  2. 单值优化(提高性能)
  3. 类型检查(确保正确性)

这些改进将使 pscm_cc 的 values 实现更符合 Scheme 规范,同时保持代码的简洁性。

This website is released under the MIT License.