4

This is a highly skeletonized version of a financial algorithm. There's a lot more logic in the actual conditions and data processing represented here by "myFunc"--which isn't really intended to make sense in this version.

My question is: how do I understand why the use of firstMultiple in this block is permissible and sensible. Why should it work?

The expression is mapped directly from the line marked A to the line marked C3 where its value is used. But then at C1 and C2--which are (roughly) the same level of "indentation" as C3--here firstMultiple is assigned--seemingly as if it were mutable-??

I guess I don't understand why C3 uses firstMultiple while C1 can seemingly overwrite it. (The debugger, and running it, indicate that it's fine).

I'm hoping this question and example might elicit some insight into how to think about (nested) scopes. (I'm glad of course to have other comments on the design as well, but keep in mind that this code is extremely stripped of a lot of analysis.) (And I may have made the algorithm illogical in my stripping of it, but I'm trying to focus on the question of scope.)

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 
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
RomnieEE
  • 618
  • 3
  • 12
  • are you asking about the difference between shadowing and real mutable variables? – s952163 May 24 '16 at 05:17
  • Thanks for the comments. I guess the issue behind my question is that I haven't learned of the concept of shadowing. So I'm not asking about the difference expressly--as just finding out now that there is a difference. Now I know what to study. Thanks. – RomnieEE May 24 '16 at 12:37
  • Just for my reference: my issue or ignorance is also to the point of why line "B" takes firstMultiple from "C2" and not from "A". Same question. I'll work on it more now. – RomnieEE May 24 '16 at 12:39
  • Of interest: [Overview of F# expressions](https://fsharpforfunandprofit.com/posts/understanding-fsharp-expressions/) – Guy Coder May 28 '16 at 16:31

2 Answers2

3

As this example is basically a loop of

let rec ListBuilder 
    ...
    ListBuilder

the loop hits C3 for every item in outputlist and then when outputlist is empty finally hits C1 and C2

At C3 firstMultiple is only read.

then firstMultiple  // "C3"

At C1 and C2, firstMultiple is bound then read, notice I said bound not set or mutated

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

At C1 firstMultiple is not the same variable as firstMultiple at A or B or C3, it is a new variable. So when you think that it is mutating the firstMultiple at the location of C3 it is not.

If your example conversion is correct then you could convert the code to this:

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

or more simply

[nextInput], inputList.Tail, (nextInput * factor) // "C2"

In this example firstMultiple at C1 and C2 is just a reused variable name and because of shadowing is allowed, but not needed.

EDIT

The OP asked in a comment the further question:

why line "B" takes firstMultiple from "C2" and not from "A".

The reason is that line B is a tuple of three values which are generated by the result of the expressions following line B. All of this are the expressions that creates the three values for the tuple.

    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 

The two lines that generate the three tuple values depending on the match result are

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

and

newOutputList, remInputList, firstMultiple

Line B is not a function with firstMultiple as a parameter, e.g.

let myFunc firstMultiple = ...

it is a match function that returns a tuple of three values

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

firstMultiple is not being passed in via B but being created as a new variable and bound at C1

let firstMultiple = nextInput * factor      // "C1"

and then returned via C2

[nextInput], inputList.Tail, firstMultiple  // "C2"
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
  • Thanks very much. The simple issue is that I wasn't familiar with shadowing, or the difference between bound, set, and mutated. Now I know what to study. It won't be easy to pick one answer between the first two replies, but I'll come back for that later. – RomnieEE May 24 '16 at 12:41
  • Thanks much for the edit. The analysis is very helpful. – RomnieEE May 24 '16 at 16:10
  • One little question, if I could: I'm doing some reading on let binding versus shadowing, also vs mutation--but of the concepts mentioned, I'm not sure what `set` is. As a keyword, I think it's a collection type. As a concept, I'm not sure if it's another distinct thing. Thanks again for the analysis already. – RomnieEE May 24 '16 at 16:43
  • [Set](https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.set-module-%5bfsharp%5d) is an F# [collection](https://msdn.microsoft.com/en-us/visualfsharpdocs/conceptual/microsoft.fsharp.collections-namespace-[fsharp]). – Guy Coder May 24 '16 at 16:46
  • Ok. I was looking at this line above: "notice I said `bound` not `set` or `mutated` " and wasn't sure about the `set` reference there. (All I can think of are VBA and let/set...) – RomnieEE May 24 '16 at 17:35
  • Normally in imperative code they talk about [getting and `setting`](https://msdn.microsoft.com/en-us/library/w86s7x04.aspx) a value. In functional code you have [immutable](https://en.wikipedia.org/wiki/Immutable_object) and mutable values, so you set a mutable, or mutate a mutable, but you bind a value to an immutable. – Guy Coder May 24 '16 at 17:41
2

Inner scopes can capture variables in outer scopes. I assume your question means that you were expecting an error when assigning a new value. There is a difference between shadowing and actual mutable variables. See these two examples.

Here the x is not actual mutable, though it feels like it:

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 

Here x is actually mutable:

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

Try it with testValue [1;2;3]

s952163
  • 6,276
  • 4
  • 23
  • 47
  • Thanks very much for the reply and the examples. I simply wasn't aware of the concept of shadowing. So now I know what to study up on. It won't be easy to pick one answer between the first two replies, but I'll come back for that later. – RomnieEE May 24 '16 at 12:41