It seems that many bindings, particularly the Electron bindings (but perhaps also the Browser bindings IIRC), are auto-generated and not particularly F# idiomatic. I'm thinking about type safety as well as other stuff (e.g. avoiding explicit callbacks in favour of async
/ promise
).
A couple of examples:
-
When using electron.remote.dialog.showOpenDialog
, it returns a ResizeArray<string>
that is undefined
if the user cancelled the dialog. The most F# idiomatic API here would be to return string list option
or string array option
, or at the very least a ResizeArray<string> option
if it has to be ResizeArray
for performance reasons.
-
fs.readFile
reads a file asynchronously and takes an explicit continuation callback. In F# it is more natural to use a CE (async
/promise
).
-
Some cases of obj
could be replaced with new types. For example, the property OpenDialogOptions.filters
(in the Electron bindings) is of type ResizeArray<obj> option
. According to the showOpenDialog documentation, this property is an array of FileFilter
which is a simple and well defined type.
-
Some cases of string
could be replaced with a StringEnum
. For example, the property OpenDialogOptions.properties
is of type ResizeArray<string> option
, but only supports a defined set of values, making a StringEnum
suitable.
-
The APIs are accessed through global objects (electron.remote.dialog.showOpenDialog
). In F# it would be more natural to access them through similarly named modules (Electron.Remote.Dialog.ShowOpenDialog
), One can argue that it's best keeping this as close to the official Electron API (and documentation) as possible, but on the other hand it often causes me some inconvenience trying to find the right object to "enter" the graph (e.g. electron
, which is defined in Fable.Import.electron_Extensions
, which puzzled me somewhat). It might also be the case that it's simply not possible in many cases to convert the API from object-based to module-based.
Fable does not hide the fact that it compiles to JS. This is both a great strength (it's fairly easy to interact with unsupported JS APIs, and devs have to learn a bit about the JS runtime environment anyway) and a significant weakness (non-trivial code needing several JS APIs, even supported ones through bindings such as in this project, may end up looking in part like JS code written with a weird and limiting syntax, and can cause troubles when functions can return e.g. undefined
unannounced through the type system). I think we all agree that type safety (cf. the first example) is important and should be ensured. I think most of you also agree that it's both safer, easier and more F# idiomatic to use list
(or even array
since it too can be pattern-matched) instead of ResizeArray
if performance allows. Personally, I'd like all APIs to "feel" like .NET and not like JS (F# is a .NET language, after all), but I can see "API feel" being a source of differing opinions.
In general: What do you think about making (at least certain parts of) the API/bindings more F# idiomatic? I'm particularly interested in cases like the return value of showOpenDialog
, where undefined
or null
are valid return values, but this fact is not part of the returned type.
I might be able to assist with this, but I'd need clear guidelines on what kinds of things are desirable and acceptable to change, and it would likely progress slowly due to my capacity in the foreseeable future. I can also make no promises as to how committed I can be to cleaning up everything (as opposed to just a few bits here and there to scratch my own itches), and I will not be able to continually maintain an F# idiomatic API as new bindings are needed for new JS API surfaces (if such maintenance is at all needed).
Note though that I think there are a lot of API changes that can be done and benefited from in isolation, so there is no strict need to do this "all at once" (except for the fact that all changes would likely be breaking, and it might be desirable to update as much as possible at once to avoid many major releases).