Search code examples
functionf#parametersquotations

How to get a name of a variable coming into a function as a parameter using F#?


Is there any way in F# how to get a name of a variable passed into a function?

Example:

let velocity = 5
let fn v = v.ParentName
let name = fn velocity // this would return "velocity" as a string

Thank you in advance

EDIT:

Why this code does not work? It is matched as value, so I can not retrieve the "variable" name.

type Test() =
  let getName (e:Quotations.Expr) =
    match e with
      | Quotations.Patterns.PropertyGet (_, pi, _) -> pi.Name + " property"
      | Quotations.Patterns.Value(a) -> failwith "Value matched"
      | _ -> failwith "other matched"
  member x.plot v = v |> getName |> printfn "%s"

let o = new Test()

let display () =
  let variable = 5.
  o.plot <@ variable @>

let runTheCode fn = fn()

runTheCode display

Solution

  • For completing Marcelo's answer, yes you can use quotations for this task:

    open Microsoft.FSharp.Quotations
    open Microsoft.FSharp.Quotations.Patterns
    
    let velocity = 5
    
    let fn (e:Expr) =
      match e with
        | PropertyGet (e, pi, li) -> pi.Name
        | _ -> failwith "not a let-bound value"
    
    let name = fn <@velocity@> 
    
    printfn "%s" name
    

    As you can see in the code, F# let-bound top definition values (functions or variables) are implemented as properties of a class.

    I can't find anymore the link that shows how a piece of F# code could be rewritten in a functional way with C#. Seeing the code, it becomes obvious why you need a PropertyGet pattern.

    Now if you want to evaluate the expression too, you will need to install F# powerpack and reference FSharp.PowerPack.Linq in your project.

    It adds an EvalUntyped method on Expr class..

    open Microsoft.FSharp.Linq.QuotationEvaluation
    
    let velocity = 5
    
    let fn (e:Expr) =
      match e with
        | PropertyGet (eo, pi, li) -> pi.Name, e.EvalUntyped
        | _ -> failwith "not a let-bound value"
    
    let name, value = fn <@velocity@> 
    
    printfn "%s %A" name value
    

    If you need to do it for the method of an instance, here's how I would do it:

    let velocity = 5
    
    type Foo () =
      member this.Bar (x:int) (y:single) = x * x + int y
    
    let extractCallExprBody expr =
      let rec aux (l, uexpr) =
        match uexpr with
         | Lambda (var, body) -> aux (var::l, body)
         | _ -> uexpr
      aux ([], expr)
    
    let rec fn (e:Expr) =
      match e with
        | PropertyGet (e, pi, li) -> pi.Name
        | Call (e, mi, li) -> mi.Name
        | x -> extractCallExprBody x |> fn
        | _ -> failwith "not a valid pattern"
    
    let name = fn <@velocity@> 
    printfn "%s" name
    
    let foo = new Foo()
    
    let methodName = fn <@foo.Bar@>
    printfn "%s" methodName
    

    Just to come back on the code snippet showing usage of EvalUntyped, you can add an explicit type parameter for Expr and a downcast (:?>) if you want/need to keep things type-safe:

    let fn (e:Expr<'T>) = 
      match e with
        | PropertyGet (eo, pi, li) -> pi.Name, (e.EvalUntyped() :?> 'T)
        | _ -> failwith "not a let-bound value"
        
    let name, value = fn <@velocity@> //value has type int here
    printfn "%s %d" name value