How to read Browser.Blob in Fable Elmish using drag / drop - f#

I'm trying to read the Browser.Blob object in fable elmish - I can see it done like this in javascript but I'm not sure how to handle the FileReader onload event.
var b = new Blob(...);
var fr = new FileReader();
fr.onload = function(evt) {
var res = evt.target.result;
console.log("onload",arguments, res, typeof res);
};
fr.readAsArrayBuffer(b);
I'm using a ondrop event to read the file list of items dropped. I can get the file name and get the blob. The function below doesn't work because I get an error on e.target.result but I don't think it would work anyway because it would return the blobstr before it could be set.
How can I read the blob string in this function?
let encodeString (filename:string) (blob:Browser.Blob) () = promise {
try
let mutable blobstr = ""
Console.WriteLine(filename)
let fs = Browser.FileReader.Create()
fs.onload <- (fun e -> blobstr <- e.target.result)
fs.readAsBinaryString(blob)
return Result.Ok (filename,blobstr)
//return Result.Ok (filename,"")
with ex -> return (Error ex.Message)
}
More code from the view and update for context:
View - handling the drag and drop:
div [ Class "card border-primary"
Draggable true
Style [ Height "100px" ]
OnDragOver ( fun e ->
Console.WriteLine("hovering")
e.preventDefault()
NoAction |> dispatch
)
OnDrop ( fun e ->
Console.WriteLine("dropping")
e.preventDefault()
let fileList= e.dataTransfer.files
let files = [0. .. fileList.length - 1.]
|> List.map int
|> List.map (fun x -> fileList.item(float x))
let fileInfos = [
for file in files do
yield file.name, file.slice()
]
FilesLanded fileInfos |> dispatch
)
] []
Update:
| FilesLanded files ->
model, Cmd.batch (
files
|> List.map ( fun (x,y) -> Cmd.ofPromise (encodeString x y ) () FileLanded UnexpectedError )
)
| FileLanded res ->
match res with
| Ok (name,contents) ->
Console.WriteLine(name)
Console.WriteLine(contents)
model, Cmd.none
| Error err ->
{ model with ErrorMessage= err }, Cmd.none
| UnexpectedError ex ->
{ model with ErrorMessage= string ex }, Cmd.none
Thanks

The only way that I was able to get this working was using local storage. It's a bit hacky but it works.
Code is below for anyone who is interested.
View:
...
div [ Class "card border-primary"
Draggable true
Style [ Height "100px" ]
OnDragOver ( fun e ->
Console.WriteLine("hovering")
e.preventDefault()
NoAction |> dispatch
)
OnDrop ( fun e ->
Console.WriteLine("dropping")
e.preventDefault()
let fileList= e.dataTransfer.files
let files = [0. .. fileList.length - 1.]
|> List.map int
|> List.map (fun x -> fileList.item(float x))
let fileInfos = [
for file in files do
yield file.name, file.slice()
]
FilesLanded fileInfos |> dispatch
)
] []
Update:
...
| FilesLanded files ->
model, Cmd.batch (
files
|> List.map ( fun (x,y) -> Cmd.ofPromise (encodeImageToUrl x y ) () FileLanded UnexpectedError )
)
| FileLanded res ->
Console.WriteLine(res)
match res with
| Ok img ->
let impurl = Browser.localStorage.getItem(sprintf "imp_%s" img.FileName).ToString()
printfn "successfully read file: %s - %s" img.FileName img.DisplayUrl
let imgWithUrl = { img with DisplayUrl= impurl }
{ model with ImportingImages= List.append model.ImportingImages [ imgWithUrl ] }, Cmd.none
| Error err ->
{ model with ErrorMessage= err }, Cmd.none
Reading Image:
let encodeImageToUrl (filename:string) (blob:Browser.Blob) () = promise {
try
printf "%s: %f" filename blob.size
let fs = Browser.FileReader.Create()
fs.onload <- (fun e ->
printf "reader returned"
let res= e.target?result
Browser.localStorage.setItem(sprintf "imp_%s" filename, res)
)
fs.readAsDataURL(blob)
//the bloburl is empty at this point, we pull the actual url in the update method from local storage
return Result.Ok { FileName= filename; ImageBlob= blob; DisplayUrl= bloburl }
with ex -> return (Error ex.Message)
}

