F# PCL and System.IO.FileStream - f#

I am writing a PCL using F# and I am trying to write something to disk. All of the PCL examples are in VB.NET and C# and those examples don't work in F#. Specifically, I have this code:
type FileSystemStockProvider(filePath:string) =
member this.PutData(stockData) =
let serializedData = stockData
|> Seq.map(fun row -> JsonConvert.SerializeObject(row))
let outFile = new System.IO.StreamWriter(filePath)
outFile.Write(serializedData)
The problem is that the System.IO.StreamWriter in F# does not have an overload to disk so it does not compile. Does anyone have a suggestion on how to write to disk using a PCL?
Thanks in advance

Confirmed, the overload you were looking for is missing. However, I don't think this is a mistake, as it is missing from the .net framework for C# and F#. It's not F# specific. The reason I expect is because of the sand boxing requirement on different mobile and cross platform devices. So, the Windows / Linux path name which we are used to doesn't apply universally any more.
See https://pclstorage.codeplex.com/. Use Nuget to retrieve
To reduce the number of steps in getFileStream, probably there is some room to do a little string parsing in a c:\ or a \\networkPath, or even a url based aRootWithAFewReservedWords://the/path/to/the/file.json kind of way.
btw: there was a problem with disposing of the file stream in your code. You need to be explicit and use one of the use / using / dispose techniques. Are you definitely not seeing the overload?
namespace PortableLibrary1
open Newtonsoft.Json
[<JsonObject>]
type TestType() =
[<JsonProperty>]
member val Property2 = "testData" with get, set
type FileSystemStockProvider(filePath:string) =
//let getFileStream folder file =
// // for windows / linux?
// new System.IO.StreamWriter("c:\\" + folder + "\\" + filePath)
let getFileStream folder file =
let rootFolder = PCLStorage.FileSystem.Current.LocalStorage
let folder = rootFolder.CreateFolderAsync(folder,PCLStorage.CreationCollisionOption.OpenIfExists).Result
let file = folder.CreateFileAsync(file, PCLStorage.CreationCollisionOption.ReplaceExisting).Result
file.OpenAsync(PCLStorage.FileAccess.ReadAndWrite).Result
member __.PutData(stockData) =
let fs = getFileStream "theFolder" "theFile.json"
use outFile = new System.IO.StreamWriter(fs)
stockData |> Seq.iter (JsonConvert.SerializeObject >> outFile.Write)

Related

How to use results of FSharp.Compiler.Services

