2

If I run the first example from MSDN (https://msdn.microsoft.com/en-us/library/dd233212.aspx) in the F# Interactive window, I get the expected output:

fun (x:System.Int32) -> x + 1
a + 1
let f = fun (x:System.Int32) -> x + 10 in f 10

But if I run it in the Main from my program, all let bindings are replaced by their constant values:

[<EntryPoint>]
let main argv = 

    let a = 2

    // exprLambda has type "(int -> int)".
    let exprLambda = <@ fun x -> x + 1 @>
    // exprCall has type unit.
    let exprCall = <@ a + 1 @>

    println exprLambda
    println exprCall
    println <@@ let f x = x + 10 in f 10 @@>

Result:

fun (x:System.Int32) -> x + 1
2 + 1
let f = fun (x:System.Int32) -> x + 10 in f 10

Is this normal or a bug? Are the rules for this documented? What can I do to force it to the expected output?

Edit:
This answer (https://stackoverflow.com/a/4945137/1872399) states (Variables are automatically replaced with values if the variable is defined outside of the quotation). but I couldn't find any mention of this elsewhere.

Edit 2: What I really want to do
This code (https://gist.github.com/0x53A/8848b04c2250364a3c22) goes into the catch-all case and fails with not implemented:parseQuotation:Value (Variable "ax1") (I expected it to go into | Var(var) ->) so not only constants known at compile-time, but also function parameters are expanded to their values.

Edit 3:
I ran the working version (https://gist.github.com/0x53A/53f45949db812bde5d97) under the debugger, and it looks like that one is actually the bug: The quotation is {Call (None, op_Addition, [PropertyGet (None, a, []), Value (1)])} witha = Program.a, so this seems to be a side-effect of the fact that let bindings in modules are compiled into properties. If I am correct, I should maybe file a doc-bug at Microsoft...

Community
  • 1
  • 1
Lukas Rieger
  • 676
  • 10
  • 31
  • What does your implementation of `println` look like? The example prints the name of variables as indicated by `| Var(var) -> printf "%s" var.Name`. You seem to be substituting the value instead. – Jeff Mercado Jun 09 '15 at 22:44
  • @JeffMercado It is the same implementation. I only moved the call to println into main. Complete: https://gist.github.com/0x53A/393517e955736ea7f4c5 – Lukas Rieger Jun 09 '15 at 22:46
  • Ok I see the same. It look like putting it in a function makes a difference. If the same snippet is in the top level, the variable is left intact. – Jeff Mercado Jun 09 '15 at 22:51

1 Answers1

2

In general, the problem with quoting of variables is that they escape their scope. That is, having a variable foo in your quotation does not really make any sense if you do not have any way to find out what does the foo variable refer to.

So, for example, the following is OK, because the variable x is defined by the lambda:

<@ fun x -> x @>

But if you have something like the following, it does not make sense to capture variable x because once the function returns, x is no longer in scope:

fun x -> <@ x @>

This is the same to the situation you're describing - top-level bindings become static members of a module and so those can be captured, but local variables are not available after the expression evaluates and so they are replaced by values. So, the general rules are:

  • Accessing top-level bindings is quoted as getter of a static member
  • Accessing variables defined inside the quotations is captured as variable access
  • Accessing local variables of a function captures the value of the variable

There are definitely cases where being able to capture the variable name would be useful. One example that I really wanted to be able to do is to let people write for example:

plot(years, GDP)

The idea is that the plot function would get a quotation with variable names (which can then be used e.g. for plot axis). There is actually an F# 4.0 change proposal that lets you do this.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thank you for that explanation, and especially for the link to the F# 4 change. I was initially confused by the MSDN example, because that never mentioned anywhere, that it has anything to do with toplevel / local scope. If I have some time I will try the F# 4 preview to see if that solves my problem. ( The new ValueWithName pattern should) – Lukas Rieger Jun 10 '15 at 14:36