Question

What can I do to get the following working?

I need to make a function that accepts a Expr list and returns a Expr (Expr list -> Epxr).

type DataObject() =
    let data = System.Collections.Generic.Dictionary<int, obj>()
    member this.AddValue propertyIndex value = data.Add(propertyIndex, value)
    member this.GetValue propertyIndex = 
        match data.TryGetValue propertyIndex with
        | (true, value) -> value
        | (false, _)    -> box "property not found"

...
(fun args -> 
    <@@ 
        let data = new DataObject(values)
        args |> List.iteri (fun i arg -> data.AddValue i <@@ (%%arg) : string @@>)
        data 
    @@>)

I made the DataObject type to add the values of the args in. But somehow I cannot manage to get the code iterating through the different args (args.[0] .. args.[i]). The message I get is:

The variable 'arg' is bound in a quotation but is used as part of a spliced expression. This is not permitted since it may escape its scope.

If I access the args explicitly (args.[0], args.[1], ...) the solution works, but as soon as I try to add the iteration I run into problems. Because the list of args is flexible in length, this is not a feasible solution for me.

I've tried different approaches, but unsuccesful. Is there some solution?

[Edit]

Adding the feedback of Tomas in my solution brings me this:

type DataObject(values: obj []) =
    let propertyMap = new Map<int, obj>(values |> Seq.mapi (fun i value -> (i, value)))
    member this.GetValue propertyIndex : obj = 
        match propertyMap.TryFind propertyIndex with
        | Some(value) -> value
        | None        -> box "property not found"

(fun args ->  
    let boxedArgs = 
        args |> List.map (fun arg -> 
            match arg with
            | Quotations.Patterns.Var var -> 
                if var.Type = typeof<int> then 
                    <@@ (box (%%arg: int)) @@>
                else if var.Type = typeof<string> then 
                    <@@ (box (%%arg: string)) @@>
                else if var.Type = typeof<System.Guid> then 
                    <@@ (box (%%arg: System.Guid)) @@>
                else 
                    failwith ("Aha: " + var.Type.ToString())
            | _ -> failwith ("Unknown Expr as parameter"))
        <@@ new DataObject(%%(Expr.NewArray(typeof<obj>, boxedArgs))) @@>))

And this works! Only thing is that I want to get rid of the if ... else construction to get the right conversion. Any ideas?

Was it helpful?

Solution

This is a tricky question! To understand why your code does not work, you need to clearly distinguish between two levels - at one level (meta), you are composing quotations and at the other level (base) you are running some code using data object.

The reason why your code does not work is that args is a list of expressions at the meta-level and you are trying to iterate over it at the base-level. The iteration needs to happen at the meta-level.

One way to solve this is to do the iteration at the meta-level and generate a list of functions that call AddValue for all arguments. Then you can compose the functions:

(fun args -> 
    // Given arguments [a0; a1; ...] Generate a list of functions:
    //
    //   [ fun data -> data.AddValue 0 a0; data ]
    //   [ fun data -> data.AddValue 1 a1; data ... ]
    args 
    |> List.mapi (fun i arg -> 
      <@ fun (data:DataObject) -> data.AddValue i (%%arg : string); data @>)

    // Compose all the functions just by calling them - note that the above functions 
    // take DataObject, mutate it and then return it. Given [f0; f1; ...] produce:
    //
    //    ... (f1 (f0 (new DataObject())))
    //
    |> List.fold (fun dobj fe -> <@ (%fe) (%dobj) @>) <@ new DataObject() @> )

This is fun to write, but it gets really complicated. In practice, you could make things a lot easier by adding AddValues method to your data object (taking obj[]) and using Expr.NewArray to create a single array (at the base-level) containing the values of all the parameters (from the meta-level):

<@@ let d = new DataObject()
    d.AddValues(%(Expr.NewArray(typeof<obj>, args)))
    d @@>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top