1

How can we build a function in F# that outputs the name of the variable passed in? For example:

let someVar1 = "x"
getVarname someVar1 //output would be "someVar1"

let someVar2 = "y"
getVarname someVar2 //output would be "someVar2"

let f toString = fun a -> printfn "%s: %d" (toString a) a
let x = 1
f getVarname x      //output would be: "x: 1"

I found a similar question in C# here (get name of a variable or parameter), but I was unable to make it work in F#.

FRocha
  • 942
  • 7
  • 11
  • Have you tried F#5's nameof? https://devblogs.microsoft.com/dotnet/announcing-f-5-preview-1/ – Filipe Carvalho Apr 10 '20 at 16:16
  • I've heard about this function, but I don't know how to use it in a script. It keeps showing an error ("the value or constructor 'nameof' is not defined"), although I have opened Microsoft.FSharp.Core.Operators. I'm using Visual Studio Community 2019 16.5.3. – FRocha Apr 10 '20 at 16:30

2 Answers2

3

If you use quotations and static methods, you can already capture the name of the variable in F# 4 using the ReflectedDefinition attribute. The Demo.GetVarName static method in the following example returns the name of the variable used as an argument together with the value:

open Microsoft.FSharp.Quotations

type Demo = 
  static member GetVarName([<ReflectedDefinition(true)>] x:Expr<int>) = 
    match x with
    | Patterns.WithValue(_, _, Patterns.ValueWithName(value, _, name)) ->
        name, value :?> int
    | _ -> failwithf "Argument was not a variable: %A" x

let test ()= 
  let yadda = 123
  Demo.GetVarName(yadda)

test()

This works for local variables as in the test() function above. For top-level variables (which are actually compiled as properties) you also need to add a case for PropertyGet:

match x with
| Patterns.WithValue(_, _, Patterns.ValueWithName(value, _, name)) -> 
    name, value :?> int
| Patterns.WithValue(value, _, Patterns.PropertyGet(_, pi, _)) -> 
    pi.Name, value :?> int
| _ -> failwithf "Argument was not a variable: %A" x
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Hi, Tomas. Thanks for your explanation! I tried your full solution (including PropertyGet) with the function below: let f = fun a -> sprintf "%s: %d" (a |> Demo.GetVarName |> fst) a; let x = 1; f x. The output is "arg00: 1". – FRocha Apr 10 '20 at 22:04
  • 1
    The issue is that the `|>` operator is inlined and so the compiler probably sees something like `(fun arg00 -> Demo.GetVarName(arg00)) a`. This will only work with direct invocations, i.e. `Demo.GetVarName(a)`. – Tomas Petricek Apr 10 '20 at 22:07
  • After making the correction you suggested now I get "a: 1" as the output, instead of "x: 1". Is it possible to get "x: 1"? – FRocha Apr 10 '20 at 22:18
  • @FRocha No, this won't work with code wrapped in a function - it gives you the name of the variable passed directly as the argument to `Demo.GetVarName`. In other words, if you want to define a method, say `Chart.Plot(whatever)` that gets the variable name of the argument, you'll need to define it in the way shown in my answer. – Tomas Petricek Apr 11 '20 at 19:00
1

The nameof implementation has the operator in F# core, but the F# 5 compiler bits haven't shipped yet. When it does, you can use it to get the name of a symbol.

let someVar1 = None
let name = nameof someVar1 // name = "someVar1"

For now, we can maybe abuse the dynamic operator to get us a shim which you can eventually replace with nameof

let name = ()
let (?) _ name = string name

Usage:

let someVar1 = None
let name = name?someVar1

It doesn't read too bad, and you get some degree of auto-completion.

If you really want to be able to retrieve the local name and value at the call-site, there's quotations.

let printVar = function
|  ValueWithName(value, _type, name) -> printfn "%s = %A" name value 
| _ -> ()

The usage is a bit noisy, though.

let someVar1 = 12
printVar <@ someVar1 @> //prints someVar1 = 12
Asti
  • 12,447
  • 29
  • 38
  • Many thanks, Asti! But suppose we have a function f getName = (fun a -> printfn "%s: %d" (getName a) a) and the user calls it as f getName x (where x is a variable defined somewhere and equals to 1). I would like to get "x: 1" as the output, not "a: 1". How can I use your solution in this case? – FRocha Apr 10 '20 at 17:00
  • 1
    That's not possible with `nameof` either. `nameof` will bind to the local symbol. – Asti Apr 10 '20 at 17:09
  • Thanks! I edited my question and added the example I provided above. – FRocha Apr 10 '20 at 17:21
  • I've added an alternate solution. – Asti Apr 10 '20 at 17:25