【问题标题】:F#: Why do I have to explicitly specify 'unit' for functions taking no arguments?F#:为什么我必须为不带参数的函数显式指定“单位”?
【发布时间】:2010-01-30 00:38:43
【问题描述】:

所以我刚刚完成了我的第一个 F# 程序,我唯一的功能背景是 一点 Haskell 知识(阅读:还没有真正在其中生成任何程序)。

在经历了一些令人难以置信的行为之后,我开始意识到 F# 区分了:

prepareDeck = allSuits |> List.collect generateCards |> shuffle

prepareDeck() = allSuits |> List.collect generateCards |> shuffle

我注意到它“缓存”了前者,如果再次调用它就不会重新计算它,而它将后者视为普通函数。如果有问题的函数没有副作用,你无法区分,显然,但我的shuffle 有!

这应该是常识吗?我还没有在任何教程材料中看到它。原因只是解析器的一个弱点,有点像您在使用函数之前必须声明一个函数吗?

【问题讨论】:

    标签: syntax f# function


    【解决方案1】:

    大多数 F# 材料确实解释了模块中的所有顶级语句都是在声明时自上而下执行的。也就是说,你声明的不是函数,而是程序运行时绑定一次的值。

    查看反映的代码确实很有帮助。我有一个简单的文件:

    let juliet = "awesome"
    let juliet2() = "awesome"
    

    编译后的代码如下所示:

    public static string juliet
    {
        [CompilerGenerated, DebuggerNonUserCode]
        get
        {
            return "awesome";
        }
    }
    
    //...
    
    public static string juliet2()
    {
        return "awesome";
    }
    

    所以一个是静态属性,另一个是函数。这是一个理想的属性,因为想象一下如果我们有这样的东西:

    let x = someLongRunningDatabaseCall()
    

    我们只希望x被绑定一次,我们不希望它每次访问x时都调用数据库函数。

    此外,我们还可以编写如下有趣的代码:

    > let isInNebraska =
        printfn "Creating cities set"
        let cities = set ["Omaha"; "Bellevue"; "Lincoln"; "Papillion"; "La Vista"; "Ralston"]
        fun n -> cities.Contains(n);;
    Creating cities set
    
    val isInNebraska : (string -> bool)
    
    > isInNebraska "Omaha";;
    val it : bool = true
    
    > isInNebraska "Okaloosa";;
    val it : bool = false
    

    由于isInNebraska 是一个值,因此立即对其进行评估。它的数据类型恰好是(string -> bool),所以它看起来 像一个函数。因此,即使我们调用该函数 1000 次,我们也只会填充一次 cities 集。

    让我们将该代码与此进行比较:

    > let isInNebraska2 n =
        printfn "Creating cities set"
        let cities = set ["Omaha"; "Bellevue"; "Lincoln"; "Papillion"; "La Vista"; "Ralston"]
        cities.Contains(n);;
    
    val isInNebraska2 : string -> bool
    
    > isInNebraska2 "Omaha";;
    Creating cities set
    val it : bool = true
    
    > isInNebraska2 "Okaloosa";;
    Creating cities set
    val it : bool = false
    

    糟糕,每次调用该函数时,我们都会创建一个新的城市集。

    因此,价值和功能之间肯定存在合法和真实的区别。

    【讨论】:

    • 很好的例子。对于任何感兴趣的人,“Expert F#”一书(第 8 章)在设计函数以实现高效的部分应用程序的上下文中进一步展开了这一讨论。
    • 我认为这里有一个缺陷:静态属性和静态方法没有太大区别,因为都是方法,而对属性的访问是实际上是一个方法调用(这就是您可以将属性标记为 virtual 的原因)。我看不到您的“理想财产”。
    • 我相信不同之处在于,当您拥有let julietNoFunction = ... 时,静态属性将简单地返回一个存储在静态字段上的值,并且该值仅在生成类型的静态构造函数中计算一次为模块。我想我已经在 Reflector 中看到过这个,但我不确定了。无论如何,你展示它的方式并没有显示出使用let juliet = ...而不是let juliet() = ...的任何优势。
    【解决方案2】:

    这就是几乎所有带有副作用的语言的工作方式。

    let name = expr
    

    “现在”运行代码,如果expr 有影响,可能会导致副作用。对name 的后续引用无效。而

    let name() = expr
    

    定义一个函数,现在没有效果,每次调用name() 时都会评估(并产生效果)。

    【讨论】:

    • 明白了。我猜我很困惑,因为 F# 是我用过的第一个功能性但有副作用的语言。
    猜你喜欢
    • 2018-09-01
    • 2020-05-23
    • 1970-01-01
    • 2021-12-31
    • 2019-02-19
    • 2012-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多