I need to compile some f# source files at run-time into an assembly.
At the moment I am using Fsharp.Compiler.CodeDom and it does what I need it to do, however it seems like this library hasn't been maintained.
I would like to know, am I using the right one?
Note: I saw some answers to similar questions to this one, but the questions were a few years old.
This should actually go to John Palmer.... the spinet from his link basically just works
[<EntryPoint>]
let main argv =
printfn "%A" argv
let scs = SimpleSourceCodeServices();
let fn = Path.GetTempFileName();
let fn2 = Path.ChangeExtension(fn, ".fs");
let fn3 = Path.ChangeExtension(fn, ".dll");
File.WriteAllText(fn2, """
module File1
let seven =
3 + 4
""");
let errors1, exitCode1 = scs.Compile([| "fsc.exe"; "-o"; fn3; "-a"; fn2 |])
let ex = File.Exists(fn3)
printfn "File %s exists: %b" fn3 ex
I have to admit it would be cool to see a non fsc.exe option :D
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.
Is there a way to make fsi print the documentation for a function?
I can retrieve the type by just evaluating the function as a value but I would like the know what the xml docs say if they exist.
Without the use case I have 2 suggestions. If this is just about help then use and FSX file.
If you are actually wanting to print out the docs in FSI you could use the following code to print out the docs for a member. Since the xml comments are not stored in the dll, this code checks if the xml doc exists in the location of the dll and loads that in if so.
#r "packages/Newtonsoft.Json/lib/net45/Newtonsoft.Json.dll"
open System.IO
open System.Xml
open System.Reflection
let loadXml (path:string) =
if (File.Exists(path)) then
let xml = new XmlDocument()
xml.Load(path)
Some xml
else None
let xmlForMember (maybeXml:XmlDocument option) (mi:MemberInfo) =
let path = sprintf "M:%s.%s" mi.DeclaringType.FullName mi.Name
match maybeXml with
| None -> None
| Some xml -> xml.SelectSingleNode("//member[starts-with(#name, '" + path + "')]") |> Some
let docFrom (node:XmlNode option) =
match node with
| None -> "No docs available"
| Some n -> n.InnerXml
Usage would be something like this but you could neaten and package this up for your needs:
let t = typedefof<Newtonsoft.Json.JsonSerializer>
let assembly = t.Assembly
let dllPath = assembly.Location
printfn "Assembly location: %s" dllPath
let expectedXmlPath = Path.ChangeExtension(dllPath, ".xml")
printfn "Expected xml: %s" expectedXmlPath
let xmlDoc = expectedXmlPath |> loadXml
let mi = t.GetMember("Create").[0]
let docNode = mi |> xmlForMember xmlDoc
docNode |> docFrom |> printfn "%s"
Hope this can get you started.
I wrote this script from some resources I found. It's working but I some files I have problem. I am new in F# so how can I change line with FileHelpersException to get exact line where is the problem? Thanks
// Learn more about F# at http://fsharp.net
// See the 'F# Tutorial' project for more help.
open FileHelpers
open System
[<DelimitedRecord(",")>]
type CsvRecord =
class
val field1 : string
val field2 : string
val field3 : int
new () = {
field1 = ""
field2 = ""
field3 = 0
}
end
[<EntryPoint>]
let main argv =
use file = System.IO.File.CreateText("result.txt")
let engine = new FileHelperEngine<CsvRecord>()
engine.Encoding <- new Text.UTF8Encoding()
let res =
try
engine.ReadFile("test.csv")
with
| :? FileHelpersException -> Array.empty<CsvRecord>
for record in res do
fprintfn file "%s" record.field1
printf "DONE!"
let s = Console.ReadLine()
0 // return an integer exit code
I suggest that you use CsvTypeProvider instead. When there's a mismatch the error message states the line which has the error
open FSharp.Data
[<EntryPoint>]
let main argv =
use file = System.IO.File.CreateText("result.txt")
let csv = new CsvProvider<"test.csv">()
for record in csv.Data do
fprintfn file "%s" record.field1
If you want to ignore the lines with errors, just pass IgnoreErrors=true as an extra parameter to CsvProvider
This question is about the FileHelpers library you are using, not F#, so looking at the docs for that might help. In this case you can check for ConvertException instead of FileHelpersException, which contains members that give you more details about the member.
try
engine.ReadFile("test.csv")
with
| :? ConvertException as ex ->
printfn "ERROR: Line %d Col %d" ex.LineNumber ex.ColumnNumber
Array.empty<CsvRecord>
I agree with Gustavo though, you might find it easier to use the CsvTypeProvider.
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
I am trying to set the message formatter for a message in F#. In C# I can have:
foreach (System.Messaging.Message message in messages)
{
message.Formatter = new XmlMessageFormatter(new String[] { "System.String,mscorlib" });
string body = message.Body.ToString();
Console.WriteLine(body);
}
which works just fine. I now want to do the same thing in F# and have:
let mList = messageQueue.GetAllMessages()
let xt = [| "System.String,mscorlib" |]
for m in mList do
m.Formatter = XmlMessageFormatter(xt)
which causes this error at compile time:
Error 2 This expression was expected to have type
IMessageFormatter
but here has type
XmlMessageFormatter
I suspect I am missing a basic concept in F#. What am I doing wrong?
--EDIT--
latkin's answer worked perfectly. Just in case anyone else is interested, here is the full working program in F#:
open System.Messaging
[<EntryPoint>]
let main argv =
printfn "%A" argv
let messageQueue = new MessageQueue(".\private$\Twitter")
messageQueue.MessageReadPropertyFilter.SetAll();
let mList = messageQueue.GetAllMessages()
let xt = [| "System.String,mscorlib" |]
for m in mList do
m.Formatter <- XmlMessageFormatter(xt)
printfn "%s " (m.Body.ToString())
0 // return an integer exit code
When you are assigning a mutable value, the operator is <-, not =. In F# = is only used for initial bindings, otherwise it's used as the Boolean equality operator (like C-family ==). Some docs here.
You want
let mList = messageQueue.GetAllMessages()
let xt = [| "System.String,mscorlib" |]
for m in mList do
m.Formatter <- XmlMessageFormatter(xt)
No casting is needed in this case.
The error comes up because the compiler thinks you are trying to compare a IMessageFormatter to a XmlMessageFormatter.
F# doesn't have implicit casts like C# does, so it doesn't automatically upcast your XmlMessageFormatter to the IMessageFormatter used by the Formatter property.
There was a similar question a couple of days ago with more information about this:
F# return ICollection