F#,尝试理解表达式的范围 - 这应该有效吗?

4
这是一个高度简化的金融算法版本。实际条件和数据处理中有更多逻辑被 "myFunc" 表示,但在这个版本中并不打算使其有意义。
我的问题是:我该如何理解在此块中使用 firstMultiple 是允许且合理的。为什么它应该起作用?表达式直接从标记为 A 的行映射到使用其值的标记为 C3 的行。但是,在与 C3 大致相同的“缩进”级别的 C1C2 中,在这里似乎将 firstMultiple 分配为可变的-??。
我想我不明白为什么 C3 使用 firstMultiple,而 C1 可以看似地重写它。(调试器和运行结果表明这是可以的)。
我希望这个问题和例子能引出一些关于如何思考(嵌套)范围的见解。(当然,我也很高兴听到关于设计的其他评论,但请记住,这段代码被极度剥离了很多分析。)(我可能在剥离它时使算法变得不合逻辑,但我试图专注于范围的问题。)
let rec ListBuilder factor firstMultiple useFirstMultiple inputList outputList = // "A"    
    match inputList with
    | [] -> []
    | h::[] -> List.rev (inputList.Head :: outputList) 
    | _ ->
        let nextInput = inputList.Head
        let newOutputList, remInputList, firstMultiple =    // "B"
            match outputList with
            | [] ->  //first pass, capture firstMultiple now
                let firstMultiple = nextInput * factor      // "C1" 
                [nextInput], inputList.Tail, firstMultiple  // "C2"
            | _ ->                                             
                let lastOutput = outputList.Head
                let multiple = 
                    if useFirstMultiple then firstMultiple  // "C3"
                                        else lastOutput * factor
                let newOutputList =
                    if (myfunc multiple nextInput: bool) then  
                        nextInput :: outputList
                    else
                        outputList
                let remInputList =
                    if not (myfunc multiple nextInput: bool) then     
                        inputList.Tail
                    else
                        inputList
                newOutputList, remInputList, firstMultiple 
        ListBuilder factor firstMultiple useFirstMultiple remInputList newOutputList 

你是在询问影子变量和真实可变变量之间的区别吗? - s952163
感谢您的评论。我想我提出问题背后的问题是我还没有学习到阴影概念。所以我并不是在明确地问有什么区别,而只是现在才发现有区别。现在我知道该学什么了。谢谢。 - RomnieEE
仅供参考:我的问题或无知也在于为什么行“B”从“C2”中获取firstMultiple,而不是从“A”中获取。同样的问题。我现在会更加努力地解决它。 - RomnieEE
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Guy Coder
2个回答

3
作为这个示例基本上是一个循环,
let rec ListBuilder 
    ...
    ListBuilder

循环针对outputlist中的每个项目命中C3,当outputlist为空时最终命中C1C2
C3处仅读取firstMultiple
then firstMultiple  // "C3"

C1C2处,firstMultiple首先被绑定,然后读取,注意我说的是绑定而不是设置改变

let firstMultiple = nextInput * factor      // "C1" 
[nextInput], inputList.Tail, firstMultiple  // "C2"

C1 中,firstMultiple 不是与 ABC3 中的 firstMultiple 相同的变量,它是一个新的变量。因此,当你认为它正在改变位于 C3firstMultiple 时,实际上并不是这样的。
如果您的示例转换正确,则可以将代码转换为以下内容:
let temp = nextInput * factor      // "C1"
[nextInput], inputList.Tail, temp  // "C2"

更简单地说
[nextInput], inputList.Tail, (nextInput * factor) // "C2"

在这个例子中,C1C2中的firstMultiple只是一个被重复使用的变量名,并且由于存在阴影效应而被允许,但并不需要。
编辑
OP在评论中进一步提出了问题:
为什么行“B”从“C2”中获取firstMultiple而不是从“A”中获取?
原因是行B是一个由跟随行B的表达式生成的三个值的元组。所有这些都是为元组创建三个值的表达式。
    let newOutputList, remInputList, firstMultiple =    // "B"
        match outputList with
        | [] ->  //first pass, capture firstMultiple now
            let firstMultiple = nextInput * factor      // "C1" 
            [nextInput], inputList.Tail, firstMultiple  // "C2"
        | _ ->                                             
            let lastOutput = outputList.Head
            let multiple = 
                if useFirstMultiple then firstMultiple  // "C3"
                                    else lastOutput * factor
            let newOutputList =
                if (myfunc multiple nextInput: bool) then  
                    nextInput :: outputList
                else
                    outputList
            let remInputList =
                if not (myfunc multiple nextInput: bool) then     
                    inputList.Tail
                else
                    inputList
            newOutputList, remInputList, firstMultiple 

生成三元组值的两行代码取决于匹配结果,它们是:
[nextInput], inputList.Tail, firstMultiple  // "C2"

并且

newOutputList, remInputList, firstMultiple

使用 firstMultiple 作为参数,第 B 行不是一个函数,例如:

let myFunc firstMultiple = ...

这是一个匹配函数,返回一个包含三个值的元组。

let newOutputList, remInputList, firstMultiple =    // "B"

firstMultiple并没有通过B传递进来,而是作为一个新变量在C1处绑定了。

let firstMultiple = nextInput * factor      // "C1"

然后通过 C2 返回

[nextInput], inputList.Tail, firstMultiple  // "C2"

非常感谢。问题很简单,我不熟悉“阴影处理”或者绑定、设置和变异之间的差别。现在我知道该学习什么了。在前两个回答中选择一个并不容易,但是我稍后会回来做出决定。 - RomnieEE
非常感谢您的编辑。分析非常有帮助。 - RomnieEE
有一个小问题,如果可以的话:我正在阅读关于let绑定与遮蔽、以及变异的一些内容——但是在提到的概念中,我不确定set是什么。作为一个关键字,我认为它是一种集合类型。作为一个概念,我不确定它是否是另一种独立的东西。再次感谢您的分析。 - RomnieEE
Set 是 F# 集合 的一种。 - Guy Coder
好的。我在看上面那行代码:"注意我说的是bound而不是set或者mutated",对于那里的set参考我不太确定。(我能想到的只有VBA和let/set...) - RomnieEE
通常在命令式编码中,他们谈论获取和设置值。在函数式代码中,你有不可变和可变的值,所以你设置可变的或改变可变的,但是将值绑定到不可变的。 - Guy Coder

2

内部作用域可以捕获外部作用域的变量。我猜你的问题是指当赋一个新值时,你原本期望会出现一个错误。但是,遮蔽和实际可变变量之间存在差异。请看以下两个示例。

在这里,x并不是真正的可变变量,尽管它看起来像是:

let testValue (l: int list) =
    let x = l.[0]
    printfn "%A" x
    do
        let x= l.[1]
        printfn "%A" x
    printfn "%A" x
    let x = l.[2]
    printfn "%A" x 

在这里,x实际上是可变的:

let testValue2 (l: int list) =
    let mutable x = l.[0]
    printfn "%A" x
    x <- l.[1]
    printfn "%A" x

使用 testValue [1;2;3] 进行尝试


非常感谢您的回复和示例。我之前并不知道阴影概念,现在我知道该学习什么了。在前两个回复中选择一个答案并不容易,但我稍后会回来做出决定。 - RomnieEE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接