Running the following in a dotnet core 3.1 console application fails to compile the assembly:
module Test
open FSharp.Compiler.SourceCodeServices
open FSharp.Compiler.Text
let source = """module Test
type TestType() =
interface System.IAsyncDisposable with
member __.DisposeAsync() =
System.Threading.Tasks.ValueTask()
"""
let references =
[
"Microsoft.Bcl.AsyncInterfaces"
]
[<EntryPoint>]
let main(args) =
let checker = FSharpChecker.Create()
let options =
{ FSharpParsingOptions.Default with
SourceFiles = [|"empty.fs"|]
}
let sourceText = SourceText.ofString source
let parseResult =
checker.ParseFile("empty.fs", sourceText, options) |> Async.RunSynchronously
let input = parseResult.ParseTree.Value
let thisAssembly =
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name
let allReferences =
thisAssembly :: references
let errors, _, assemblyOpt =
checker.CompileToDynamicAssembly([input], "TestProject", allReferences, None)
|> Async.RunSynchronously
0
assemblyOpt is None and the errors array contains the following messages:
The type 'IAsyncDisposable' is not defined in 'System'. Maybe you want one of the following:
IDisposable
The type 'obj' is not an interface type
The type 'IAsyncDisposable' is not defined in 'System'. Maybe you want one of the following:
IDisposable
The type 'IAsyncDisposable' is not defined in 'System'. Maybe you want one of the following:
IDisposable
This type is not an interface type
No abstract or interface member was found that corresponds to this override
The value, constructor, namespace or type 'ValueTask' is not defined.
p_tyar_spec: from error
p_tyar_spec: from error
However, both Microsoft.Bcl.AsyncInterfaces, and System should contain IAsyncDisposable. What's causing the missing reference error?
I suspect its the assembly list is incomplete in some way, you can confirm this by altering the dynamic assembly generation function to the following:
checker.CompileToDynamicAssembly([input], "TestProject", allReferences, None, noframework = true)
noframework = true
Which works, (I just tested it)
Related
When I move UiRoutes module into a separate file it gives me an error when compiling. This is the first time using Saturn and Giraffe so I'm not sure what is happening? What is the difference having the module in a separate file?
The error I get is:
"Value restriction. The value 'uiRouter' has been inferred to have generic type\n val uiRouter : (HttpFunc -> '_a -> HttpFuncResult) when '_a :> AspNetCore.Http.HttpContext \nEither make the arguments to 'uiRouter' explicit or, if you do not intend for it to be generic, add a type annotation."
Program.fs
open Saturn
// MongoDB.FSharp.Serializers.Register()
module ApiRoutes =
open Giraffe
let apiRoutes =
router {
//Routers in here
}
module UiRoutes =
open Giraffe
open Giraffe.ViewEngine
open DBApi
let carsView =
html [] [
//Some HTML
]
let uiRouter = htmlView carsView //HttpFunc -> AspNetCore.Http.HttpContext -> HttpFuncResult
let appRouter =
router {
forward "/api" ApiRoutes.apiRoutes
forward "" UiRoutes.uiRouter
}
let myApp = application { use_router appRouter }
run myApp
Solution:
//Changed
let uiRouter = htmlView carsView
//to:
let (uiRouter: HttpFunc -> Microsoft.AspNetCore.Http.HttpContext -> HttpFuncResult) = htmlView carsView
F# compiles each file separately. When you move the definition of uiRouter into a different file from the one where it's used, the F# compiler can no longer infer that it has a non-generic type. In other words, when compiling UiRoutes.fs, the compiler can't tell that you want uiRouter to have the exact type HttpFunc -> AspNetCore.Http.HttpContext -> HttpFuncResult. Since F# doesn't allow uiRouter to have a generic value, you get the value restriction error.
Here are two ways to avoid this situation:
Ensure that uiRouter is constrained to a non-generic type by a subsequent use of the value in the same file where it is defined.
Provide an explicit type annotation to manually constrain the type of uiRouter.
See similar SO question here.
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.
So, I was trying to get this simple test working in an F# console app:
open System.Reflection
open System.ComponentModel.Composition
open System.ComponentModel.Composition.Hosting
[<Export(typeof<int -> string>)>]
let toString(i: int) = i.ToString()
[<EntryPoint>]
let main argv =
use catalog = new AssemblyCatalog(Assembly.GetEntryAssembly())
use container = new CompositionContainer(catalog)
let myFunction = container.GetExportedValue<int -> string>()
let result = myFunction(5)
0
I expected MEF to get the function properly resolved, but it doesn't.
Instead, I get this:
An unhandled exception of type
'System.ComponentModel.Composition.CompositionContractMismatchException'
occurred in System.ComponentModel.Composition.dll
Additional information:
Cannot cast the underlying exported value of type 'Program.toString (ContractName="Microsoft.FSharp.Core.FSharpFunc(System.Int32,System.String)")' to type 'Microsoft.FSharp.Core.FSharpFunc``2[System.Int32,System.String]'.
What am I missing here?
What is the difference between FSharpFunc(System.Int32, System.String) and FSharpFunc``2[System.Int32, System.String]?
What is the correct way to import/export F# functions via MEF?
The compiler turns top-level F# functions into methods, so your example will be compiled as:
[Export(FSharpFunc<int,string>)]
public string toString(int i) { return i.ToString(); }
This is probably causing the error. You can force the compiler to produce a property getter of FSharpFunc type by calling some operation that returns a function - even a simple identity function will don:
let makeFunc f = f
[<Export(typeof<int -> string>)>]
let toString = makeFunc <| fun (i:int) ->
i.ToString()
I have not tested this, but I think it could work. That said, it is probably safer to go with a simple single-method interface in this case.
In my first attempt to create a type provider, I have a ProvidedTypeDefinition for a message:
// Message type
let mTy = ProvidedTypeDefinition(asm, ns, message.Key, Some(typeof<ValueType>),
HideObjectMethods = true, IsErased = false)
// Direct buffer
let bufferField = ProvidedField("_directBuffer", typeof<IDirectBuffer>)
mTy.AddMember bufferField
let mCtor1 =
ProvidedConstructor(
[ProvidedParameter("buffer", typeof<IDirectBuffer>)],
InvokeCode = fun args ->
match args with
| [this;buffer] ->
Expr.FieldSet (this, bufferField, <## %%buffer:IDirectBuffer ##>)
| _ -> failwith "wrong ctor params"
)
mTy.AddMember mCtor1
Then I need to create an instance of that type in a method of another provided type. I am doing this:
let mMethod = ProvidedMethod(message.Key, [ProvidedParameter("buffer", typeof<IDirectBuffer>)], mTy)
mMethod.InvokeCode <- (fun [this;buffer] ->
let c = mTy.GetConstructors().Last()
Expr.NewObject(c, [ buffer ])
)
ILSpy shows the following C# code equivalent for the method:
public Car Car(IDirectBuffer buffer)
{
return new Car(buffer);
}
and it also shows that the Car struct is present in the test assembly (this test assembly builds OK unless I access the Car method):
But when I try to create the Car via the method like this:
type CarSchema = SbeProvider<"Path\to\SBETypeProvider\SBETypeProvider\Car.xml">
module Test =
let carSchema = CarSchema()
let car = carSchema.Car(null)
I get the following errors:
The module/namespace 'SBETypeProvider' from compilation unit 'tmp5CDE' did not contain the namespace, module or type 'Car'
A reference to the type 'SBETypeProvider.Car' in assembly 'tmp5CDE' was found, but the type could not be found in that assembly
What I am doing wrong? The picture shows that the type is here. Why I cannot create it?
I looked through many type providers on GitHub and cannot find a clear example how to generate a ProvidedTypeDefinition from another one.
This might not be the problem, but at a quick glance it looks like the line you linked might actually be the issue:
let mTy = ProvidedTypeDefinition(asm, ns, message.Key, Some(typeof<ValueType>),
HideObjectMethods = true, IsErased = false)
This type is being added to the ty provided type (the one that will actually be written to the temporary assembly) and so shouldn't have the assembly and namespace specified itself.
let mTy = ProvidedTypeDefinition(message.Key, Some(typeof<ValueType>),
HideObjectMethods = true, IsErased = false)
Might work better. Generated types are a bit of a black art though, with very little documentation, so it's possible (probable?) that there will be other issues you might find.
On a more general note, for creating provided types what I normally end up doing is returning the provided constructor as a value which can then be embedded in the invoke code for other properties/functions using Expr.Call. This is especially important for erased types, as reflection will not work on them anyway.
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.