如何执行模块做阻塞?

时间:2013-09-04 16:23:42

标签: f#

我需要在我希望通过利用do块来完成的模块中进行一些设置。奇怪的是,我的do区块似乎永远不会被击中。

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

Main.fs

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

TestModule.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的评论后编辑,因为函数被认为是一个常量表达式。

3 个答案:

答案 0 :(得分:5)

有关问题的详细解释,请参阅this previous SO question的答案。重要的一点是:

  

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

现在,“可观察的初始化”有点棘手,但简单的常量初始化肯定没有可观察的初始化 - 这就是为什么不执行do块的原因。您可以欺骗编译器认为有一些必要的操作,例如添加do ()

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

    let x = (do ()); true

答案 1 :(得分:2)

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

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

    static member val x  = true

答案 2 :(得分: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代码。如果你有几个单独的初始化代码块,很明显你依赖的是哪个依赖(尽管第一个初始化将执行所有这样的块当然)。

<强>更新

这是一个版本,也适用于Release版本。不太干净,但差不多:

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

有人可以改进吗?