Có vẻ như bạn đã quen thuộc với một số nhược điểm và lợi thế.
Một số người khác: a) Làm cho nó có thể để hỗ trợ thích hợp tối ưu hóa cuộc gọi đuôi ngay cả khi thực hiện cơ bản không có bất kỳ sự hỗ trợ cho nó b) dễ dàng hơn để xây dựng những thứ như một trình độ ngoại ngữ "stack trace" c) dễ dàng hơn để thêm các tiếp tục thích hợp, như bạn đã chỉ ra
Gần đây tôi đã viết một trình thông dịch "Đề án" đơn giản trong C#, ban đầu đã sử dụng chồng .NET. Sau đó tôi lại viết nó sử dụng một ngăn xếp rõ ràng - vì vậy có lẽ sau đây sẽ giúp bạn:
Phiên bản đầu tiên sử dụng ngăn xếp .NET runtime ngầm ...
Ban đầu, nó chỉ là một hệ thống phân cấp lớp, với các hình thức khác nhau (Lambda, Lết, vv) được triển khai của các giao diện sau:
// A "form" is an expression that can be evaluted with
// respect to an environment
// e.g.
// "(* x 3)"
// "x"
// "3"
public interface IForm
{
object Evaluate(IEnvironment environment);
}
IEnvironment trông như bạn mong muốn:
/// <summary>
/// Fundamental interface for resolving "symbols" subject to scoping.
/// </summary>
public interface IEnvironment
{
object Lookup(string name);
IEnvironment Extend(string name, object value);
}
Để thêm " nội dung dựng sẵn "cho trình thông dịch Scheme của tôi, ban đầu tôi có giao diện sau:
/// <summary>
/// A function is either a builtin function (i.e. implemented directly in CSharp)
/// or something that's been created by the Lambda form.
/// </summary>
public interface IFunction
{
object Invoke(object[] args);
}
Đó là khi sử dụng ngăn xếp .NET runtime ngầm định. Chắc chắn có ít mã hơn, nhưng không thể thêm những thứ như đệ quy đuôi thích hợp, và quan trọng nhất, nó là vụng về thông dịch viên của tôi để có thể cung cấp dấu vết ngăn xếp "ngôn ngữ" trong trường hợp lỗi thời gian chạy.
Vì vậy, tôi viết lại nó để có ngăn xếp (phân bổ đống) rõ ràng.
của tôi "IFunction" giao diện đã phải thay đổi để những điều sau đây, để tôi có thể thực hiện những điều như "bản đồ" và "áp dụng", mà gọi trở lại vào phiên dịch Scheme:
/// <summary>
/// A function that wishes to use the thread state to
/// evaluate its arguments. The function should either:
/// a) Push tasks on to threadState.Pending which, when evaluated, will
/// result in the result being placed on to threadState.Results
/// b) Push its result directly on to threadState.Results
/// </summary>
public interface IStackFunction
{
void Evaluate(IThreadState threadState, object[] args);
}
Và IForm đổi thành :
public interface IForm
{
void Evaluate(IEnvironment environment, IThreadState s);
}
đâu IThreadState được như sau:
/// <summary>
/// The state of the interpreter.
/// The implementation of a task which takes some arguments,
/// call them "x" and "y", and which returns an argument "z",
/// should follow the following protocol:
/// a) Call "PopResult" to get x and y
/// b) Either
/// i) push "z" directly onto IThreadState using PushResult OR
/// ii) push a "task" on to the stack which will result in "z" being
/// pushed on to the result stack.
///
/// Note that ii) is "recursive" in its definition - that is, a task
/// that is pushed on to the task stack may in turn push other tasks
/// on the task stack which, when evaluated,
/// ... ultimately will end up pushing the result via PushResult.
/// </summary>
public interface IThreadState
{
void PushTask(ITask task);
object PopResult();
void PushResult(object result);
}
Và ITask là:
public interface ITask
{
void Execute(IThreadState s);
}
Và "sự kiện" chính của tôi vòng lặp là:
ThreadState threadState = new ThreadState();
threadState.PushTask(null);
threadState.PushTask(new EvaluateForm(f, environment));
ITask next = null;
while ((next = threadState.PopTask()) != null)
next.Execute(threadState);
return threadState.PopResult(); // Get what EvaluateForm evaluated to
EvaluateForm chỉ là một nhiệm vụ mà các cuộc gọi IForm.Evaluate với một môi trường cụ thể.
Cá nhân, tôi thấy phiên bản mới này "đẹp hơn" để làm việc với quan điểm thực hiện - dễ dàng có được dấu vết ngăn xếp, dễ dàng thực hiện tiếp tục đầy đủ (mặc dù ... tôi chưa làm điều này như được nêu ra - cần phải làm cho "ngăn xếp" liên tục của tôi-danh sách liên kết chứ không phải bằng cách sử dụng C# ngăn xếp, và ITask "trả về" mới ThreadState hơn là đột biến nó để tôi có thể có một "gọi tiếp tục" nhiệm vụ) ... vv v.v.
Về cơ bản, bạn chỉ ít phụ thuộc vào việc triển khai ngôn ngữ cơ bản.
Về nhược điểm duy nhất tôi có thể tìm thấy là hiệu suất ... Nhưng trong trường hợp của tôi, nó chỉ là một thông dịch viên vì vậy tôi không quan tâm nhiều về hiệu suất anyway.
Tôi cũng muốn chỉ cho bạn bài viết rất đẹp này về lợi ích của tái viết code đệ quy như mã lặp đi lặp lại với một chồng, bởi một trong những tác giả của KAI C++: Considering Recursion
Thực ra, câu hỏi là về những nhược điểm chỉ xem xét tích hợp mã gốc. Nhưng cảm ơn câu chuyện. –