I'm trying to build a system that is similar to FsBolero (TryWebassembly), Fable Repl and many more that uses Fsharp.Compiler.Services.
So I expect it is feasible to achieve my goals but I encountered a problem that I hope is only a result of my lack of experience with that realm of software development
I'm implementing a service that gives user the power to write custom algorithms (DSL) in the context of the domain system.
The code to compile come as a plain raw string that is fully correct F# code.
Sample DSL algorithm looks like:
let code = """
module M
open Lifespace
open Lifespace.LocationPricing
let alg (pricing:LocationPricing) =
let x=pricing.LocationComparisions.CityLevel.Transportation
(8.*x.PublicTransportationStation.Data+ x.RailwayStation.Data+ 5.*x.MunicipalBikeStation.Data) / 14.
"""
that code compiles correctly via CompileToDynamicAssembly. I also provided proper reference to my domain *.dll via -r Fsc parameter.
And here comes my problems as next I have the generated dynamic assembly and want to invoke that algorithm.
I do it with reflection (is there any other way?) with
f.Invoke(null, [|arg|]) when arg is of type LocationPricing and comes from main/hosting project reference.
The Invoke doesn't work because I have error:
Cannot cast LocationPricing to LocationPricing
I had the same problem when tried to use F# interactive services, the error was similar:
Cannot cast [A]LocationPricing to [B]LocationPricing
I'm aware I have two same dlls in the context and F# does have extern alias syntax to solve it.
But other mentioned public systems somehow deals with that or I'm doing it wrongly.
I will look at code of Bolero and FableRepl but it will definately take some time to understand the pitfalls.
Update: Full code (Azure Function)
namespace AzureFunctionFSharp
open System.IO
open System.Text
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Extensions.Http
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.Logging
open FSharp.Compiler.SourceCodeServices
open Lifespace.LocationPricing
module UserCodeEval =
type CalculationResult = {
Value:float
}
type Error = {
Message:string
}
[<FunctionName("UserCodeEvalSampleLocation")>]
let Run([<HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)>] req: HttpRequest, log: ILogger , [<Blob("ranks/short-ranks.json", FileAccess.Read)>] myBlob:Stream)=
log.LogInformation("F# HTTP trigger function processed a request.")
// confirm valid domain dll location
// for a in System.AppDomain.CurrentDomain.GetAssemblies() do
// if a.FullName.Contains("wrometr.lam.to.ranks") then log.LogInformation(a.Location)
// let code = req.Query.["code"].ToString()
// replaced just to show how the user algorithm can looks like
let code =
"""
module M
open Lifespace
open Lifespace.LocationPricing
open Math.MyStatistics
open MathNet.Numerics.Statistics
let alg (pricing:LocationPricing) =
let x= pricing.LocationComparisions.CityLevel.Transportation
(8.*x.PublicTransportationStation.Data+ x.RailwayStation.Data+ 5.*x.MunicipalBikeStation.Data) / 14.
"""
use reader = new StreamReader(myBlob, Encoding.UTF8)
let content = reader.ReadToEnd()
let encode x = LocationPricingStore.DecodeArrayUnpack x
let pricings = encode content
let checker = FSharpChecker.Create()
let fn = Path.GetTempFileName()
let fn2 = Path.ChangeExtension(fn, ".fsx")
let fn3 = Path.ChangeExtension(fn, ".dll")
File.WriteAllText(fn2, code)
let errors, exitCode, dynAssembly =
checker.CompileToDynamicAssembly(
[|
"-o"; fn3;
"-a"; fn2
"-r";#"C:\Users\longer\azure.functions.compiler\bin\Debug\netstandard2.0\bin\MathNet.Numerics.dll"
"-r";#"C:\Users\longer\azure.functions.compiler\bin\Debug\netstandard2.0\bin\Thoth.Json.Net.dll"
// below is crucial and obtained with AppDomain resolution on top, comes as a project reference
"-r";#"C:\Users\longer\azure.functions.compiler\bin\Debug\netstandard2.0\bin\wrometr.lam.to.ranks.dll"
|], execute=None)
|> Async.RunSynchronously
let assembly = dynAssembly.Value
// get one item to test the user algorithm works in the funtion context
let arg = pricings.[0].Data.[0]
let result =
match assembly.GetTypes() |> Array.tryFind (fun t -> t.Name = "M") with
| Some moduleType ->
moduleType.GetMethods()
|> Array.tryFind (fun f -> f.Name = "alg")
|>
function
| Some f -> f.Invoke(null, [|arg|]) |> unbox<float>
| None -> failwith "Function `f` not found"
| None -> failwith "Module `M` not found"
// end of azure function, not important in the problem context
let res = req.HttpContext.Response
match String.length code with
| 0 ->
res.StatusCode <- 400
ObjectResult({ Message = "No Good, Please provide valid encoded user code"})
| _ ->
res.StatusCode <-200
ObjectResult({ Value = result})
**Update: changing data flow **
To move forward I resigned to use domain types in both places. Instead I do all logic in domain assembly and only pass primitives (strings) to reflected invocation. I'm also suprised a lot that caching still works everytime I do compilation on each Azure Function call. I will experiment as well with FSI, in theory it should be faster than reflection but with additional burden to pass parameters to evaluations
In your example, the code that runs inside your dynamically compiled assembly and the code calling it need to share a type LocationPricing. The error you are seeing typically means that you somehow ended up with different assembly loaded in the process that is calling the dynamically compiled code and the code actually running the computation.
It is hard to say exactly why this happened, but you should be able to check whether this is indeed the case by looking at assemblies loaded in the current App Domain. Say that your shared assembly is MyAssembly. You can run:
for a in System.AppDomain.CurrentDomain.GetAssemblies() do
if a.FullName.Contains("MyAssembly") then printfn "%s" a.Location
If you were using F# Interactive Services, then a trick to fix this is to start an FSI session and then send an interaction to the service that loads the assembly from the right place. Something along those lines:
let myAsm = System.AppDomain.CurrentDomain.GetAssemblies() |> Seq.find (fun asm ->
asm.FullName.Contains("MyAssembly"))
fsi.EvalInteraction(sprintf "#r #\"%s\"" myAsm.Location)

