如何从 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
在第一个函数中使用元组:
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,但不知道究竟是哪个表达式生成了中间值(我认为您想知道值 n
是 workingVariable1 = 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
也许有更好的方法来做到这一点,比如编译器服务之类的,但这是迄今为止我能做的最好的尝试。
我在 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
在第一个函数中使用元组: 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,但不知道究竟是哪个表达式生成了中间值(我认为您想知道值 n
是 workingVariable1 = 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
也许有更好的方法来做到这一点,比如编译器服务之类的,但这是迄今为止我能做的最好的尝试。