Reputation: 3794
My question is this:
How can I splice the expressions from a list into a quotation when I don't know the number and types of those expressions at design time?
At the bottom I've included the full code for a type provider. (I've stripped the concept down to demonstrate the problem.) My issue occurs at these lines:
let func = ProvidedMethod((*...*), InvokeCode = fun args ->
<@@ let stringParts =
args
|> List.mapi (fun i arg ->
if paramTypes.[i] = typeof<int> then
sprintf "%i" (%%arg: int)...
On the lambda parameter arg
, I get the following error:
error FS0446: 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.``
I can't figure out how to write code such that I "extract" the parameter values when the numbers and types of the values are not known at provider-design time (although they will be known at compile time).
When I DO know of a parameter's existence and type at design time, I can do this:
printfn "%A" (%%args.[0]: int)
But I can't figure out how to get from the Expr list
input to an obj list
within the quotation.
Here's the full type provider code:
[<TypeProvider>]
type SillyProviderDefinition(config: TypeProviderConfig) as self =
inherit TypeProviderForNamespaces()
let sillyType = ProvidedTypeDefinition(THIS_ASSEMBLY, NAMESPACE, "SillyProvider", Some typeof<obj>)
do sillyType.DefineStaticParameters([ProvidedStaticParameter("argTypes", typeof<string>)], fun typeName args ->
let retType = ProvidedTypeDefinition(typeName, Some typeof<obj>)
let paramTypes =
(args.[0] :?> string).Split([|'|'|])
|> Array.map (function
| "int" -> typeof<int>
| "string" -> typeof<string>
| x -> failwithf "Invalid argType %A. Only string or int permitted" x)
let parameters =
paramTypes
|> Array.mapi (fun i p -> ProvidedParameter(sprintf "arg%i" i, p))
|> Array.toList
let func = ProvidedMethod("Stringify", parameters, typeof<string>, IsStaticMethod = true, InvokeCode = fun args ->
<@@ let stringParts =
args
|> List.mapi (fun i arg ->
if paramTypes.[i] = typeof<int> then
sprintf "%i" (%%arg: int)
elif paramTypes.[i] = typeof<string> then
(%%arg: string)
else
failwith "Unexpected arg type")
//printfn "%A" (%%args.[0]: int)
String.Join("", stringParts) @@>)
do retType.AddMember func
do sillyType.AddMember retType
retType)
do self.AddNamespace(NAMESPACE, [sillyType])
Upvotes: 3
Views: 183
Reputation: 243051
As a minimal example, let's say that we have list with types and a list with some quotations (in the context of type provider, you have the list of types and args
is the list of quotations, possibly also containing the this
instance):
open Microsoft.FSharp.Quotations
let tys = [ typeof<int>; typeof<string> ]
let args = [ Expr.Value(42); Expr.Value("test"); ]
We want to construct an expression that calls formatInt
or formatString
depending on the type and then concatenates all the formatted strings:
let formatInt (n:int) = string n
let formatString (s:string) = s
Now, it's important to distinguish what happens in the provided quoted code (quote-level) and in the ordinary code that is run to generate the quotation (code-level). At code-level, we iterate over all the types and quoted arguments and generate a list of quotations with calls to formatInt
or formatString
- those can be typed Expr<string>
because they have the same type:
let formattedArgList =
[ for t, e in List.zip tys args ->
if t = typeof<int> then <@ formatInt %%e @>
elif t = typeof<string> then <@ formatString %%e @>
else failwith "!" ]
Now you can build a list expression by calling fold
at code-level and using the list ::
operator at the quote-level:
let listArgExpr =
formattedArgList
|> List.fold (fun state e -> <@ %e::%state @>) <@ [] @>
And now you can construct a quotation with quoted String.concat
call:
<@ String.concat "," %listArgExpr @>
Upvotes: 6