Does the >>= operator not take a function?

I'm working on a side project and I'm using Hopac for the first time. I ran into an odd (to me) compilation issue that I haven't been able to grok. I suspect that I'm the problem here, and not Hopac.
The program is supposed to be a simple console app that consumes notifications from various services. Here's the problematic module:
module Provider
open System
open System.IO
open Hopac
open BitThicket.NotificationHelper.Core
open BitThicket.NotificationHelper.Providers
let defaultProviderTypes =
[| typeof<GitHub.GitHubNotificationProvider> |]
type Provider = {
getCh : Ch<Providers.INotification seq>
}
let giveLatest ch latest =
Ch.give
let start config logger (providerType:Type) = Job.delay <| fun () ->
let providerImpl = Activator.CreateInstance(providerType) :?> Providers.INotificationProvider
let p = { getCh = Ch() }
let rec server =
let latest = providerImpl.GetLatestNotificationsAsync(None) |> Job.fromAsync
latest >>= Ch.give p.getCh // error here
}
Job.start server
In this case, the compiler complains: Expecting a type supporting the operator '>>=' but given a function type. You may be missing an argument to a function.
Similarly, if I use a slightly different syntax:
// ...
let rec server =
let latest = providerImpl.GetLatestNotificationsAsync(None) |> Job.fromAsync
latest >>= fun l -> Ch.give p.getCh l // error here
// ...
In this case, the error is: This function takes too many arguments, or is used in a context where a function is not expected.
I asked haf about his in slack, and his suggestion was to check for alternative definitions of >>=. The tooling doesn't really do much to help me figure that one out, but the only namespace/module I have opened that defines >>= is Hopac (the BitThicket ones are just trivially simple namespaces with some type definitions in them).
What am I doing wrong here?
I'm looking at the source code, and I see that the bind operator is actually defined in Hopac.Infixes, not in Hopac.

Infer the type information for any arbitrary CSV files?

I want to use the following console program to get the type information (not the data) of Csv type provider. The file name will be passed as a command line argument. However, it seems the CsvProvider<> only accept constant literal.
Is there a way to workaround it? Or is it possible to do it using F# script?
Or can F# compiler service help?
Or is there any other project does this?
open FSharp.Data
open Microsoft.FSharp.Collections
open System
[<Literal>]
let fn = """C:\...\myfile.csv""" // Want to dynamically set the fn from arguments
[<EntryPoint>]
let main argv =
let myFile = CsvProvider<fn>.GetSample()
// The following doesn't work
let fn = argv.[0]
let myFile = CsvProvider<fn>.GetSample()
// code to get type information of myFile
I think you might be misunderstanding the purpose of the CSV type provider - the idea is that you have a representative sample of your data available at compile time (and can use it to guide the type inference). At runtime, you just give it (possibly a different) file with the same format. This gives you a nice way of handling files with known format.
If you want to parse arbitrary CSV files (with different headers etc.) then CSV type provider won't help. However, you can still use the CsvFile type from F# Data which provides a simple CSV parser. Example from the documentation:
// Download the stock prices
let msft = CsvFile.Load("http://ichart.finance.yahoo.com/table.csv?s=MSFT")
// Print the prices in the HLOC format
for row in msft.Rows do
printfn "HLOC: (%s, %s, %s)" (row.GetColumn "High")
(row.GetColumn "Low") (row.GetColumn "Date")
Here, you loose the nice static typing, but you can load file with any format (and then dynamically look at the columns that were available in the file).
Suggested by Tomas, the following F#-Data CSV provider function can be used to resolve the issue.
let data = CsvFile.Load(....)
let inferredProperties =
// InferColumnTypes : inferRows:int
// * missingValues:string []
// * cultureInfo:CultureInfo
// * schema:string
// * assumeMissingValues:bool
// * preferOptionals:bool
// * ?unitsOfMeasureProvider:IUnitsOfMeasureProvider
// -> PrimitiveInferedProperty list
data.InferColumnTypes(10000, [|""|], CultureInfo.InvariantCulture, "", false, true)
Not sure what the parameters should be used. But the above settings seem work OK.

