如何强制在F#中延迟执行表达式或属性访问器而不使用额外的关键字,如“lazy”

时间:2015-12-11 23:53:46

标签: lambda f#

我正在尝试以下内容,然后才意识到运算符两侧的参数总是被之前评估为传递它们:

let tryGet f =
    try
        f() |> sprintf "%A"
    with
    | e -> sprintf "ERROR: %s" e.Message
let (!%) a = tryGet(fun() -> a)
sprintf "Test: %A" !% this.Property

好像!% this.Property会扩展为tryGet(fun() -> this.Property),但事实并非如此,它更接近:

let a = this.Property
tryGet(fun() -> a)

现在我知道我可以使用lazy强制执行延迟评估,但我想知道是否使用标准语法(意思是:没有lazy本身,或let!就此而言),或者使用F#4.4自动引用的新方法,可以创建一个延迟评估的表达式吗?

2 个答案:

答案 0 :(得分:4)

你不能。

F#具有适用的评估顺序,这意味着在应用函数之前,函数参数会得到完全评估,这适用于任何函数,包括前缀运算符。

因此,推迟表达式评估的唯一方法是将其隐藏在闭包中,这是您可以理解的不想做的。

编译器提供的一个“hack”是lazy关键字,坦率地说,我不明白为什么你不喜欢它,因为它不能比这更短 - 只添加一个关键字:

let deferred = lazy f()
let evaluated = deferred.Value

答案 1 :(得分:0)

(将其添加为Fyodor的正确答案)

替代方法1

刚才意识到当需求是延迟加载属性时,还有另一种更简单的方法。只需获取gettor成员函数。

换句话说,从我的OP中,改变这个:

let (!%) a = tryGet(fun() -> a)
sprintf "Test: %A" !% this.Property

进入这个:

let (!%) prop = tryGet(prop())
sprintf "Test: %A" !% this.get_Property    // note the "get_" prefix

这是有效的,即使默认情况下属性gettor方法是隐藏的(在VB的C#中它甚至不能直接访问),它在F#中可用,并且可以像任何其他函数一样传递。

这样做的另一个好处是,如果对象是null,它不会抛出调用站点,而只会在tryGet包装函数内部。

替代方法2

F#附带?运算符,它将左侧操作数视为正常,右侧操作数作为您放置的任何内容的字符串值。

它打算允许在F#中进行动态编程,但你必须自己创建必要的反射。

当然,这比普通的闭包慢得多,但它也更灵活,它肯定是延迟执行对象访问器的一种方式。