12

I would like to know if there is a way to compile a code quotation into an assembly?

I understand that it is possible to call CompileUntyped() or Compile() on the Exp<_> object, for example:

let x = <@ 1 * 2 @>
let com = x.Compile()

However, how can I persist com to disk as an assembly?

Thanks.

Ncc
  • 167
  • 7
  • 2
    I am not sure if PowerPack supports this. But as an aside, I would not advise using PowerPack at all. Their code is often buggy or too slow. Writing your own compiler or evaluator from scratch using System.Reflection.Emit would probably give better results. There is also the problem that F# does not optimize the quotations, for example matching becomes a series of if/then/else, instead of the jump instruction it is in CIL in normal F# compilation. – t0yv0 Aug 06 '11 at 16:25
  • Hi, thanks for this, I guess I will look into alternatives them (such as Reflection.Emit as you mentionned). – Ncc Aug 07 '11 at 20:28
  • You might find this useful: http://stackoverflow.com/questions/2682475/converting-f-quotations-into-linq-expressions and http://stackoverflow.com/questions/1618682/linking-a-net-expression-tree-into-a-new-assembly – JPW Aug 18 '11 at 16:57
  • possible duplicate of [F# equivalent to Eval](http://stackoverflow.com/questions/2608114/f-equivalent-to-eval) – N_A Mar 19 '13 at 18:09

1 Answers1

12

You can evaluate an F# Code Quotations with pattern matching:

open Microsoft.FSharp.Quotations.Patterns

let rec eval  = function
    | Value(v,t) -> v
    | Call(None,mi,args) -> mi.Invoke(null, evalAll args)
    | arg -> raise <| System.NotSupportedException(arg.ToString())
and evalAll args = [|for arg in args -> eval arg|]

let x = eval <@ 1 * 3 @>

See the F# PowerPack, Unquote, Foq OSS projects or this snippet for more complete implementations.

To compile an F# Code Quotation you can define a dynamic method using Reflection.Emit:

open System.Reflection.Emit

let rec generate (il:ILGenerator) = function
    | Value(v,t) when t = typeof<int> ->
        il.Emit(OpCodes.Ldc_I4, v :?> int)
    | Call(None,mi,args) -> 
        generateAll il args
        il.EmitCall(OpCodes.Call, mi, null)
    | arg -> raise <| System.NotSupportedException(arg.ToString())
and generateAll il args = for arg in args do generate il arg

type Marker = interface end

let compile quotation =
    let f = DynamicMethod("f", typeof<int>, [||], typeof<Marker>.Module)
    let il = f.GetILGenerator()
    quotation |> generate il
    il.Emit(OpCodes.Ret)
    fun () -> f.Invoke(null,[||]) :?> int

let f = compile <@ 1 + 3 @>
let x = f ()

To compile to an assembly again use Reflection.Emit to generate a type with a method:

open System
open System.Reflection

let createAssembly quotation =
    let name = "GeneratedAssembly"
    let domain = AppDomain.CurrentDomain
    let assembly = domain.DefineDynamicAssembly(AssemblyName(name), AssemblyBuilderAccess.RunAndSave)
    let dm = assembly.DefineDynamicModule(name+".dll")
    let t = dm.DefineType("Type", TypeAttributes.Public ||| TypeAttributes.Class)
    let mb = t.DefineMethod("f", MethodAttributes.Public, typeof<int>, [||])
    let il = mb.GetILGenerator()
    quotation |> generate il
    il.Emit(OpCodes.Ret)
    assembly.Save("GeneratedAssembly.dll")

createAssembly <@ 1 + 1 @>

See the Fil project (F# to IL) for a more complete implementation.

Phillip Trelford
  • 6,513
  • 25
  • 40
  • 1
    Has the implementation of https://github.com/eiriktsarpalis/QuotationCompiler obviated the need to use Reflection as above to compile the Code Quotation. This implementation seems to use the Microsoft.FSharp.Compiler namespace heavily which perhaps was not previously accessible... – Sam Apr 30 '15 at 19:43
  • 1
    @Sam good point Eirik's Quotation Compiler is probably the way to go now – Phillip Trelford May 01 '15 at 06:17