如何从 F# 中的函数中获取工作变量

How to get working variables out of a function in F#

我在 F# 中有一个函数,例如:

let MyFunction x = 
    let workingVariable1 = x + 1
    let workingVariable2 = workingVariable1 + 1
    let y = workingVariable2 + 1
    y

基本上,MyFunction 接受输入 x 和 returns y。但是在计算的过程中,有几个工作变量(中间变量),由于我的工作性质(土木工程),我需要报告所有的中间结果。我应该如何存储函数的所有工作变量?

如果我没理解错,那么有几种选择:

let MyFunction1 x = 
    let workingVariable1 = x + 1
    let workingVariable2 = workingVariable1 + 1
    let y = workingVariable2 + 1
    y,workingVariable1,workingVariable2

MyFunction1 2 |> printfn "%A"

type OneType()=
    member val Y = 0 with get,set
    member val WV1 = 0 with get,set
    member val WV2 = 0 with get,set

    override this.ToString() =
        sprintf "Y: %d; WV1: %d; WV2: %d\n" this.Y this.WV1 this.WV2

let MyFunction2 x = 
    let workingVariable1 = x + 1
    let workingVariable2 = workingVariable1 + 1
    let y = workingVariable2 + 1
    new OneType(Y=y,WV1=workingVariable1,WV2=workingVariable2)

MyFunction2 2 |> printfn "%A"

输出:

(5, 3, 4)
Y: 5; WV1: 3; WV2: 4

http://ideone.com/eYNwYm

在第一个函数中使用元组: https://msdn.microsoft.com/en-us/library/dd233200.aspx

第二种本机数据类型。 https://msdn.microsoft.com/en-us/library/dd233205.aspx

这不是很"functional"的方式,但您可以使用可变变量来存储中间结果:

let mutable workingVariable1 = 0
let mutable workingVariable2 = 0

let MyFunction x = 
    workingVariable1 <- x + 1
    workingVariable2 <- workingVariable1 + 1
    let y = workingVariable2 + 1
    y

我不太确定 "report" 你期待什么样的。这是中间值的日志吗?该日志应保留多长时间?等等等等这是我的尝试,据我所知。这不是理想的解决方案,因为它允许报告中间步骤的 values,但不知道究竟是哪个表达式生成了中间值(我认为您想知道值 nworkingVariable1 = x + 1 表达式的输出)。

所以我的解决方案是基于Computation Expressions。计算表达式是一种 F# "monads".

首先你需要定义一个计算表达式:

type LoggingBuilder() =
    let log p = printfn "intermediate result %A" p

    member this.Bind(x, f) = 
        log x
        f x

    member this.Return(x) = 
        x

接下来我们创建一个计算表达式生成器的实例:

let logIntermediate = new LoggingBuilder()

现在您可以像这样重写您的原始函数:

let loggedWorkflow x = 
    logIntermediate
        {
            let! workingVariable1 = x + 1
            let! workingVariable2 = workingVariable1 + 1
            let! y = workingVariable2 + 1
            return y,workingVariable1,workingVariable2
        }

如果你 运行 loggedWorkflow 函数传入 10 你会得到这个结果:

> loggedWorkflow 10;;
intermediate result 11
intermediate result 12
intermediate result 13
val it : int * int * int = (13, 11, 12)

正如我所说,您的中间值已记录,但是您不确定哪一行代码负责。

然而,我们可以稍微改进一下,以使用相应的代码行获取类型的全名。我们必须稍微改变一下我们的计算表达式构建器:

member this.Bind(x, f) = 
        log x (f.GetType().FullName)
        f x

和一个日志函数:

let log p f = printfn "intermediate result %A %A" p f

如果你再次 运行 函数传入 10 你会得到这个结果(这是来自我在 FSI 中的脚本 运行):

intermediate result 11 "FSI_0003+loggedWorkflow@34"
intermediate result 12 "FSI_0003+loggedWorkflow@35-1"
intermediate result 13 "FSI_0003+loggedWorkflow@36-2"

这是一个 hack 但我们得到了一些额外的信息,比如 workingVariable1 = x + 1 等表达式的定义位置(在我的例子中是 "FSI_")和在哪一行代码上(@34、@35-1)。如果您的代码更改并且这很可能发生,那么如果长时间记录,您的中间结果将是错误的。请注意,我没有在 FSI 之外对其进行测试,也不知道是否在每种情况下都包含代码行。

我不确定我们是否可以从计算表达式中获取表达式名称(如 workingVariable1 = x + 1)来记录。我觉得不可能。

注意:除了日志函数,您还可以定义一些其他函数,将这些中间步骤保存在持久存储或其他任何东西中。

更新

我试过想出一个不同的解决方案,但并不容易。但是我可能已经妥协了。让我解释。您无法在计算表达式中获取绑定到值的名称。因此,我们无法记录 "'workingVariable1' result is 2" 的表达式 workingVariable1 = x + 1。假设我们向计算表达式传递了一个额外的中间结果名称:

let loggedWorkflow x = 
    logIntermediate
        {
            let! workingVariable1 = "wk1" @@ x + 1
            let! workingVariable2 = "wk2" @@ workingVariable1 + 1
            let! y = "y" @@ workingVariable2 + 1
            return y,workingVariable1,workingVariable2
        }

正如您在 @@ 符号之前看到的,我们给出了中间结果的名称,因此 let! workingVariable1 = "wk1" @@ x + 1 行将被记录为 "wk1".

然后我们需要一个额外的类型来存储表达式的名称和值:

type NamedExpression<'T> = {Value:'T ; Name: string}

然后我们必须重新定义一个中缀运算符@@我们使用une计算表达式:

let (@@) name v = {Value = v; Name = name}

此运算符只取表达式的左右部分并将其包装在 NamedExpression<'T> 类型中。

我们还没有完成。我们必须修改计算表达式构建器的绑定部分:

member this.Bind(x, f) =
        let {Name = n; Value = v} = x
        log v n
        f v

首先我们将NamedExpression<'T>值解构为name和wraped value。我们记录它并将函数 f 应用于展开的值 v。日志函数看起来像这样:

let log p n = printfn "'%s' has intermediate result of : %A" n p

现在,当您 运行 工作流程 loggedWorkflow 10;; 时,您会得到以下结果:

'wk1' has intermediate result of : 11
'wk2' has intermediate result of : 12
'y' has intermediate result of : 13

也许有更好的方法来做到这一点,比如编译器服务之类的,但这是迄今为止我能做的最好的尝试。