How do I execute F# code from a string in a compiled F# program?
Here's a little script that uses the FSharp CodeDom to compile a string into an assembly and dynamically load it into the script session.
It uses a type extension simply to allow useful defaults on the arguments (hopefully let bound functions will support optional, named and params arguments in the near future.)
#r "FSharp.Compiler.dll"
#r "FSharp.Compiler.CodeDom.dll"
open System
open System.IO
open System.CodeDom.Compiler
open Microsoft.FSharp.Compiler.CodeDom
let CompileFSharpString(str, assemblies, output) =
use pro = new FSharpCodeProvider()
let opt = CompilerParameters(assemblies, output)
let res = pro.CompileAssemblyFromSource( opt, [|str|] )
if res.Errors.Count = 0 then
Some(FileInfo(res.PathToAssembly))
else None
let (++) v1 v2 = Path.Combine(v1, v2)
let defaultAsms = [|"System.dll"; "FSharp.Core.dll"; "FSharp.Powerpack.dll"|]
let randomFile() = __SOURCE_DIRECTORY__ ++ Path.GetRandomFileName() + ".dll"
type System.CodeDom.Compiler.CodeCompiler with
static member CompileFSharpString (str, ?assemblies, ?output) =
let assemblies = defaultArg assemblies defaultAsms
let output = defaultArg output (randomFile())
CompileFSharpString(str, assemblies, output)
// Our set of library functions.
let library = "
module Temp.Main
let f(x,y) = sin x + cos y
"
// Create the assembly
let fileinfo = CodeCompiler.CompileFSharpString(library)
// Import metadata into the FSharp typechecker
#r "0lb3lphm.del.dll"
let a = Temp.Main.f(0.5 * Math.PI, 0.0) // val a : float = 2.0
// Purely reflective invocation of the function.
let asm = Reflection.Assembly.LoadFrom(fileinfo.Value.FullName)
let mth = asm.GetType("Temp.Main").GetMethod("f")
// Wrap weakly typed function with strong typing.
let f(x,y) = mth.Invoke(null, [|box (x:float); box (y:float)|]) :?> float
let b = f (0.5 * Math.PI, 0.0) // val b : float = 2.0
To use this in a compiled program you would need the purely reflective invocation.
Of course this is a toy compared to a full scripting API that many of us in the community have urgently requested.
best of luck,
Danny
Are you looking for an Eval function?
You might want to try looking at this blog post:
http://fsharpnews.blogspot.com/2007/02/symbolic-manipulation.html
If you read in your expressions into these kind of symbolic datastructures, they are pretty easy to evaluate.
Or, perhaps you are looking for scripting support:
http://blogs.msdn.com/chrsmith/archive/2008/09/12/scripting-in-f.aspx
If you really want dynamic compilation, you could do it with the F# CodeDom provider.
There has been movement on this front. You can now compile using the FSharp.Compiler.Service
simple sample using FSharp.Compiler.Service 5.0.0 from NuGet
open Microsoft.FSharp.Compiler.SimpleSourceCodeServices
let compile (codeText:string) =
let scs = SimpleSourceCodeServices()
let src,dllPath =
let fn = Path.GetTempFileName()
let fn2 = Path.ChangeExtension(fn, ".fs")
let fn3 = Path.ChangeExtension(fn, ".dll")
fn2,fn3
File.WriteAllText(src,codeText)
let errors, exitCode = scs.Compile [| "fsc.exe"; "-o"; dllPath; "-a";src; "-r"; "WindowsBase"; "-r" ;"PresentationCore"; "-r"; "PresentationFramework" |]
match errors,exitCode with
| [| |],0 -> Some dllPath
| _ ->
(errors,exitCode).Dump("Compilation failed")
File.Delete src
File.Delete dllPath
None
then it's a matter of Assembly.LoadFrom(dllPath) to get it into the current app domain.
followed by reflection based-calls into the dll (or possibly Activator.CreateInstance)
Sample LinqPad Usage
Related
A couple days ago, I posted a question about deserialization with enums in F#.
The question is here: Deserialization in F# vs. C#
The answer pointed to some code written by Isaac Abraham, at: https://gist.github.com/isaacabraham/ba679f285bfd15d2f53e
However I am facing another problem:
If the object to deserialize to has an object of type 'enum option', the deserialization will fail, whereas it'll work if the type is just 'enum'.
A minimal example:
type TestType =
| A = 0
| B = 1
type TestObjectA =
{
test : TestType
}
type TestObjectB =
{
test : TestType option
}
let x = "{\"test\":\"A\"}"
let TestA = Deserialize<TestObjectA> x // will work
let TestB = Deserialize<TestObjectB> x // will fail
and the large deserialization code is at: https://pastebin.com/95JZLa6j
I put the whole code in a fiddle: https://dotnetfiddle.net/0Vc0Rh
but it can't be run from there since the F# version they support will not accept the 'object' keyword.
So, my question is: why can't I use the option type on an enum, but it works on other types? As a side note, since I'm quite new to F#, I'm not fully understanding Isaac's code, although I spent some time going through it and trying to troubleshoot it.
My understanding is that this line:
|> Seq.map (fun (value, propertyInfo) -> Convert.ChangeType(value, propertyInfo.PropertyType))
will try to convert the type to the right enum, but not to the enum option.
As a bonus question, is there a working solution that does full idiomatic deserialization with enums? (without going through null types)
open System.IO
type TestType =
| A = 0
| B = 1
type TestObjectB =
{
test : TestType option
}
let jsonSerializeToString obj =
use writer = new StringWriter()
let ser = new Newtonsoft.Json.JsonSerializer()
ser.Formatting <- Newtonsoft.Json.Formatting.Indented
ser.Serialize(writer, obj)
writer.ToString()
let jsonDeserializeFromString str =
Newtonsoft.Json.JsonConvert.DeserializeObject<TestObjectB>(str)
let Test obj =
let str = jsonSerializeToString obj
let obj' = jsonDeserializeFromString str
obj'
[<EntryPoint>]
let main argv =
{ test = Some TestType.B } |> Test |> ignore
{ test = None } |> Test |> ignore
0
Note: if you need to serialize a large collection of objects, then stream them to a file instead of an in-memory string to avoid an OutOfMemoryException. Like use writer = File.CreateText(filePath).
As a bonus question, is there a working solution that does full
idiomatic deserialization with enums?
I use the Microsoft.FsharpLu.Json package in production and find it works quite well for serializing and deserializing between "plain" javascript and idiomatic F#. Note Microsoft.FsharpLu.Json relies on Newtonsoft.Json under the hood.
Below is an example with your types and your test string, using Expecto for tests.
namespace FsharpLuJsonTest
open Newtonsoft.Json
open Microsoft.FSharpLu.Json
open Expecto
open Expecto.Flip
// Setup for FSharpLu.Json
type JsonSettings =
static member settings =
let s = JsonSerializerSettings(
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore)
s.Converters.Add(CompactUnionJsonConverter())
s
static member formatting = Formatting.None
type JsonSerializer = With<JsonSettings>
// Your example
type TestType =
| A = 0
| B = 1
type TestObjectA = { test : TestType }
type TestObjectB = { test : TestType option }
module Tests =
let x = """{"test":"A"}"""
[<Tests>]
let tests =
testList "Deserialization Tests" [
testCase "To TestObjectA" <| fun _ ->
JsonSerializer.deserialize x
|> Expect.equal "" { TestObjectA.test = TestType.A }
testCase "To TestObjectB" <| fun _ ->
JsonSerializer.deserialize x
|> Expect.equal "" { TestObjectB.test = Some TestType.A }
]
module Main =
[<EntryPoint>]
let main args =
runTestsInAssembly defaultConfig args
As you can see FsharpLu.Json supports Discriminated Unions and option types out of the box in the way you prefer. FsharpLu.Json is a less flexible solution than some others like Chiron (which allow for much more customisation) but I tend to prefer the opinionated approach of FsharpLu.Json.
I haven't used it personally, but the new FSharp.SystemText.Json library with the JsonUnionEncoding.ExternalTag setting should work roughly the same way FsharpLu.Json does. That library uses Microsoft's new System.Text.Json library under the hood rather than Newtonsoft.Json.
This is my very first attempt at F#, I know I still need to read a lot and I will, but these challenges help me understand the next parts I will read better, so far I managed to solve some of the first challenges, so please be patient......
The xml document looks like this:
<BuildCollection xmlns="http://schemas.datacontract.org/2004/07/BuildModels" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
...
<PostEvents xmlns:a="http://schemas.datacontract.org/2004/07/BuildModels.Events">
<a:EventBase i:type="a:ExternalProcess">
<a:Description>Structuring assemblies</a:Description>
How do I access the elements that start with "a:......."?
In C# I would do it like this:
foreach (var postBuildEventElement in
document
.Root.Element(ns + "PostEvents")
.Elements()
.Where(_ => _.Name.LocalName == "EventBase"))
So far in F# I have this:
module PostBuildInstructions
open System.Xml.Linq
open System.Linq
let xn s = XName.Get(s)
let PostBuildInstructions (xdoc: System.Xml.Linq.XDocument) =
xdoc.Root.Elements(xn "PostEvents").Elements().Where(e => e.)
let ExecutePostBuildInstructions buildDescriptor =
let "I need to enumerate the post build events here"
In the "main" code block I started with this: (I'm focusing on line 6 with this question)
open Log
[<EntryPoint>]
let main argv =
TryLogArgs argv
ReadPostBuildInstructions |> ExecutePostBuildInstructions
log "Press enter to exit"
System.Console.ReadLine() |> ignore
0 // return an integer exit code
Your use of the xn function is not equivalent to the use in the C# version. Your xn function returns a name with no associated namespace. However you're dealing with names within namespaces you you should be generating names as such.
Your C# version should ideally be like this:
XNamespace ns = "http://schemas.datacontract.org/2004/07/BuildModels";
XNamespace a = "http://schemas.datacontract.org/2004/07/BuildModels.Events";
var query = doc.Root
.Elements(ns + "PostEvents")
.Elements(a + "EventBase");
The F# version shouldn't be that much different:
let ns = XNamespace.Get("http://schemas.datacontract.org/2004/07/BuildModels")
let a = XNamespace.Get("http://schemas.datacontract.org/2004/07/BuildModels.Events")
let query = doc.Root.Elements(ns + "PostEvents").Elements(a + "EventBase")
But you may want to keep them as function calls. Just make sure you associate with the correct namespaces for the functions.
let xn n = XName.Get(n)
let ns n = XName.Get(n, "http://schemas.datacontract.org/2004/07/BuildModels")
let a n = XName.Get(n, "http://schemas.datacontract.org/2004/07/BuildModels.Events")
let query = doc.Root.Elements(ns "PostEvents").Elements(a "EventBase")
my goal is to simply output a javascript file containing my translated F# library. Nothing more.
I have an empty solution to which I added two F# projects. One is a library called WSLib with a single file:
namespace WSLib
[<ReflectedDefinition>]
type Class1() =
member this.X = "F#"
[<ReflectedDefinition>]
module Foo =
let bar = 34
The other project is a console app and references the WebSharper and WebSharper.Compiler NuGet packages. It has a single file. I copied the first half of the code from http://www.fssnip.net/snippet/rP.
module Program
open Microsoft.FSharp.Quotations
open WebSharper
type AR = IntelliFactory.Core.AssemblyResolution.AssemblyResolver
module FE = WebSharper.Compiler.FrontEnd
let compile (expr: Expr) : string option =
let loader = FE.Loader.Create (AR.Create()) (eprintfn "%O")
let options =
{ FE.Options.Default with
References =
List.map loader.LoadFile [
// These contain the JavaScript implementation for most of the standard library
"WebSharper.Main.dll"
"WebSharper.Collections.dll"
"WebSharper.Control.dll"
"WSLib.dll"
// Add any other assemblies used in the quotation...
] }
let compiler = FE.Prepare options (sprintf "%A" >> System.Diagnostics.Debug.WriteLine)
compiler.Compile expr
|> Option.map (fun e -> e.ReadableJavaScript)
[<JavaScript>]
let main() =
let a = WSLib.Class1().X
let b = WSLib.Foo.bar
(a,b)
let code =
match (compile <# main() #>) with
|None -> failwith "parse failed"
|Some x -> x
open System.IO
let filePath = Path.Combine(System.Environment.CurrentDirectory, "index.js")
File.WriteAllText(filePath, code)
I get a couple of errors:
{Location = {ReadableLocation = "main";
SourceLocation = null;};
Priority = Error;
Text = "Failed to translate property access: X [WSLib.Class1].";}
{Location = {ReadableLocation = "main";
SourceLocation = null;};
Priority = Error;
Text = "Failed to translate property access: bar [WSLib.Foo].";}
What do I need to do to get the websharper compiler working with different projects? I get the same error if I include the WebSharper package on WSLib and replace ReflectedDefinition with JavaScript.
What happens here is that adding WSLib.dll to the compiler references will only make it look for WebSharper metadata in that assembly, if there is any; but WSLib needs to be WebSharper-compiled already. For this to happen, you need to reference WebSharper in WSLib (as you did) and add the following property to the project file:
<WebSharperProject>Library</WebSharperProject>
to instruct WebSharper that it does have to compile this assembly.
I am trying to use the Math.NET numerics implementation of the FFT algorithm, but I must be doing something wrong because the output is always unit
The following is the the setup:
open MathNet.Numerics
open MathNet.Numerics.Statistics
open MathNet.Numerics.IntegralTransforms
let rnd = new Random()
let rnddata = Array.init 100 (fun u -> rnd.NextDouble())
let x = rnddata |> Array.Parallel.map (fun d -> MathNet.Numerics.complex.Create(d, 0.0) )
then when I run this:
let tt = MathNet.Numerics.IntegralTransforms.Fourier.BluesteinForward(x, FourierOptions.Default)
I receive an empty output below?
val tt : unit = ()
Any ideas why?
I think the Fourier.BluesteinForward method stores the results in the input array (by overwriting whatever was there originally).
If you do not need the input after running the transform, you can just use x and read the results (this saves some memory copying, which is why Math.NET does that by default). Otherwise, you can clone the array and wrap it in a more functional style code like this:
let bluesteinForward input =
let output = Array.copy input
MathNet.Numerics.IntegralTransforms.Fourier.BluesteinForward
(output, FourierOptions.Default)
output
From
#r "FSharp.Data.TypeProviders"
#r "System.ServiceModel"
open Microsoft.FSharp.Data.TypeProviders
[<Literal>]
let serviceAddress = "http://localhost/Microsoft/Dynamics/GP/eConnect/mex"
type Dynamics = WsdlService<serviceAddress>
type DynTypes = Dynamics.ServiceTypes.SimpleDataContextTypes
type Address = System.ServiceModel.EndpointAddress
No matter what I do WSDL type provider can't disambiguate the function call:
let svc: DynTypes.eConnectClient = Dynamics.GeteConnectServiceEndpoint()
let svc2 = (Dynamics.GeteConnectServiceEndpoint : unit -> DynTypes.eConnectClient)()
let svc3 = (Dynamics.GeteConnectServiceEndpoint : Address -> DynTypes.eConnectClient)(Address serviceAddress)
None of them works.
Disabling the other Endpoints and leaving only the one for eConnectClient solves the problem, but I don't even know if I may end up needing the other endpoints.
Not familiar with the schema or the type provider, but overloads are not supported by WSDL standard. If the WSDL is generated at runtime from the implementation (as is often the case), the runtime might produce such an invalid WSDL.
I was able to get past this problem by using reflection to find the method I wanted to invoke, and invoked it dynamically.
let noteServiceType = typedefof<NoteService>
let creatorMethod =
noteServiceType.GetMethods()
|> Seq.filter (fun staticMethod ->
staticMethod.Name = "GetCustomBinding_IIntakeNoteManager"
&& staticMethod.ReturnType = typedefof<NoteService.ServiceTypes.SimpleDataContextTypes.IntakeNoteManagerClient>
&& staticMethod.GetParameters().Length = 0)
|> Seq.toList
let creatorMethod = creatorMethod |> Seq.head
let client = creatorMethod.Invoke(null, [||]) :?> NoteService.ServiceTypes.SimpleDataContextTypes.IntakeNoteManagerClient