Fable icon indicating copy to clipboard operation
Fable copied to clipboard

Rework Fable.Core API for multi target

Open MangelMaxime opened this issue 2 years ago • 5 comments

Hello while fixing #3484,

I took this opportunity to restrict the different target to their own module.

For example, before it was possible to use the JsInterop.import for importing inside of Python or Rust code.

I believe it is better to have a clear and consistent API for the user.

Indeed, we working with Python it is better if the user can do:

open Fable.Core.Python
open Fable.Core.PyInterop
// or open Fable.Core.PythonInterop

Then he knows that everything he has access to is supported. Instead of letting him believe that he can use jsOptions<'T>, exportDefault (because this if valid F# code) and fail at the Fable compilation stage.

There is still a need to move the Attributes to their own target modules for example Fable.Core.ImportAttribute needs to be duplicated as

  • Fable.Core.JavaScript.ImportAttribute
  • Fable.Core.Rust.ImportAttribute
  • Fable.Core.Python.ImportAttribute

I believe doing a rework of Fable.Core would lead to more solid ground for the future of Fable however there is the problem of the JavaScript target.

Indeed, JavaScript target has a lot of package that use the current API and reworking it would lead to a break change in the ecosystem.

For now, I propose to explore a new API design for JavaScript, Python, Rust, PHP, etc. We would mark all the currently non specific target types / functions with Obselete and perhaps in a next major version remove them completely to have a clean base again.

I will work on a base API proposition so we can discuss it and ask feedback from the community.

MangelMaxime avatar Aug 25 '23 14:08 MangelMaxime

While revisiting https://github.com/fable-compiler/Fable/issues/3482, we thought with @dbrattli that we should probably remove emitJsStatement, emitJsExpress in favor of the JS.js version of that function.

The JS.js version use F# string interpolation which integrate better with F# syntax than using macro syntax like $0, etc.

Same goes for the variation for the other targets.

image

While doing this revisit we should try to find better names than JS.js and Py.python. Also we need to update or fork the VSCode Extension: https://github.com/alfonsogarciacaro/vscode-template-fsharp-highlight which make syntax embedded possible.

I know that @baronfel is also exploring possibilities to support syntax highlighting injection from FSAC but I think this is blocked right now because of missing information from FCS.

MangelMaxime avatar Oct 18 '23 09:10 MangelMaxime

This might be a good opportunity to take a breaking change on the layout of some of those namespaces in general. I found it confusing (and still do tbh) when trying to figure out whether I need Fable.Core.JsInterop or Fable.Core.JS. Particularly when you are looking for something you vaguely remember, like createEmpty etc.

Should the interop not be a subset of JS in the namespace scheme? Also, would this be an opportunity to use Js instead of JS, to more closely align with general convention on two letter acronyms (and languages like Py).

Regarding emitJs.., I'm not too sure about that. I actually like that you get to keep the arguments and the string separate, for the sake of readability. Interpolated strings are not always 'better' this regard. I still often use sprintf for example, over an interpolated string.

Also, there are some differences between expression/statement iirc, would that be normalized into a single form here?

robitar avatar Oct 18 '23 10:10 robitar

Should the interop not be a subset of JS in the namespace scheme? Also, would this be an opportunity to use Js instead of JS, to more closely align with general convention on two letter acronyms (and languages like Py).

This is part of what can be done indeed, but I would prefer to use the fullname of the language. This is because some language don't have a 2 letters abbreviation.

Note: I believe the JS with 2 capital letters is because the language is JavaScript and not Javascript.

This might be a good opportunity to take a breaking change on the layout of some of those namespaces in general.

We can't completely remove the old APIs because it would probably require republishing all the existing Package to NuGet. However, old APIs will be marked as obsolete to encourage a progressive migration.

Regarding the separation between Fable.Core.JsInterop or Fable.Core.JS, I am not sure yet what form it will take. The existing separation makes sense so people can open only Fable.Core.JS and they know that "no magic" is happening in the code.

And if they need to interop with low level code then they need to access the interop module.

Regarding emitJs.., I'm not too sure about that. I actually like that you get to keep the arguments and the string separate, for the sake of readability. Interpolated strings are not always 'better' this regard. I still often use sprintf for example, over an interpolated string.

In general, I think this is better to have a single way of doing things. From my experience, people often had a hard time with some of Fable features because there was severals ways of doing something.

Also, having a single way of doing things means it is easier to maintain and document. For example, I was not aware before this morning that Py.python and JS.js were a thing.

MangelMaxime avatar Oct 18 '23 13:10 MangelMaxime

Re names, I would also prefer to just use JavaScript (and Python etc) rather than the abbreviated forms, its a bit more verbose but more obvious.

Re interop, yes makes sense, it's a more advanced thing, but then it would make even more sense if you are further opening the language specific namespace. Start with say Fable.Core.Language, and then if you need to do low level interactions, open Fable.Core.Languge.Interop, It seems counterintuitive that you would actually open Fable.Core.LanInterop instead, I suspect this is more of a legacy convention.

Re emit, agreed in general that its better to have one way to do the exact same thing, as long as that one way naturally covers all use cases and doesn't become overloaded and cumbersome. We may want to take a look at how other languages/technologies handle(d) this, like old GWT, Script# or JNI etc, if it's going to become more formalized.

I do think we'll need to have at least two different forms for a statement and expression context at least. In the statement context, the compiler does some work to isolate iirc, which is useful, but means it can't be used in every place an expression can be used.

Also, subjective, but I think the function style (emitExpression ...) is quite a good fit for F#, and in keeping with other 'primatives' like printfn etc.

robitar avatar Oct 18 '23 13:10 robitar

Re interop, yes makes sense, it's a more advanced thing, but then it would make even more sense if you are further opening the language specific namespace. Start with say Fable.Core.Language, and then if you need to do low level interactions, open Fable.Core.Languge.Interop, It seems counterintuitive that you would actually open Fable.Core.LanInterop instead, I suspect this is more of a legacy convention.

That's what I have in mind. I really need to start working on a proposition 😅

MangelMaxime avatar Oct 18 '23 13:10 MangelMaxime