Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Compiler construction 应用序列变换时的F#-可变上下文_Compiler Construction_F#_Functional Programming - Fatal编程技术网

Compiler construction 应用序列变换时的F#-可变上下文

Compiler construction 应用序列变换时的F#-可变上下文,compiler-construction,f#,functional-programming,Compiler Construction,F#,Functional Programming,为这篇文章奇怪的标题道歉,我不知道用什么方式来描述它最好 一般问题: 顺序应用Seq.map(或类似函数),除了列表中的每个项目外,还传递“上下文”。每次迭代都可以修改这个“上下文”,更新后的版本应该传递到列表中的下一个项目中 具体问题是: 我正在用F#创建一个编译器。我目前的步骤是将基于堆栈的IL转换为基于寄存器的IL。我在想我可以“走”基于堆栈的IL并携带当前的“eval堆栈”(类似于.NET的eval堆栈)。显然,每个堆栈IL操作码都会改变堆栈(例如,“添加”操作码会从堆栈中弹出两个项目并

为这篇文章奇怪的标题道歉,我不知道用什么方式来描述它最好

一般问题:

顺序应用Seq.map(或类似函数),除了列表中的每个项目外,还传递“上下文”。每次迭代都可以修改这个“上下文”,更新后的版本应该传递到列表中的下一个项目中

具体问题是:

我正在用F#创建一个编译器。我目前的步骤是将基于堆栈的IL转换为基于寄存器的IL。我在想我可以“走”基于堆栈的IL并携带当前的“eval堆栈”(类似于.NET的eval堆栈)。显然,每个堆栈IL操作码都会改变堆栈(例如,“添加”操作码会从堆栈中弹出两个项目并推送结果)。这个更新的堆栈将被传递到下一个操作码的发射周期

请注意,我对函数式编程非常陌生(我一周前就知道了),来自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.reduce
,除非您打算经常重用它,或者它没有修复
List.reduce
,因为其他原因。

我将尝试使用一个非常基本的示例来解释这一点,这个示例在某种程度上是出于您的问题(但没有实现任何现实的东西)

因此,让我们假设我们有IL指令
Push
,它将命名变量推送到堆栈上,并且
Add
在堆栈上添加最后两项(为了保持简单,假设它只将结果打印到控制台)。目标是带有
Nop
Add
的注册表语言,它接受两个变量名,并将它们添加(并将结果打印到控制台):

输入应转换为
Reg.Add(“b”、“a”)
Reg.Add(“c”、“a”)
和一些NOP。转换函数接受当前堆栈和单个指令:

let transform stack = function
  | IL.Push var -> Reg.Nop, var::stack
  | IL.Add -> Add(stack.Head, stack.Tail.Head), stack.Tail.Tail
要转换整个列表,我们可以使用保持当前“状态”的
list.fold
。它使用当前状态和单个输入指令调用提供的函数,并且提供的函数必须返回新状态。这里,“状态”是堆栈,也是我们正在生成的寄存器机器指令列表:

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
现在,我们可以检查堆栈末尾是否为空,并打印注册表机器指令(注意,我们将它们附加到前端,因此需要反转结果):

如果endStack[]则printfn“Stack不是空的!”
regs |>List.rev
正如Jack提到的,您还可以使用更高级的方法来处理这个问题,比如计算表达式(state)。我认为编写严肃的编译器实际上是一个使用它们有意义的地方,但是如果你正在学习F#,那么从折叠和递归等基本概念开始就更容易了。

传递一个“上下文”并对其进行变异——你说的是工作流;在那里,状态将是你的评估堆栈

如果您确实使用
状态
工作流(我建议您这样做),您可以使用函数from--它将一个列表映射到另一个列表,在处理列表时将上下文值从一个元素传递到下一个元素


不要担心长IL块(即大方法体)会导致堆栈溢出——堆栈溢出实际上只是在调用堆栈非常深时才需要考虑的问题。

我不完全确定List.reduce如何工作-您可以给出一个代码示例吗?看起来它的目标是更多的聚合/缩减,而不是一个带有“上下文”的“映射”函数。啊,对了。我的意思是
List.fold
。我有时会把这两个人搞糊涂。
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