Module F#加载时的模块效应

Module F#加载时的模块效应,module,f#,lazy-evaluation,Module,F#,Lazy Evaluation,我怀疑这种行为可能不是F#独有的,但我会选择它,因为这是我在工作中使用的 假设我有一个模块 open System module Bar = let bar = Console.WriteLine("BAR!") "bar" 我在fsx中有以下内容: // this is a standard library function (Operators.defaultArg or Option.defaultValue) let getValueOr v

我怀疑这种行为可能不是F#独有的,但我会选择它,因为这是我在工作中使用的

假设我有一个模块

open System

module Bar =
    let bar =
        Console.WriteLine("BAR!")
        "bar"
我在fsx中有以下内容:

// this is a standard library function (Operators.defaultArg or Option.defaultValue)
let getValueOr v = function
| Some x -> x
| None -> v

let opt = Some "foo"

Console.WriteLine( opt |> getValueOr Bar.bar )
当我运行此命令时,我看到以下打印内容

BAR!
foo
这是意料之中的,因为参数通常在函数体之前求值,所以我希望
Bar.Bar
中的效果在
getValueOr
部分应用它之前发生(甚至在读取模块时)

但是,当我将
Bar
模块编译成DLL和
#r
模块时,我只看到

foo
换句话说,
Bar.Bar
不会被计算。。。为什么?是因为#r


这种行为实际上是我想要创建的东西,但它有点违反直觉,我想更好地理解它。

这是因为优化

当您在FSI中运行时,优化被关闭,因此一切都按照您期望的方式工作

但是,当您在发行版中编译(即通过优化)时,F#compiler能够做更多的事情,因为它知道代码的结构。在这种情况下,函数
getValueOr
在调用站点内联,最后一行大致变成以下内容:

// C# syntax
Console.WriteLine( foo == null ? Bar.bar : foo.Value )
下面是另一个有趣的实验:如果将
Bar
模块的定义移动到引用
Bar.Bar
的同一位置,效果(可能)会再次出现,因为
Bar
的定义本身将是内联的,大致如下所示:

// C# syntax:
Console.WriteLine( "BAR!" )
var bar = "bar"
var foo = new Some("foo")
Console.WriteLine( foo == null ? bar : foo.Value )


底线是:不受控制的影响是坏的。他们使你的程序不可预测。尽量避开它们。

我觉得这不是我可以一直依赖的东西。谢谢你的回答!对于包含某些副作用过程(例如,从某些外部源获取)必须初始化的值的模块,您建议采用什么样的结构方式?不要公开此类值。公开工厂,然后在应用程序初始化代码中以可预测的顺序调用它们。