Related

F# match with query results. Is there an elegant way to do this?

I have an result of a JObject type, from parsing json:
let j = JObject.Parse x
the code I have to do is like:
if j = null then
... do stuff
else if j["aa"] <> null then
... do stuff
else if j["bb"] <> null then
... do stuff
else if j["cc"] <> null and j["dd"] <> null then
... do stuff
is there a clean way to do this match?
doing statements like
| _ when j.["error"] <> null ->
doesn't seem super clean. Can this be done better?
If you create an active pattern that returns the matched JToken...
let (|NonNull|_|) prop (o : JObject) =
o.[prop] |> Option.ofObj
you could write something like:
let handleAA (a : JToken) = ()
match JObject.Parse "{}" with
| null -> () // ...
| NonNull "aa" a -> handleAA a
| NonNull "bb" b & NonNull "cc" c -> ()
| _ -> () // all other
Update
If you need more power, Active Patterns galore...
let (|J|_|) prop (o : obj) =
match o with
| :? JObject as o -> o.[prop] |> Option.ofObj
| _ -> None
let (|Deep|_|) (path : string) (o : obj) =
let get t p = t |> Option.bind (fun t -> (``|J|_|``) p t)
match o with
| :? JToken as t ->
path.Split('.') |> Array.fold get (Option.ofObj t)
| _ -> None
... some helpers ...
let jV (t : JToken) = t.Value<string>()
let handle t = jV t |> printfn "single: %s"
let handle2 a b = printfn "(%s, %s)" (jV a) (jV b)
... a parse function ...
let parse o =
match JsonConvert.DeserializeObject o with
| null -> printfn "null"
| J "aa" a -> handle a
| J "bb" b & J "cc" c -> handle2 b c
| J "bb" b & J "dd" _ -> handle b
| Deep "foo.bar" bar & Deep "hello.world" world -> handle2 bar world
| Deep "foo.bar" bar -> handle bar
| o -> printfn "val: %A" o
... and off we go:
parse "null" // null
parse "42" // val: 42L
parse "{ aa: 3.141 }" // single: 3.141
parse "{ bb: 2.718, cc: \"e\" }" // (2.718, e)
parse "{ bb: 2.718, dd: 0 }" // single: 2.718
parse "{ foo: { bar: \"baz\" } }" // single: baz
parse "{ foo: { bar: \"baz\" }, hello: { world: \"F#|>I❤\" } }" // (baz, F#|>I❤)
To do something for the first non-null value:
let j = JObject.Parse x
let doSomething s = printf "%A" s
if isNull j then
()
else
[ j.["aa"]; j.["bb"]; j.["cc"] ]
|> List.tryFind (fun s -> s |> Option.ofObj |> Option.isSome)
|> doSomething
Or do something for each non-null value:
let j = JObject.Parse x
let doSomething s = printf "%A" s
if isNull j then
()
else
[ j.["aa"]; j.["bb"]; j.["cc"] ]
|> List.choose (fun s -> s |> Option.ofObj)
|> List.iter doSomething
Or do something different (depending on which value is non-null) for the first non-null value:
let j = JObject.Parse x
let doSomethingA s = printf "%A" s
let doSomethingB s = printf "%A" s
let doSomethingC s = printf "%A" s
if isNull j then
()
else
[
j.["aa"], doSomethingA
j.["bb"], doSomethingB
j.["cc"], doSomethingC
]
|> List.tryFind (fun (s, _) -> s |> Option.ofObj |> Option.isSome)
|> Option.iter (fun (s, f) -> f s)
You could create an active pattern to match non-null values...
let (|NonNull|_|) = function null -> None | v -> Some v
...which would allow the following.
if isNull j then
//do stuff
else
match j.["aa"], j.["bb"], j.["cc"], j.["dd"] with
| NonNull aa, _, _, _ -> //do stuff
| _, NonNull bb, _, _ -> //do stuff
| _, _, NonNull cc, NonNull dd -> //do stuff
You could make a list of actions for each key so you could apply the null checking logic uniformly for each one.
let j = JObject.Parse x
let doStuff key value = printfn "%s=>%s" key value
If you wanted to apply doStuff for every key you could iterate though. This is your example but without the else so it does it for every key present.
["aa", doStuff
"bb", doStuff
"cc", doStuff]
|> List.iter (fun (key,action) ->
j.TryGetValue key
|> snd
|> Option.ofObj
|> Option.iter (action key))
Matching your example more closely where you only doStuff for the first key present might use choose to get only the valid values,actions.
["aa", doStuff
"bb", doStuff
"cc", doStuff]
|> Seq.choose (fun (key,action) ->
j.TryGetValue key
|> snd
|> Option.ofObj
|> Option.map (fun v -> action key v))
|> Seq.tryHead
This version also returns the result of the applied doStuff if there was a matching key and doStuff returned a value. This is abusing the lazy nature of Seq a little bit to only call the first value but you could also map to a function an call the result of Seq.tryHead.

