对帖子的奇怪标题表示道歉,我不确定描述它的最佳方式是什么。
一般问题:
Seq.map(或类似函数)的顺序应用,除了列表中的每个项目外,还传入“上下文”。每次迭代都可以修改这个“上下文”,更新的版本应该传递到列表中的下一个项目。
具体问题:
我正在用F#创建一个编译器。我目前正在进行的步骤是将基于堆栈的IL转换为基于寄存器的IL。我想我可以“走”基于堆栈的IL并携带当前的“eval堆栈”(类似于.NET的eval堆栈)。显然,每个堆栈IL操作码都会改变堆栈(例如:“add”操作码会从堆栈中弹出两个项目并推送结果)。这个更新的堆栈将被传递到下一个操作码的发送周期。
请注意,我对函数式编程(我在一周前就已经了解它)非常陌生,来自C#背景,我的主要问题是“实现这一目的的'功能'方式是什么?”
这是我对这种“功能性”方法的最佳猜测(psudocode)。我不喜欢“transformStackToRegisterIL”的元组返回值,如果我想保持不可变值的标准,它是否需要?另外,我担心过长的IL块会出现堆栈溢出,这是我的一个有效问题吗?
let rec translate evalStack inputIl =
match inputIl with
| singleOpcode :: tail ->
let (transformed, newEvalStack) = transformStackToRegisterIL evalStack singleOpcode
transformed :: translate newEvalStack tail
| [] -> []
编辑:List.scan是否是我想要的内置函数? (它似乎相似,但不完全正确......但它可能是正确的,我不确定)
答案 0 :(得分:3)
我将尝试使用一个非常基本的例子来解释这一点,这个例子在某种程度上受到你的问题的驱动(但没有实现任何现实的)。
所以,我们假设我们有IL指令Push
在堆栈上推送一个命名变量而Add
在堆栈上添加两个最后一项(为了保持简单,让我们说它只是将结果打印到控制台)。目标是一个注册表语言Nop
和Add
,它接受两个变量名称,添加它们(并将结果打印到控制台):
type IL =
| Push of string
| Add
type Reg =
| Add of string * string
| Nop
let input = [ IL.Push "a"; IL.Push "a"; IL.Push "b"; IL.Add; IL.Push "c"; IL.Add ]
输入应转换为Reg.Add("b", "a")
和Reg.Add("c", "a")
以及一些Nops。转换函数采用当前堆栈和单个指令:
let transform stack = function
| IL.Push var -> Reg.Nop, var::stack
| IL.Add -> Add(stack.Head, stack.Tail.Head), stack.Tail.Tail
要转换整个列表,我们可以使用List.fold
来保持当前的“状态”。它使用当前状态和单个输入指令调用提供的函数,并且提供的函数必须返回新状态。这里,“state”是堆栈,但也是我们正在生成的寄存器机器指令列表:
let endStack, regsReversed =
input |> Seq.fold (fun (stack, regs) il ->
// Transform current IL instruction, given current 'stack'
let reg, newStack = transform stack il
// Add new registry instruction to 'regs' and return new stack
(newStack, reg::regs) ) ([], [])
使用递归也可以完成同样的操作。结构非常相似,除了我们将状态保持为参数并通过进行递归调用来更改它:
let rec compile (stack, regs) = function
| [] -> (stack, regs)
| il::ils ->
// Transform current IL instruction, given current 'stack'
let reg, newStack = transform stack il
// Add new registry instruction to 'regs' and return new stack
compile (newStack, reg::regs) ils
let endStack, regs = compile ([], []) input
现在我们可以检查堆栈末尾是否为空并打印注册表机器指令(注意我们将它们附加到前面,所以我们需要反转结果):
if endStack <> [] then printfn "Stack is not empty!"
regs |> List.rev
正如杰克所说 - 你也可以使用更高级的处理方法,如计算表达式( state )。我认为编写严肃的编译器实际上是一个使用它们的地方,但如果你正在学习F#,那么从折叠和递归等基本概念开始就更容易了。
答案 1 :(得分:3)
传递“背景”并改变它 - 你在谈论state
工作流程;在那里,状态将是你的评估堆栈。
如果你确实使用state
工作流程(我建议你这样做),你可以使用State.List.map
中的ExtCore功能 - 它将一个列表映射到另一个列表,传递处理列表时从一个元素到下一个元素的上下文值。
不要担心使用长IL块(即大型方法体)溢出堆栈 - 一旦你有非常深的调用堆栈,堆栈溢出真的只是一个问题。
答案 2 :(得分:0)
您可以使用List.reduce
或使用自定义computation expression执行此操作(类似于异步工作方式)。我可能会使用List.reduce
,除非您经常重复使用它,或者由于其他原因而无法修复List.reduce
。