I've been playing around with the various ways of linking Fable to existing javascript, and have identified a few places where none of the obvious documents appear to explain what good practice may be. I'd be very happy to attempt to write something up about this, but first I need answers to a few questions ( or pointers to somewhere I've missed which explains.
1 Consider the following:
let iarray = [| 1;2;3 |]
let sarray = [| "hello";"goodbye |]
let marray = [| (box 1); (box "Hello") |]
Browser.console.log(iarray)
Browser.console.log(sarray)
Browser.console.log(marray)
The first surprise is that sarray and marray are both turned into JS arrays but iarray is turned into an Int32Array instance. I'm sure there are good reasons for it, but where is this documented? And when precisely does it happen? I'm guessing its because ints are value types, but maybe I'm wrong.
And is this use of box the preferred way of creating a heterogeneous javascript array, or is there a better syntax?
function boring(a) {
console.log(a);
}
I can access it from F# using emit:
[<Emit("boring($0)")>]
let boring (o:obj) : unit = jsNative
Is this the best idiom, or is there a version of import which can import a function not in a module?
var theStructure = { "myfunc": boring }
theStructure?("boring")(param1,param2,...)
Obviously, one should ideally be mapping these functions onto properly typed F#, and that is indeed my goal wherever possible, but there are cases where just getting things to work is a useful step on the way, and while most such cases are clearly documented in the github for JSInterop or the excellent Medium article, I can't find answers to the above there.
Thanks
Gary
This is an optimization which can be done for some (or all?) numeric types. See the compatibility page. I agree that this isn't 100% clear and there are some issues with this, see #1292. I think if you want to use a regular JS array, then the ResizeArray<_> type (an alias for System.Collections.Generic.List<T>) is the more accurate F# counterpart.
In this case, I guess you can use the Global attribute. See the interacting with JavaScript page.
If I understand you right, you mean something like the following?
open System
type Thing =
abstract member Method : [<ParamArray>] parameters : obj array -> obj
let thing : Thing = ...
thing.Method (1, 2, 3)
thing.Method (1, 2, 3, 4, 5)
Note that we can't use the ParamArray on regular F# functions, it has to be a method on a type. It doesn't matter if the method is static or not.
I hope that helps a little bit.
Thanks @inosik that does help. I realised that the Global attribute would work after rereading the interacting with javascript page - my bad. But I'm a bit confused now how your example for 3., which does indeed do what I want, tie in with the example in the document, which I can't get to work:
type Test() =
[<Emit("$0($1...)")>]
member __.Invoke([<ParamArray>] args: int[]): obj = jsNative
How would you use that, and why?
Thanks for your comments @bilkusg! Any help to improve the docs is much appreciated. I'll try to answer your questions now and if you feel there's something missing in the docs please feel free to send a PR :)
Converting numeric arrays to JS typed arrays is a Fable _feature_. It's mentioned in the Caveats section of the compatibility document, which explains how .NET/F# types are converted to JS. I like to think of it as a selling point as typed arrays are much faster than dynamic JS arrays, but it's true some people find it confusing. There's some discussion in https://github.com/fable-compiler/Fable/issues/433#issuecomment-350062631 where it's also explained how to use the typedArrays: false option to disable the feature.
I'd try to avoid using Emit and use modern JS arrays modules instead with import to access external JS code. The different options for that are explained here. When the function is globally available you can use the Global attribute instead:
[<Global>]
let boring (o:obj) : unit = jsNative
// You can pass an argument to the attribute if the function has a different name in JS
[<Global("boring")>]
let funny (o:obj) : unit = jsNative
To apply an arbitrary number of parameters directly to an object you can use the $ operator (as in foo $ (arg1, arg2)), or the latest DynamicExtensions mentioned at the end of the Dynamic Typing section in the docs:
open Fable.Core.DynamicExtensions
let foo = obj()
let bar1 = foo.["b"] // Same as foo.Item("b")
foo.["c"] <- 14
let bar2 = foo.Invoke(4, "a")
Thanks, that's very helpful . I've tried some tests now, and ResizeArray produces much nastier code than boxing the integers.
I think what I'd like to do is create a documented code sample which more or less demonstrates all these features and publish that. If it proves of interest, I'd then be happy to help incorporate it back into the existing docs in some way.
I'll work on that in the next day or so.
Gary
This can also be a good addition for the REPL, we have some documentation about POJO and bindings.
And also, the REPL should help you see the JavaScript output :)
Sorry, I forgot to tell you that F# also accepts this syntax 馃槄
let marray: obj[] = [| 1; "Hello" |]
Should JsOptionsbe added to this doc?
Yes, it should! I'll update the docs. Thanks for the reminder!
OK. Thanks for all the great info. I've written now a small app which I can run in the repl successfully and which is commented to explain what it does. What's the best way to make it available for people to look at? I'd like to get some feedback before finalising it and perhaps publishing it to the official repl.
Should I set up my own repl on my own server, or just publish to a new github repo, or something else...
Gary
@bilkusg That's great! We want to make it so users can save snippets easily as in http://www.fssnip.net/ but it's not ready yet. For now, the easiest way is to send a PR to the repl repository that puts your files in the public/samples folder and also adds the information about the sample to src/App/Widgets/samples.json.
Please let me know if you have any problem 馃憤
That's sounds great :)
Just to complete @alfonsogarciacaro answer, if you need help to understand the samples.json you can take a look at the readme. It's explain the structure :)
Err. there is no samples.json file in the src directory. Do you mean the one in src/App/Widgets?
I've created my files, added in the lines to src/App/Widgets/samples.json and run
build.cmd ( after updating the .paket directory to fix the github problem )
But after a while I get:
````Running build failed
Error: Could not find a part of the path .....\Fable\Fable\src\dotnet\Fable.Compiler\RELEASE_NOTES.md
while in Target UpdateVersion ==> Download ReplArtifact
````
There is no Fable\Fable\src directory in the path and no Fable.Compiler directory anywhere I can see
Pls advise....
I am fixing the issue and will ping you when it's done :)
@bilkusg Should be fixed in latest version of master branch of the repl repo
OK I will pull again and retry Thanks
OK. so Now it goes as far as Starting Target UpdateVersion
Then it says Can't find Fable
Do you want me to setup it for you
and when I say yes it fails with Failed to add the RSA host key for IP address 192.30.253.113
please make sure you have the correct access rights....
Oh, never mind - I've manually downloaded it using https instead of git: and now it's happy.
I'm then guessing I run build.cmd Watch.App and check it works. Is that right...?
OK. It seems to be working locally. I want to do a few minor clean-ups and should be able to issue a pull request tomorrow.
Yes, downloading it manually fix thing :)
I use git url instead of https because in general people use (should use) ssh when working with git :D But I probably should change this setting or make it configurable. Like using https by default but offering the git url for people who prefer it.
Hmm,
I've hit a problem that some of the examples I've written work fine against the latest Fable, but not against the version of Fable which the repl uses.
I don't really want to be documenting an older version. Is there any plan to update the repl to the latest versions? How do you recommend I proceed?
You should run .paket/paket.exe update to compile the repl with latest Fable version. But not sure if it's supported.
After, the Fable version used by the REPL should be the latest because it's download the latest Fable version master branch on appveyor.
@alfonsogarciacaro am I missing something ?
Just tried that and got a compile error:
MSBuild failed with exitCode 1 ..... at Loader.fableUtils.client.send.then.r in fable-loader\index.js:67:22
So I tried to change the fsproj files to use .net standard 2.0 instead of 1.6 but that gives a whole load of other errors
A bit stuck now....
@ncave Do you something about this problem ?
@bilkusg Could be interesting if you open an issue on the repl repo to track it down :)
OK, so I've removed a couple of examples and it now works in the existing repl. The things which fail are:
type BoringObjType = {
mutable string1: string
int1: int
func1: int->int // this line causes failure
}
You don't need permission to the repo to send a PR.
You need to fork the project, send your branch on your repo. And then if you go to your repo on github, you will see a button Create a pull request.
To create the fork, you can click on the "Fork" button on the top right side of the page (when on the repl repo).
Ah OK. Sorry, I'm new to using git to contribute that way. I think I've now done the pull request.....
Gary
I've pushed the changes requested, and as far as I can tell the pull request is now updated, but since I'm new to this, just checking that there's nothing else I need to do before it can be reviewed again in due course.
Thanks