3

我需要在我想通过利用do块来完成的模块中进行一些设置。奇怪的是,我的do街区似乎从未被击中。

更奇怪的是,如果我将模块代码加载到 fsi 中,它确实会受到影响。这是我的例子:

主文件

[<EntryPoint>]
let main args = 
    printfn "%b" TestNamespace.M.x
    0

测试模块.fs

namespace TestNamespace

module M = 
    do
        printfn "In do"
        failwith "Error" // this is line 6

    let x  = true

当我运行编译的可执行文件时,我得到

>test.exe
 true

为什么没有抛出异常?如果我自己在 FSI 中运行模块,我会得到

In do
System.Exception: Error
    at <StartupCode$FSI_0006>.$FSI_0006.main@() in    C:\Projects\Personal2\Playground\fsscripts\fsscripts\TestModule.fs:line 6
Stopped due to error

所以它得到了例外。

我在反编译中看到 do 初始化程序被滚动到一个单独的类中

namespace \u003CStartupCode\u0024fsscripts\u003E
{
  internal static class \u0024Library1
  {
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    [CompilerGenerated]
    [DebuggerNonUserCode]
    internal static int init\u0040;

    static \u0024Library1()
    {
      ExtraTopLevelOperators.PrintFormatLine<Unit>((PrintfFormat<Unit, TextWriter, Unit, Unit>) new PrintfFormat<Unit, TextWriter, Unit, Unit, Unit>("In do"));
      Operators.FailWith<Unit>("Error");
      bool x = M.x;
    }
  }
}

VS 实际的模块代码:

namespace TestNamespace
{
  [CompilationMapping(SourceConstructFlags.Module)]
  public static class M
  {
    public static bool x
    {
      [DebuggerNonUserCode] get
      {
        return true;
      }
    }
  }
}

那么如何确保 do 块实际执行?

--

编辑,鉴于上面的示例算作一个简单的常量表达式,因此不会产生可观察的初始化,为什么以下也不起作用?

[<EntryPoint>]
let main args = 
    printfn "%d" (TestNamespace.M.x id 1)
    0
namespace TestNamespace

module M = 
    do
        printfn "In do"
        failwith "Error"

    let x f a = f a

这打印出来1没有问题。


编辑,在重新阅读 Tomas 的评论后,因为函数被认为是一个常量表达式。

4

3 回答 3

6

有关该问题的一个很好的解释,请参阅这个先前 SO 问题的答案。重要的一点说:

文件的静态初始化程序在首次访问具有可观察初始化的值时执行

现在,“可观察初始化”是一个有点棘手的想法,但简单的常量初始化肯定没有可观察初始化 - 这do就是不执行块的原因。您可以诱使编译器认为存在一些命令式操作,例如通过添加do ()

module M = 
    do
        printfn "In do"
        failwith "Error" // this is line 6

    let x = (do ()); true
于 2013-09-04T16:33:39.230 回答
2

这是一种干净的方法。

首先,请注意,如果您希望在每次调用函数时都运行初始化代码,您可以这样做:

module M =
    let init () =
        printfn "In init"

    let x f a =
        init ()
        printfn "In x"
        f a

所以,如果你希望它只被调用一次(静态初始化),你可以简单地()从两个地方删除:

module M =
    let init =
        printfn "In init"

    let x f a =
        init
        printfn "In x"
        f a

好消息是您已经记录了init将首先调用代码的设计。如果您有几个单独的初始化代码块,那么您所依赖的依赖项就很清楚了(尽管第一次初始化当然会执行所有这些块)。

更新

这是一个版本,它也适用于发布版本。不太干净,但几乎:

module Init =
    open System.Runtime.CompilerServices

    [<MethodImpl(MethodImplOptions.NoInlining)>]
    let call init = init

module M =
    let init =
        printfn "In init"

    let x f a =
        Init.call init
        printfn "In x"
        f a

请注意,init它仍然是一个unit值,因此Init.call什么也不做的非内联函数也是如此。所以没有函数调用的开销。

另一种方法,它也有效,但看起来有点奇怪:

module M =
    let mutable init =
        printfn "In init"

    let x f a =
        init <- init
        printfn "In x"
        f a

有人可以改进吗?

于 2015-02-23T09:25:05.907 回答
2

您可以使用类获得所需的行为,同时保持相同的公共接口:

type M private () = 
    static do
        printfn "In do"
        failwith "Error"

    static member val x  = true
于 2013-09-04T16:57:36.003 回答