Does the >>= operator not take a function? - f#

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.

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)

Calling ES 3rd party script method from F# Fable

I'm trying to get Fable to compile the following code correctly but am unable to do so:
module AppView
#r "../../../node_modules/fable-core/Fable.Core.dll"
open Fable.Core
open Fable.Import.Browser
open Fable.Core.JsInterop
[<Import("default", from="../../../js/3rd/riot.js")>]
module riot_js =
let mount:((string*obj)->array<obj>) = jsNative
type App
(
tagName:string
,state
,store
) =
member public x.AppTag =
(riot_js?mount ("app", state))
// does not compile: The value or constructor 'riot_js' is not defined.
// (riot_js.mount ("app", state))
// compiles wrongly to: riot_js.mount(["app", this.state]);
Trying riot_js?mount would magically cause riot_js to not exist any more and trying riot_js.mount compiles into riot_js.mount(["app", this.state]);.
Mount does not take one argument but 2 but it either won't transpile or transpile wrong.
For now I have one of the strangest looking solutions:
[<Emit("riot_js")>]
let riot_js (x: int): obj = jsNative
...
((riot_js 1)?mount ("app", state))
This returns an array but again Fable does not let me take the first element in a "normal" way:
((riot_js 1)?mount ("app", state))?[0]
Gives me red on [ with error Unexpected symbol '[' in expression. Expected identifier, '(' or other token.
And
((riot_js 1)?mount ("app", state)).[0]
Gives red on everything with error The field, constructor or member 'Item' is not defined.
The following "works"
((riot_js 1)?mount ("app", state))?``0``
And is compiled to:
riot_js.mount("app", this.state)["0"];
Not the best result someone can get. I'll let this question sit for a while and set a bounty on it for a week or so before opening 2 issues with Fable.
The following seems to compile to the right ES and does not need the ? so it'll be strongly typed.
open Fable.Core.JsInterop
type Riotjs =
{
mount:(System.Func<string,obj,string []>)
}
let riot = (importAll<obj> "../js/3rd/riot.js") :?> Riotjs
let app = riot.mount.Invoke("app",(createObj []))
I set initial state to type obj but can use a strong typed application state as well.
ES generated is:
export var app = riot.mount("app", {});

Is there a way to use let statements in classes with inline methods?

A simplified example that gives an error because let str is private:
let launch = printfn "%s"
type Test() =
let str = "Hello"
member inline t.A() =
launch str
I've discovered statically resolved type parameters in F# and after a rewrite have a neural net library where 95% of the functions are inlined as a result, including class methods. I wrote it as a F# script at first, forgetting that there is a difference between script mode and compiled mode in their treatment of inlined methods.
let test = // Is this the only choice?
let str = "Hello"
fun () ->
launch str
Is there any way to use body initializers in F# classes or should I rewrite the classes to be higher order functions like the above? Thankfully, that would not be a problem here.
This question is related to this one, but I thought I'd ask again since it has been 5 years.
Actually I was wrong. It is true that the higher order function example does work in the simplified case above, but I've realized that lambda arguments cannot be statically resolved. I thought of using records for a bit and then tried this:
type Test() =
let str = "Hello"
member t.Str = str
member inline t.A() =
launch t.Str
Private member can be exposed and this will compile. Doing it like the above would be satisfactory.

What is the syntax for resolving Thenable<'t> in F# using fable?

I'm working on a vscode extension written in F# using Fable to compile to javascript. Many of the api's return a promise. What is the syntax for for resolving a promise that have return types such as Thenable<string[]> for F#?
Here is an example of many of the api's for vscode: vscode api
Take a look at how Ionide does it:
https://github.com/ionide/ionide-vscode-helpers/blob/fable/Helpers.fs
https://github.com/ionide/ionide-vscode-helpers/blob/fable/Fable.Import.VSCode.fs
Basically, it looks like Ionide almost ignored the existence of Thenable<T> and converted every API call to a Promise<T> in their Fable bindings. They do have a pair of toPromise and toThenable functions in Helpers.fs, but I don't see those being used anywhere in the entire https://github.com/ionide/ionide-vscode-fsharp repository.
I don't have any personal experience with Fable, so if this isn't enough to answer your question, hopefully someone else will chime in with more information.
After some playing with the syntax, I was able to figure it out with the clue that rmunn gave with converting the Thenable to Promise.
module PromiseUtils =
let success (a : 'T -> 'R) (pr : Promise<'T>) : Promise<'R> =
pr?``then`` (unbox a) |> unbox
let toPromise (a : Thenable<'T>) = a |> unbox<Promise<'T>>
let toThenable (a : Promise<'T>) = a |> unbox<Thenable<'T>>
Using the utility module above, I was able to convert the functions that return Thenable to Promises so they could be resloved.
let result = commands.getCommands ()
|> PromiseUtils.toPromise
|> PromiseUtils.success (fun item ->
let firstOne = item.Item 1
console.log(firstOne))

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