【发布时间】:2017-07-17 19:18:16
【问题描述】:
我正在开发一种我自己设计的函数式编程语言,但我偶然发现了一个我无法解决的问题。我想知道是否有人对如何解决它有任何建议,或者为什么它是不可能的。
下面的代码概述了一个不是理想但妥协的解决方案。
这个问题是我目前使用的运行时系统的核心。我没有依赖 .Net 堆栈,而是使用 monad 在蹦床上执行操作。这应该有助于逐步调试,并使用户不必担心堆栈空间。这是我目前使用的 monad 的简化版本。
type 't StackFree =
|Return of 't //Return a value
|StackPush of ('t->'t StackFree)*'t StackFree //Pushes a return handler onto the "Stack"
|Continuation of (unit->'t StackFree) //Perform a simple opperation
type StackFreeMonad() =
member this.Delay(fn) =
Continuation(fn)
member this.Bind(expr,fn) =
StackPush(fn,expr)
member this.Return(value) =
Return(value)
member this.ReturnFrom(x) =x
let stackfree = StackFreeMonad()
这不是最初的设计,但它是我在理想世界中使用 F# 计算表达式所能得到的最好的设计,上述计算表达式适用于这种类型。
type 't Running =
|Result of 't
|Step of (unit->'t Running)
所以为了将 StackFree 转换为 Running 类型,我必须使用这个转换函数
//this method loops through the StackFree structure finding the next computation and managing a pseudo stack with a list.
let prepareStackFree<'t> :'t StackFree->'t Running =
let rec inner stack stackFree =
Step(fun ()->
match stackFree with
//takes the return values and passes it to the next function on the "Stack"
|Return(value)->
match stack with
|[]->Result(value)
|x::xs -> inner xs (x value)
//pushes a new value on the the "Stack"
|StackPush(ret,next) ->
inner (ret::stack) next
//performs a single step
|Continuation(fn)->
inner stack (fn()))
inner []
下面是这两种类型的简单示例。
let run<'t> :'t StackFree->'t =
let rec inner = function
|Step(x)-> inner (x())
|Result(x)-> x
stackFreeToRunning>>inner
//silly function to recompute an intiger value using recursion
let rec recompute number = stackfree {
if number = 0 then return 0
else
let! next = recompute (number-1)
return next+1
}
let stackFreeValue = recompute 100000
let result = run stackFreeValue
do printfn "%i" result
我花了几个小时试图获得一个直接在 Running 类型上工作的计算表达式,并去掉了中间人 StackFree。但是我不知道该怎么做。在这一点上,我正在认真考虑不可能解决这个问题的可能性。但是我无法弄清楚这是不可能的原因。
我已经接近了几次,但最终的解决方案最终以某种令人困惑的方式使用了堆栈。
是否可以在不使用 .Net 堆栈的情况下对 Running 类型进行运算的计算表达式?如果这不可能,为什么不可能。我一定缺少一些简单的数学推理。
注意:这些不是我使用的实际类型,它们针对这个问题进行了简化,真正的类型会跟踪脚本中的范围和位置。此外,我知道这种抽象类型的严重性能成本
编辑:这是解决问题的另一种方法。这个实现是有缺陷的,因为它使用了堆栈。有没有在不使用堆栈的情况下获得下面的确切行为?
type RunningMonad() =
member this.Delay(fn) =
Step(fun ()->fn ())
member this.Bind(m, fn) =
Step(fun ()->
match m with
|Result(value)-> fn value
//Here is the problem
|Step(next)-> this.Bind(next(),fn))
member this.Return(v) =
Result(v)
member this.ReturnFrom(x) = x
上述计算表达式中的绑定实现创建了一个调用另一个函数的函数。因此,随着您越来越深入并越来越多地调用 bind,您必须追逐一堆函数调用,然后最终遇到 stackoverflow 异常。
Edit2:清晰。
【问题讨论】:
-
如果我运行你的示例代码,它在没有
StackOverflowException的情况下运行良好,所以我猜(没有真正阅读代码)它不使用堆栈(至少不用于递归调用)。这是您所看到的,还是您所期待的? -
是的,示例代码运行良好,没有堆栈溢出异常。问题是有没有办法摆脱必须管理“堆栈”列表的中间层。
-
我解决了它我能够将 prepareStackFree 混合到 StackFree monad 中。它仍然需要一个列表来传递给它。 gist.github.com/thomasd16/017e550f4fd5c5ffd110cb7e2f337b88。仍然想知道是否有可能没有列表来表示堆栈。