FS2024 Static linking error when PCL project use by TypeProvider

It's trying to make a TypeProvider for Xamarin.Forms, but has been plagued by FS2024 error.
Parse own library from the XAML of Xamarin.Forms
Assign x:Name to Propertis
`F#
type MainPage = Moonmile.XamarinFormsTypeProvider.XAML<"MainPage.xaml">
// made btn1 and text1 propertis
type MainPageEx(target:MainPage) =
let mutable count = 0
do
// When set event to btn.Clicked, happen FS2024 error.
// If this event is comment out, it success build.
target.btn1.Clicked.Add( fun e ->
count <- count + 1
target.btn1.Text <- "Clicked " + count.ToString())
// Property is success
member this.CurrentPage
with get() = target.CurrentPage
When you are referring to a property, build & operation you can normally.
But the internal class of Xamarin.Forms like Button.Clicked, If you try to access to, it is the build error.
Sample code for error
https://github.com/moonmile/SimpleEventTypeProvider
Making code for XamarinFormsTypeProvider
github.com/moonmile/XamarinFormsTypeProvider
Maybe, I suspect inconsistencies and is happening in the part of the generation of a Native TypeProvider and Xamrin.Forms.Core a PCL.
F# Compiler for F# 3.1 (Open Source Edition)
Freely distributed under the Apache 2.0 Open Source License my error!!!
isMscorlib: true
name: "System.Runtime"
PrimaryAssembly.DotNetCore.Name: "System.Runtime"
PrimaryAssembly.Mscorlib.Name: "mscorlib"
parameter error FS2024: Static linking may not use assembly that targets different profile.
It's to operate the property they work properly, and to MVVM perhaps.
Butt I am trying to implement a way to be assigned to Button.Clicked events
as shown in the codebehide-like buildings if possible.
Would there workaround or what?
In the case of XAML in WPF, How can such seems to work well.
github.com/fsprojects/FsXaml
This answer isn't guaranteed to be correct, but it should help at least point you in the right direction.
The first thing to do is to make sure that you have installed the latest Visual F# Tools Build, as this adds the FSharp.Core that is compatible with the PCL profiles (You can find it here: (https://visualfsharp.codeplex.com/). Once that is installed, you will want to reference either the Profile78, or Profile259 FSharp.Core.dll (On my machine, these are found at: "C:\Program Files (x86)\Reference Assemblies\Microsoft\FSharp.NETPortable\2.3.5.0", and "C:\Program Files (x86)\Reference Assemblies\Microsoft\FSharp.NETPortable\2.3.5.1" respectively).
Once you have that installed, the next thing to do is make sure that your PCL projects have the following in their project files (This tells MSBuild / xBuild that the projects are PCL libraries, and that they are F# Projects):
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{F2A71F9B-5D33-465A-A702-920D77279786}</ProjectTypeGuids>
Once that is done, you will need to select either Profile78, or Profile259 (I would recommend 78, as the current Xamarin.Forms nuget package doesn't support 259).
Once that is done, then you should be able to build and run and it should get rid of the error.
Thank you for my question.
Meybe,
When it build TypeProvider, F# compiler use classes in mscorlib.
When it resolve the type of btn1.Clicked event, the F# comiler use type in System.Runtime.
I think for that, and it can not be resolved at build time
Try, if you attach the Clicked Event using reflection, it has moved successfully on Android build through.
I seem, if it I use only shard classes in mscorlib and System.Rutime, I can build no FS2024 error.
type MainPage = Moonmile.XamarinFormsTypeProvider.XAML<"MainPage.xaml">
type MainPageEx() as this =
inherit BindObject<MainPage>(new MainPage())
// Add handlder by reflection
let AddHandler(target:obj, eventName:string, eventMethod: obj*obj -> unit ) =
let hdr = Action<obj,obj>( fun s e -> eventMethod(s,e))
let ei = target.GetType().GetRuntimeEvent(eventName)
let dt = ei.AddMethod.GetParameters().[0].ParameterType
let handler = new Action<obj,obj>(fun s e -> hdr.Invoke( s, new EventArgs() ))
let handlerInvoke = handler.GetType().GetRuntimeMethod("Invoke", [|typeof<obj>; typeof<Type[]>|])
let dele = handlerInvoke.CreateDelegate( dt, handler )
let add = new Func<Delegate, EventRegistrationToken> ( fun t ->
let para = ei.AddMethod.GetParameters()
let ret = ei.AddMethod.Invoke( target, [|t|])
if ret <> null then
ret :?> EventRegistrationToken
else
new EventRegistrationToken()
)
let remove = new Action<EventRegistrationToken>( fun t -> ei.RemoveMethod.Invoke(target, [|t|]) |> ignore )
// WindowsRuntimeMarshal.AddEventHandler<Delegate>(add, remove, dele)
add.Invoke( dele ) |> ignore
()
let mutable count = 0
do
(* // build error
target.btn1.Clicked.Add( fun e ->
count <- count + 1
target.btn1.Text <- "Clicked " + count.ToString())
*)
// add handler by reflection
AddHandler( base.Target.btn1, "Clicked", this.ButtonClick )
()
member this.CurrentPage
with get() = this.Target.CurrentPage
member this.ButtonClick(s,e) =
count <- count + 1
base.Target.text1.Text <- "clicked " + count.ToString()

The field, constructor or member 'AsyncReadToEnd' is not defined error

I am learning RX (Reactive Extensions), and have found someone posted some code nearly one year ago using F# and RX to make a simple webCrawler. I tried to see if I can re-use the code. I download RX, and create a F# windows application, add reference to System.Reactive. My IDE is VS 2010 Ultimate, RX version is: 1.1.11111. The following is the code:
#light
open System
open System.Linq
open System.Collections.Generic
open System.Net
open System.IO
open System.Threading
open System.Text.RegularExpressions
open System.Reactive
open System.Reactive.Linq
let create f =
Observable.Create<_>(fun x ->
f x
new System.Action((fun () -> ())))
let ofAsync async =
create (fun obs -> Async.StartWithContinuations(async, obs.OnNext,obs.OnError,obs.OnError))
let fromEvent (event:IEvent<_,_>) = create (fun x -> event.Add x.OnNext)
let tickEvent = new Event<unit> ()
let tickEventObs = tickEvent.Publish |> fromEvent
let fetch(url:string) =
async { let req = WebRequest.Create(url)
let! resp = req.AsyncGetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let! html = reader.AsyncReadToEnd()
return html
} |> ofAsync
But the code can not get compiled, I got the error message:
Error 1 The field, constructor or member 'AsyncReadToEnd' is not defined
So the error was on this line:
let! html = reader.AsyncReadToEnd()
I guess there could be some changes for the past one year in RX or F#.
For my current environment, what is the correct way to re-write the above code?
Thanks and happy new year to you all!
John
AsyncReadToEnd() extension method of StreamReader is part of FSharpPowerPack now. Install FSharpPowerPack from this link, if not yet, then add reference to FSharp.PowerPack to your project. This should make AsyncReadToEnd()method accessible from the rest of your code.
Use the dedicated AsyncStreamReader type
AsyncReadToEnd() extension method does not exists anymore in the FSharp.PowerPack.
It has been replaced with the AsyncStreamReader dedicated type that contains proper asynchronous implementation of stream reading (like ReadToEnd, ReadLine, etc.)
It can be used like that:
async {
use asyncReader = new AsyncStreamReader(stream)
return! asyncReader.ReadToEnd() }
Note: Once you have installed FSharp.PowerPack, the AsyncStreamReader type will be 'injected' in the Microsoft.FSharp.Control namespace
Other related answer: https://stackoverflow.com/a/7925440/1480391

Resources