为什么在F#中的方法内可以进行多个let绑定

V.B*_*.B. 4 .net f#

一位同事审查了我的代码并询问我为什么使用以下模式:

type MyType() =
  member this.TestMethod() =
    let a = 1
    // do some work
    let a = 2 // another value of a from the work result
    ()
Run Code Online (Sandbox Code Playgroud)

我告诉他这是一个糟糕的模式,我的意图是使用let mutable a = ....但后来他问为什么在F#类中有可能有这样的顺序绑定,而在模块或.fsx文件中是不可能的?实际上,我们正在改变一个不可变的值!

我回答说,在模块或.fsx文件中,let绑定变为静态方法,因此具有相同名称的绑定与具有相同名称的两个类属性相同的方式冲突是直截了当的.但我不知道为什么在方法中这是可能的!

在C#中,我发现对范围变量很有用,特别是在单元测试中,当我想通过复制粘贴代码来为测试用例组成一些不同的值时:

{
    var a = 1;
    Assert.IsTrue(a < 2);
}

{
    var a = 42;
    Assert.AreEqual(42, a);
}
Run Code Online (Sandbox Code Playgroud)

在F#中,我们不仅可以重复相同的let绑定,而且可以将一个不可变的绑定更改为可变的绑定,然后以通常的方式将其更改为:

type MyType() =
  member this.TestMethod() =
    let a = 1
    // do some work
    let a = 2 // another value of a from the work result
    let mutable a = 3
    a <- 4
    ()
Run Code Online (Sandbox Code Playgroud)

为什么允许我们在F#方法中重复let绑定?我应该如何向一个刚接触F#的人解释这个问题并问"什么是不可变的变量以及我为什么要改变它?"

就个人而言,我对设计选择和权衡取舍感兴趣吗?我很满意不同范围很容易被检测到的情况,例如当变量在构造函数中然后我们在体内重新定义它,或者当我们在for/ whileloops中定义新的绑定时.但是同一级别的两个连续绑定有点违反直觉.我觉得我应该在精神上添加in到每一行的末尾,就像详细的语法来解释范围一样,这样那些虚拟的ins类似于C#的{}

Tom*_*cek 8

I think it is important to explain that there is a different thing going on in F# than in C#. Variable shadowing is not replacing a symbol - it is simply defining a new symbol that happens to have the same name as an existing symbol, which makes it impossible to access the old one.

When I explain this to people, I usually use an example like this - let's say we have a piece of code that does some calculation using mutation in C#:

var message = "Hello";
message = message + " world";
message = message + "!";
Run Code Online (Sandbox Code Playgroud)

This is nice because we can gradually build the message. Now, how can we do this without mutation? The trick is to define new variable at each step:

let message1 = "Hello";
let message2 = message1 + " world";
let message3 = message2 + "!";
Run Code Online (Sandbox Code Playgroud)

This works - but we do not really need the temporary states that we defined during the construction process. So, in F# you can use variable shadowing to hide the states you no longer care about:

let message = "Hello";
let message = message + " world";
let message = message + "!";
Run Code Online (Sandbox Code Playgroud)

现在,这意味着完全相同的事情 - 你可以很好地向使用Visual F#Power Tools的人们展示这一点,它强调所有出现的符号 - 所以你会看到符号是不同的(即使它们具有相同的名称) .

  • 这有用的一个例子是参数消毒.你的函数接受一个可能具有一些不良质量的参数(比如非修剪字符串或非正数或其他),你在体内做的第一件事就是重新定义该名称以指向一个已消毒的值:`let fx = let x = sanitize x; calculateSomething x`. (3认同)

Gru*_*oon 6

F#代码:

let a = 1
let a = 2
let a = 3
a + 1
Run Code Online (Sandbox Code Playgroud)

只是一个浓缩(又名"轻")的版本:

let a = 1 in
    let a = 2 in
        let a = 3 in
            a + 1
Run Code Online (Sandbox Code Playgroud)

(类型)C#等价物将是这样的:

var a = 1;
{
    var a = 2;
    {
        var a = 3;
        return a + 1;
    }
}
Run Code Online (Sandbox Code Playgroud)

在具有嵌套作用域的上下文中,C#不允许对名称进行遮蔽,但F#和几乎所有其他语言都不允许.

实际上,根据所有知识字体, C#在这种情况下明显不允许阴影的少数语言之一是不寻常的.

这可能是因为C#是一种相对较新的语言.OTOH F#的大部分设计都是从OCaml复制而来的,OCaml又基于较旧的语言,所以在某种意义上,F#的设计比C#"更老".


Mar*_*ann 5

Tomas Petricek 已经解释过这不是突变,而是阴影。一个后续问题是:它有什么用?

这不是我每天都使用的功能,但有时我发现它很有用,尤其是在进行基于属性的测试时。这是我最近在使用 FsCheck进行网球 Kata时所做的一个示例:

[<Property>]
let ``Given player has thirty points, when player wins, then the new score is correct``
    (points : PointsData)
    (player : Player) =

    let points = points |> pointTo player Thirty

    let actual = points |> scorePoints player

    let expected =
        Forty {
            Player = player
            OtherPlayerPoint = (points |> pointFor (other player)) }
    expected =? actual
Run Code Online (Sandbox Code Playgroud)

在这里,我在阴影 points用一个新的值进行。

原因是我想明确测试玩家已经拥有的情况 Thirty积分并再次获胜的情况,无论另一个玩家有多少积分。

PointsData 定义如下:

type Point = Love | Fifteen | Thirty
type PointsData = { PlayerOnePoint : Point; PlayerTwoPoint : Point }
Run Code Online (Sandbox Code Playgroud)

但是 FsCheck 会为我提供 的各种值PointsData,而不仅仅是其中一位玩家拥有的值Thirty

这意味着points作为函数参数到达的并不能真正代表我感兴趣的测试用例。为了防止意外使用,我隐藏了测试中的值,同时仍然使用输入作为我可以构建的种子实际测试用例值。

在这种情况下,阴影通常很有用。