How to switch from error track back to success track in railway-oriented program in F#?

Using AsyncResult from Scott Wlashin and wondering how I can change from the error track to the success track.
Pseudo-code:
let orchestratorFunction() : AsyncResult<Customer, CustomerError> = asyncResult {
let! output1 = callFunction1 arg1 arg2 |> AsyncResult.MapError CustomerError.Val1
let! output2 = callFunction2 arg1 arg2 |> AsyncResult.MapError CustomerError.Val2
let! output3 = callFunction3 arg1 arg2 |> AsyncResult.MapError (fun e -> ********HERE I WANT TO GET BACK TO THE SUCCESS PATH AND RETURN output3*********)
}
or a more realistic example:
let createCustomer() : AsyncResult<Customer, CustomerError> = asyncResult {
let! customerDto = mapDtoFromHttpRequest arg1 arg2 |> AsyncResult.MapError CustomerError.Val1
let! validatedCustomer = validateCustomer arg1 arg2 |> AsyncResult.MapError CustomerError.Val2
let! validatedCustomer = insertCustomer arg1 arg2
|> AsyncResult.MapError (fun e ->
match e with
| DuplicateCustomer _ ->
loadCustomerById xx
|> (fun c ->
if c.LastCausationId = validatedCustomer.LastCausationId
then c
else e))
}
So basically I am trying to get out of the unhappy path, because this is an idempotent REST operation and any repetitive requests will be answered with 200 OK, as if they were the original request, so that the client can have a simple logic.
Based on the answer from #Gus to this question (AsyncResult and handling rollback) it seems that bindError is what I needed. I created a similar to his bindError bindExn, and it seems to work as well, so now both error and exns can be converted to Ok:
(From Gus):
/// Apply a monadic function to an AsyncResult error
let bindError (f: 'a -> AsyncResult<'b,'c>) (xAsyncResult : AsyncResult<_, _>) :AsyncResult<_,_> = async {
let! xResult = xAsyncResult
match xResult with
| Ok x -> return Ok x
| Error err -> return! f err
}
(My code):
let bindExn
(f: exn -> AsyncResult<'a,'b>)
(xAsyncResult:AsyncResult<_,_>)
: AsyncResult<'a,'b>
=
async {
let! res =
xAsyncResult
|> Async.Catch
|> Async.map(function
| Choice1Of2 res -> res |> AsyncResult.ofResult
| Choice2Of2 (ex:exn) ->
f ex
)
return! res
}

