F# 是否具有访问词法作用域的语言构造(如 python locals()/globals())
Does F# have a language construct to access the lexical scope (like python locals()/globals())
在 F# 中编写测试时,我试图生成有关导致错误的状态的有用消息。在 python 中,我将包含所有的 locals(),因此在测试跟踪中可以轻松访问它们。
F# 中是否有类似的结构?
我一直在网上搜索优秀的 fsharpforfunandprofit 网站,并浏览了保留关键字列表。
这是我希望能够执行的一些代码。
[<Property>]
let some_test x =
let example_inner_helper y =
let z = y + x
// Example of the meta-construction i am looking for
let scope = {|
local = {| y = y; z = z |};
capture = {|
local = {| x = x |};
capture = {| (* ... *) |};
|};
|}
x = z |@ sprintf "require %A=%A (%A)" x y scope
example_inner_helper (x+x)
这会产生非常有用的输出
Tests.some_test
Source: Tests.fs line 105
Duration: 51 ms
Message:
FsCheck.Xunit.PropertyFailedException :
Falsifiable, after 1 test (1 shrink) (StdGen (387696160,296644521)):
Label of failing property: require 1=2 ({ capture = { capture = {}
local = { x = 1 } }
local = { y = 2
z = 3 } })
Original:
-1
Shrunk:
1
但是,我必须明确捕获像 "scope" 这样的构造可以自动提供的信息。导致丑陋、容易出错的东西,比如
let ``firstAscendingLastDescendingPartE Prop`` (a: Extent<int>) b =
let validateT ea eb =
let checkXbeforeY ex ey headx restx =
match restx with
| ValueNone -> // No rest
(ex = headx) |@ sprintf "no rest: ex = headx (%A = %A)" ex headx
.&. ((intersectE ex ey) = ValueNone) |@ sprintf "no rest: ex ∩ ey = ∅ (%A ∩ %A)" ex ey
| ValueSome rex -> // Some rest ->
// headx and restx combines to ex
((expandE headx rex) = ex) |@ sprintf "rest: headx+rex = ex (%A...%A)" headx rex
.&. (exactlyTouchesE headx rex) |@ sprintf "rest: headx ends where rex starts (ex=%A ey=%A headx=%A restx=%A)" ex ey headx restx
.&. (exactlyTouchesE headx ey) |@ sprintf "rest: headx ends where ey starts (ex=%A ey=%A headx=%A restx=%A)" ex ey headx restx
....
(当然,我不关心 "scope" 构造的实际类型)
关于取消引用
我已经看过 unquote 了,它很可爱,看起来也不错。但它对代码的限制很大:
- 错误 FS3155 引用可能不涉及对捕获的局部变量的赋值或取地址
- 错误 FS1230 引用表达式中不允许使用内部泛型函数。考虑添加一些类型约束,直到此函数不再通用。
我的代码有点像:
[<Struct>]
type X<'T when 'T: comparison and 'T: equality> =
val public first: 'T
val public last: 'T
new (first: 'T, last: 'T) = {
first =
(if last <= first then
invalidOp (sprintf "first < last required, (%A) is not < (%A)" first last)
else first)
last = last;
}
// ..
所以我在使用以下两种结构进行测试时遇到问题(导致上述错误)。
let quote_limits () =
let x first last = X(first, last)
let validate (x: X<'a>) = x.first < x.last
let a = X(0, 1)
<@ a.first = 0 @> |> test
<@ validate a @> |> test
第一个我可以解决的方法是使用函数来访问结构部分,但对泛型的限制是 PITA。
我认为没有办法做到这一点,即使在原则上也是如此。 F# 代码被编译为基于堆栈的 .NET 字节码,因此局部变量实际上(通常)不存在于编译代码中。您也许可以获得一些全局变量(它们是静态成员)或者以某种方式使用调试信息,但我认为没有标准的方法可以做到这一点。
就是说,在您的特定情况下,F# 实际上有一个使用 unquote 测试框架的不错选择。这使您可以将测试代码放在 F# 引用中,然后它会显示该引用的计算方式。例如:
let some_test x =
let example_inner_helper y =
<@
let z = y + x
x = z
@> |> test
example_inner_helper (x+x)
some_test 10
这里,我在引用里面定义了局部变量z
。当您 运行 测试时,unquote 将打印各个评估步骤,您可以在此处看到 z
的值:
Test failed:
let z = y + x in x = z
let z = 20 + 10 in x = z
let z = 30 in x = z
false
在 F# 中编写测试时,我试图生成有关导致错误的状态的有用消息。在 python 中,我将包含所有的 locals(),因此在测试跟踪中可以轻松访问它们。
F# 中是否有类似的结构?
我一直在网上搜索优秀的 fsharpforfunandprofit 网站,并浏览了保留关键字列表。
这是我希望能够执行的一些代码。
[<Property>]
let some_test x =
let example_inner_helper y =
let z = y + x
// Example of the meta-construction i am looking for
let scope = {|
local = {| y = y; z = z |};
capture = {|
local = {| x = x |};
capture = {| (* ... *) |};
|};
|}
x = z |@ sprintf "require %A=%A (%A)" x y scope
example_inner_helper (x+x)
这会产生非常有用的输出
Tests.some_test
Source: Tests.fs line 105
Duration: 51 ms
Message:
FsCheck.Xunit.PropertyFailedException :
Falsifiable, after 1 test (1 shrink) (StdGen (387696160,296644521)):
Label of failing property: require 1=2 ({ capture = { capture = {}
local = { x = 1 } }
local = { y = 2
z = 3 } })
Original:
-1
Shrunk:
1
但是,我必须明确捕获像 "scope" 这样的构造可以自动提供的信息。导致丑陋、容易出错的东西,比如
let ``firstAscendingLastDescendingPartE Prop`` (a: Extent<int>) b =
let validateT ea eb =
let checkXbeforeY ex ey headx restx =
match restx with
| ValueNone -> // No rest
(ex = headx) |@ sprintf "no rest: ex = headx (%A = %A)" ex headx
.&. ((intersectE ex ey) = ValueNone) |@ sprintf "no rest: ex ∩ ey = ∅ (%A ∩ %A)" ex ey
| ValueSome rex -> // Some rest ->
// headx and restx combines to ex
((expandE headx rex) = ex) |@ sprintf "rest: headx+rex = ex (%A...%A)" headx rex
.&. (exactlyTouchesE headx rex) |@ sprintf "rest: headx ends where rex starts (ex=%A ey=%A headx=%A restx=%A)" ex ey headx restx
.&. (exactlyTouchesE headx ey) |@ sprintf "rest: headx ends where ey starts (ex=%A ey=%A headx=%A restx=%A)" ex ey headx restx
....
(当然,我不关心 "scope" 构造的实际类型)
关于取消引用
我已经看过 unquote 了,它很可爱,看起来也不错。但它对代码的限制很大:
- 错误 FS3155 引用可能不涉及对捕获的局部变量的赋值或取地址
- 错误 FS1230 引用表达式中不允许使用内部泛型函数。考虑添加一些类型约束,直到此函数不再通用。
我的代码有点像:
[<Struct>]
type X<'T when 'T: comparison and 'T: equality> =
val public first: 'T
val public last: 'T
new (first: 'T, last: 'T) = {
first =
(if last <= first then
invalidOp (sprintf "first < last required, (%A) is not < (%A)" first last)
else first)
last = last;
}
// ..
所以我在使用以下两种结构进行测试时遇到问题(导致上述错误)。
let quote_limits () =
let x first last = X(first, last)
let validate (x: X<'a>) = x.first < x.last
let a = X(0, 1)
<@ a.first = 0 @> |> test
<@ validate a @> |> test
第一个我可以解决的方法是使用函数来访问结构部分,但对泛型的限制是 PITA。
我认为没有办法做到这一点,即使在原则上也是如此。 F# 代码被编译为基于堆栈的 .NET 字节码,因此局部变量实际上(通常)不存在于编译代码中。您也许可以获得一些全局变量(它们是静态成员)或者以某种方式使用调试信息,但我认为没有标准的方法可以做到这一点。
就是说,在您的特定情况下,F# 实际上有一个使用 unquote 测试框架的不错选择。这使您可以将测试代码放在 F# 引用中,然后它会显示该引用的计算方式。例如:
let some_test x =
let example_inner_helper y =
<@
let z = y + x
x = z
@> |> test
example_inner_helper (x+x)
some_test 10
这里,我在引用里面定义了局部变量z
。当您 运行 测试时,unquote 将打印各个评估步骤,您可以在此处看到 z
的值:
Test failed:
let z = y + x in x = z
let z = 20 + 10 in x = z
let z = 30 in x = z
false