Search code examples
f#quotations

Injecting a variable definition into F# quotation


I have a custom variable definition, that I want to insert into a quotation. Is it even possible with the quotations syntax sugar?

What I wanted to do:

open Microsoft.FSharp.Quotations
let var = Var("myvar", typeof<int>)
let op = <@@ fun l -> match l with
        | [] -> 0
        | %%myvar :: _ -> ... @@>

I've also tried <@@ let %%myvar = ... @@> with a similar purpose.

In both cases I got FS0010 "Unexpected prefix operator in binding", or "... in pattern matching".

Is there a way to inject an existing Var like this? Or do I have to resort to manually generating the entire expression?

PS: I am using the whole thing to translate some other AST into an F# quotation.


Solution

  • What you describe in your question is really kind of nonsensical. You cannot splice a Var into an expression. Only a value of type Expr can be spliced. If you created an instance of Expr our of your var via the Expr.Var constructor, then the splicing would be possible:

    let var = Expr.Var( Var("myvar", typeof<int>) )
    let op = <@@ fun l -> %%var @@>
    

    But this won't let you do what you're trying to do: you can't splice an expression in a pattern position (the left side of an arrow -> inside a match is what we call a "pattern", and so is the left side of equal sign = inside a let). You can only splice expressions, not other parts of the syntax. F# code quotations are not quite as free-for-all as Lisp macros or TemplateHaskell.

    Admittedly, it is not entirely clear what you're actually trying to do.


    One possibility of your true intent that comes to mind is this: you want to match this variable on the left side of the arrow ->, then pass it to some other function which would construct the right side of the arrow ->. Something like this:

    let mkRightSide var = <@@ %%var + 42 @@>
    
    let var = Expr.Var( Var("myvar", typeof<int>) )
    let op = <@@ fun l -> match l with
                 | [] -> 0
                 | %%var :: _ -> %%(mkRightSide var)   // Doesn't compile
             @@>
    

    Which would yield the following quotation:

    fun l -> match l with
    | [] -> 0
    | myvar :: _ -> myvar + 42
    

    If this is your intent, then I suggest having mkRightSide return a function, which would simply take myvar as a parameter:

    let mkRightSide = <@@ fun myvar -> myvar + 42 @@>
    
    let op = <@@ fun l -> match l with
                 | [] -> 0
                 | (myvar:int) :: _ -> (%%mkRightSide) myvar @@>
    

    The above would yield the following quotation:

    fun l -> match l with
    | [] -> 0
    | myvar :: _ -> (fun myvar -> myvar + 42) myvar
    

    Note 1: the type annotation on myvar is necessary because your quotations are untyped. Since mkRigthSide carries no type information, the compiler can't infer myvar to be int and makes it generic instead, which causes type mismatch when the splicing is attempted.

    Note 2: the parentheses around (%%mkRightSide) are necessary. Without them, the compiler would understand it as %%(mkRightSide myvar), because function application has a higher priority than the %% operator.


    If I am wrong in guessing your intent, please clarify it, and I'll be happy to amend the answer.