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

Lambda 如何在F中强制延迟执行表达式或属性访问器,而不使用额外的关键字,如;“懒惰”;,lambda,f#,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 看起来好像!%此.Property将扩展为tryGet(fun()->

在意识到运算符两边的参数总是在传递之前进行评估之前,我尝试了以下内容:

let tryGet f =
    try
        f() |> sprintf "%A"
    with
    | e -> sprintf "ERROR: %s" e.Message
let (!%) a = tryGet(fun() -> a)
sprintf "Test: %A" !% this.Property
看起来好像
!%此.Property
将扩展为
tryGet(fun()->this.Property)
,但这不是真的,它更接近于:

let a = this.Property
tryGet(fun() -> a)
现在我知道我可以使用
lazy
来强制执行延迟求值,但我想知道,如果使用标准语法(意思是:没有
lazy
本身,或者是
let!
),或者使用新的自动引用方式F#4.4,是否可以创建延迟求值表达式?

你不能

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

因此,推迟表达式求值的唯一方法是将其隐藏在闭包中,这是可以理解的,您不希望这样做

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

let deferred = lazy f()
let evaluated = deferred.Value
你不能

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

因此,推迟表达式求值的唯一方法是将其隐藏在闭包中,这是可以理解的,您不希望这样做

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

let deferred = lazy f()
let evaluated = deferred.Value
(将此添加到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#中使用动态编程,但您必须自己创建必要的反射

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

(将此添加到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#中使用动态编程,但您必须自己创建必要的反射


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

感谢您确认我所理解的内容。另一个黑客是
let
执行
和friends,它们将表达式包装在绑定表达式中,这也会延迟执行。是的,
lazy
是我们在需要延迟评估时经常使用的方法。汽车报价也有可能吗?虽然它们不适用于函数,但它们可以用于(我认为是昂贵的)实现方法的惰性求值的方法。使用自动引用将比
lazy
更重的语法。这是可以理解的。因此,如果我们尝试完整,我们有
lazy
,它可以懒散地计算任何表达式,我们有
fun
当然,它将创建一个lambda,它只在调用时进行计算,我们有(自动)引用。由于“应用程序求值顺序”,所有这些都不允许被运算符或函数“替换”,对吗?我忘记了什么吗?对。您忘了提到计算构建器,但它们可以被视为构建闭包的语法糖,但是
lazy
也可以。所以在最底层,有人可能会说只有两种机制——闭包和引用。谢谢你确认我所理解的。另一个黑客是
let
执行
和friends,它们将表达式包装在绑定表达式中,这也会延迟执行。是的,
lazy
是我们在需要延迟评估时经常使用的方法。汽车报价也有可能吗?虽然它们不适用于函数,但它们可以用于(我认为是昂贵的)实现方法的惰性求值的方法。使用自动引用将比
lazy
更重的语法。这是可以理解的。所以,如果我们试图做到完整,我们会有
懒惰的
,它懒惰地评估