我正在尝试以下内容,然后才意识到运算符两侧的参数总是被之前评估为传递它们:
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自动引用的新方法,可以创建一个延迟评估的表达式吗?
答案 0 :(得分:4)
你不能。
F#具有适用的评估顺序,这意味着在应用函数之前,函数参数会得到完全评估,这适用于任何函数,包括前缀运算符。
因此,推迟表达式评估的唯一方法是将其隐藏在闭包中,这是您可以理解的不想做的。
编译器提供的一个“hack”是lazy
关键字,坦率地说,我不明白为什么你不喜欢它,因为它不能比这更短 - 只添加一个关键字:
let deferred = lazy f()
let evaluated = deferred.Value
答案 1 :(得分:0)
(将其添加为Fyodor的正确答案)
刚才意识到当需求是延迟加载属性时,还有另一种更简单的方法。只需获取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
包装函数内部。
F#附带?
运算符,它将左侧操作数视为正常,右侧操作数作为您放置的任何内容的字符串值。
它打算允许在F#中进行动态编程,但你必须自己创建必要的反射。
当然,这比普通的闭包慢得多,但它也更灵活,它肯定是延迟执行对象访问器的一种方式。