Using "bind" with an async function

Let's say I have some function that returns Async<Result<string>>:
let getData id = async {
return Ok (string id)
}
Now the input to this function is the result of another function that returns Result<int>.
I'm struggling on how to compose the 2 together with Result.bind inside the async CE.
For example:
let main = async {
let id = Ok 123
let! x = id |> Result.bind getData
return x
}
This doesn't work, I get the error:
error FS0001: Type mismatch. Expecting a
'Result<int,'a> -> Async<'b>'
but given a
'Result<int,'a> -> Result<'c,'a>'
Or if I don't use let! I get and just use let
error FS0001: Type mismatch. Expecting a
'int -> Result<'a,'b>'
but given a
'int -> Async<Result<string,'c>>
I've seen some answers that say don't use Result<'a> and just let the Async exception handling do the hard work, but I face the same problems with Option<'a> and Option.bind.
I know I could use Option.isSome/isNone and/or write my own isOk/isError functions for Result, but I feel I shouldn't have to.
Any ideas on the best way to compose something like this together?
The problem is Result.bind can not be used with getData because the signatures do not match. Result.bind expects a function that produces a Result<> but getData produces an Async<Result<_,_>>. You need a bind for Async<Result<_,_>>.
Define an AsyncResult.bind function for Async<Result<_,_>> like this:
module AsyncResult =
let bind fRA vRA = async {
let! vR = vRA
match vR with
| Ok v -> return! fRA v
| Error m -> return Error m
}
now you can compose your getData function with a function that returns a Result like this:
let composed p = resultFunction p |> async.Return |> AsyncResult.bind getData
If you define a CE for AsyncResult then you can compose it like this:
let composed2 p = asyncResult {
let! id = resultFunction p |> async.Return
return! getData id
}
Here is a full implementation I use for handling Async<Result<>>.
First some useful definitions for Result:
module Result =
open Result
let rtn = Ok
let toOption r = r |> function Ok v -> Some v | _ -> None
let defaultWith f r = r |> function Ok v -> v | Error e -> f e
let defaultValue d r = r |> function Ok v -> v | Error _ -> d
let failIfTrue m v = if v then m |> Error else Ok ()
let failIfFalse m v = if not v then m |> Error else Ok ()
let iter fE f r = r |> map f |> defaultWith fE : unit
let get r = r |> defaultWith (string >> failwith)
let ofOption f vO = vO |> Option.map Ok |> Option.defaultWith (f >> Error)
let insertO vRO = vRO |> Option.map(map Some) |> Option.defaultWith(fun () -> Ok None)
let absorbO f vOR = vOR |> bind (ofOption f)
... and for Async:
module Async =
let inline rtn v = async.Return v
let inline bind f vA = async.Bind( vA, f)
let inline map f = bind (f >> rtn)
let inline iterS (f: 'a->unit) = map f >> Async.RunSynchronously
let inline iterA f = map f >> Async.Start
... and now for AsyncResult:
type AsyncResult<'v, 'm> = Async<Result<'v, 'm>>
module AsyncResult =
let mapError fE v = v |> Async.map (Result.mapError fE)
let rtn v = async.Return(Ok v )
let rtnR vR = async.Return vR
let iterS fE f vRA = Async.iterS (Result.iter fE f) vRA
let iterA fE f vRA = Async.iterA (Result.iter fE f) vRA
let bind fRA vRA = async {
let! vR = vRA
match vR with
| Ok v -> return! fRA v
| Error m -> return Error m
}
let inline map f m = bind (f >> rtn) m
let rec whileLoop cond fRA =
if cond ()
then fRA () |> bind (fun () -> whileLoop cond fRA)
else rtn ()
let (>>=) v f = bind f v
let rec traverseSeq f sq = let folder head tail = f head >>= (fun h -> tail >>= (fun t -> List.Cons(h,t) |> rtn))
Array.foldBack folder (Seq.toArray sq) (rtn List.empty) |> map Seq.ofList
let inline sequenceSeq sq = traverseSeq id sq
let insertO vRAO = vRAO |> Option.map(map Some) |> Option.defaultWith(fun () -> rtn None)
let insertR ( vRAR:Result<_,_>) = vRAR |> function | Error m -> rtn (Error m) | Ok v -> map Ok v
let absorbR vRRA = vRRA |> Async.map (Result.bind id)
let absorbO f vORA = vORA |> Async.map (Result.absorbO f)
Finally, a builder for the CE asyncResult { ... }
type AsyncResultBuilder() =
member __.ReturnFrom vRA : Async<Result<'v , 'm>> = vRA
member __.ReturnFrom vR : Async<Result<'v , 'm>> = AsyncResult.rtnR vR
member __.Return v : Async<Result<'v , 'm>> = AsyncResult.rtn v
member __.Zero () : Async<Result<unit, 'm>> = AsyncResult.rtn ()
member __.Bind (vRA, fRA) : Async<Result<'b , 'm>> = AsyncResult.bind fRA vRA
member __.Bind (vR , fRA) : Async<Result<'b , 'm>> = AsyncResult.bind fRA (vR |> AsyncResult.rtnR)
member __.Combine (vRA, fRA) : Async<Result<'b , 'm>> = AsyncResult.bind fRA vRA
member __.Combine (vR , fRA) : Async<Result<'b , 'm>> = AsyncResult.bind fRA (vR |> AsyncResult.rtnR)
member __.Delay fRA = fRA
member __.Run fRA = AsyncResult.rtn () |> AsyncResult.bind fRA
member __.TryWith (fRA , hnd) : Async<Result<'a , 'm>> = async { try return! fRA() with e -> return! hnd e }
member __.TryFinally(fRA , fn ) : Async<Result<'a , 'm>> = async { try return! fRA() finally fn () }
member __.Using(resource , fRA) : Async<Result<'a , 'm>> = async.Using(resource, fRA)
member __.While (guard , fRA) : Async<Result<unit, 'a>> = AsyncResult.whileLoop guard fRA
member th.For (s: 'a seq, fRA) : Async<Result<unit, 'b>> = th.Using(s.GetEnumerator (), fun enum ->
th.While(enum.MoveNext,
th.Delay(fun () -> fRA enum.Current)))
let asyncResult = AsyncResultBuilder()
[<AutoOpen>]
module Extensions =
type AsyncResultBuilder with
member __.ReturnFrom (vA: Async<'a> ) : Async<Result<'a, 'b>> = Async.map Ok vA
member __.Bind (vA: Async<'a>, fRA) : Async<Result<'b, 'c>> = AsyncResult.bind fRA (Async.map Ok vA)
member __.Combine (vA: Async<'a>, fRA) : Async<Result<'b, 'c>> = AsyncResult.bind fRA (Async.map Ok vA)

F# Compiler Service: get a list of names visible in the scope

How can I get get a list of names visible in the scope with FSC?
I tried this:
#r "../../packages/FSharp.Compiler.Service.16.0.2/lib/net45/FSharp.Compiler.Service.dll"
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.SourceCodeServices
do
let file = "TestFileName.fsx"
let checker = SourceCodeServices.FSharpChecker.Create()
let code =
"""
let testStr = "x"
t
"""
async{
let! options, _ = checker.GetProjectOptionsFromScript(file,code)
let! parseRes,checkAnser = checker.ParseAndCheckFileInProject(file, 0, code, options)
match checkAnser with
| FSharpCheckFileAnswer.Succeeded checkRes ->
let! decls =
checkRes.GetDeclarationListInfo(
Some parseRes, //ParsedFileResultsOpt
3 , //line
1 , //colAtEndOfPartialName
"t" , //lineText
[ "t" ] , //qualifyingNames
"" , //partialName
( fun _ -> [] ) //getAllSymbols: (unit -> AssemblySymbol list)
)
if Seq.isEmpty decls.Items then
printfn "*no declarations found*"
else
decls.Items
|> Seq.sortBy (fun d -> d.Name)
|> Seq.truncate 10
|> Seq.iter (fun d -> printfn "decl: %s" d.Name)
| _ -> failwithf "*Parsing did not finish... "
} |> Async.RunSynchronously
but it only prints "no declarations found". I would expect not only testStr but also all the other names that are available by default.
I did not find an example in the documentation.
qualifyingNames should be an empty list, it’s for dot separated prefix, excluding the last (possibly partial) ident. However, there is no a method in FCS that returns unfiltered list of names for scope, yet it’s really easy to add one.
With the answer of vasily-kirichenko and using the current FCS 17.0.1 I came up with this solution:
#r "../../packages/FSharp.Compiler.Service.17.0.1/lib/net45/FSharp.Compiler.Service.dll"
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.SourceCodeServices
do
let file = "TestFileName.fsx"
let checker = SourceCodeServices.FSharpChecker.Create()
let code =
"""
let testStr = "x"
testStr.
"""
async{
let! options, _ = checker.GetProjectOptionsFromScript(file,code)
let! parseRes,checkAnser = checker.ParseAndCheckFileInProject(file, 0, code, options)
match checkAnser with
| FSharpCheckFileAnswer.Succeeded checkRes ->
let! decls =
let partialName = PartialLongName.Empty 6 //use any location before before the dot to get all declarations in scope
//let partialName = PartialLongName.Empty 7 //use the loacation of the dot (7) to get memebers of string
checkRes.GetDeclarationListInfo(
Some parseRes, // ParsedFileResultsOpt
3 , // line
"testStr." , // lineText
partialName, // PartialLongName
( fun _ -> [] ) // getAllSymbols: (unit -> AssemblySymbol list)
)
if Seq.isEmpty decls.Items then
printfn "*no declarations found*"
else
decls.Items
|> Seq.sortBy (fun d -> d.Name)
|> Seq.truncate 10
|> Seq.iter (fun d -> printfn "decl: %s" d.Name)
| _ -> failwithf "*Parsing did not finish... "
} |> Async.RunSynchronously

Capture exception in Async.Start?

I have the following code. And I want to run without blocking the main thread.
let post () = .....
try
let response = post ()
logger.Info(response.ToString())
with
| ex -> logger.Error(ex, "Exception: " + ex.Message)
So I changed the code to the following. However, how to catch the exception in post?
let post = async {
....
return X }
try
let response = post |> Async.StartChild
logger.Info(response.ToString())
with
| ex -> logger.Error(ex, "Exception: " + ex.Message)
One way is to use Async.Catch in a calling workflow. Given a couple of functions (a throwaway "async" function and something to work with the result):
let work a = async {
return
match a with
| 1 -> "Success!"
| _ -> failwith "Darnit"
}
let printResult (res:Choice<'a,System.Exception>) =
match res with
| Choice1Of2 a -> printfn "%A" a
| Choice2Of2 e -> printfn "Exception: %s" e.Message
One can use Async.Catch
let callingWorkflow =
async {
let result = work 1 |> Async.Catch
let result2 = work 0 |> Async.Catch
[ result; result2 ]
|> Async.Parallel
|> Async.RunSynchronously
|> Array.iter printResult
}
callingWorkflow |> Async.RunSynchronously
Async.Catch returns a Choice<'T1,'T2>. Choice1Of2 for a successful execution, and the exception thrown for the Choice2Of2.
You'd put the try/catch in an async block as well
let post = async { .... }
async {
try
let! response = post
logger.Info(response.ToString())
with
| ex -> logger.Error(ex, "Exception: " + ex.Message)
} |> Async.Start

Resources