Skip to content

Commit bc551cc

Browse files
committed
Fixes for fable from py
1 parent 47fa649 commit bc551cc

File tree

5 files changed

+132
-398
lines changed

5 files changed

+132
-398
lines changed
Lines changed: 66 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
# Call Fable from Python
22

3-
Sometimes, we'd like to use the power of Fable in our JavaScript apps. For instance, to create a new
4-
[js node in Node-RED](https://nodered.org/docs/creating-nodes/first-node) or call some handy F# code from our new Node.js serverless function or even call some powerful json parsing into our JavaScript app.
3+
Sometimes, we'd like to use the power of Fable in our Python apps. For instance, to call some handy F# code from our new
4+
Python function or even call some powerful json parsing into our Python app.
55

6-
It may allow you to play with Fable and add features, one at a time. So what does it take to call Fable from JavaScript? First you need to understand a bit how the generated JS code looks like so you can call it correctly.
6+
It may allow you to play with Fable and add features, one at a time. So what does it take to call Fable from Python?
7+
First you need to understand a bit how the generated Python code looks like so you can call it correctly.
78

8-
> Remember you can use the [Fable REPL](https://fable.io/repl/) to easily check the generated JS for your F# code!
9+
> Remember you can use the [Fable REPL](https://fable.io/repl/) to easily check the generated Python for your F# code!
10+
> Just switch to Python on the options panel.
911
1012
## Name mangling
1113

12-
Because JS doesn't support overloading or multiple modules in a single file, Fable needs to mangle the name of some members to avoid clashes, which makes it difficult to call such functions or values from JS. However, there're some cases where Fable guarantees names won't change in order to improve interop:
14+
Because Python doesn't support overloading or multiple modules in a single file, Fable needs to mangle the name of some
15+
members to avoid clashes, which makes it difficult to call such functions or values from Python. However, there're some
16+
cases where Fable guarantees names won't change in order to improve interop:
1317

1418
- Record fields
1519
- Interface and abstract members
1620
- Functions and values in the root module
1721

18-
What's a root module? Because F# accepts multiple modules in the same file, we consider the root module the first one containing actual members and is not nested by any other.
22+
What's a root module? Because F# accepts multiple modules in the same file, we consider the root module the first one
23+
containing actual members and is not nested by any other.
1924

2025
```fsharp
2126
// It doesn't matter if we have a long namespace, Fable only starts
@@ -30,7 +35,8 @@ module Nested =
3035
let add (x: int) (y: int) = x * y
3136
```
3237

33-
In F# it's possible to have more than one root module in a single file, in that case everything will be mangled. You should avoid this pattern if you want to expose code to JS:
38+
In F# it's possible to have more than one root module in a single file, in that case everything will be mangled. You
39+
should avoid this pattern if you want to expose code to Python:
3440

3541
```fsharp
3642
namespace SharedNamespace
@@ -48,26 +54,35 @@ module Bar =
4854

4955
In some cases, it's possible to change the default behavior towards name mangling:
5056

51-
- If you want to have all members attached to a class (as in standard Python classes) and not-mangled use the `AttachMembers` attribute. But be aware **overloads won't work** in this case.
52-
- If you are not planning to use an interface to interact with Python and want to have overloaded members, you can decorate the interface declaration with the `Mangle` attribute. Note: Interfaces coming from .NET BCL (like System.Collections.IEnumerator) are mangled by default.
57+
- If you want to have all members attached to a class (as in standard Python classes) and not-mangled use the
58+
`AttachMembers` attribute. But be aware **overloads won't work** in this case.
59+
- If you are not planning to use an interface to interact with Python and want to have overloaded members, you can
60+
decorate the interface declaration with the `Mangle` attribute. Note: Interfaces coming from .NET BCL (like
61+
System.Collections.IEnumerator) are mangled by default.
5362

5463
## Common types and objects
5564

56-
Some F#/.NET types have [counterparts in JS](../dotnet/compatibility.md). Fable takes advantage of this to compile to native types that are more performant and reduce bundle size. You can also use this to improve interop when exchanging data between F# and Python. The most important common types are:
65+
Some F#/.NET types have [counterparts in Python](../dotnet/compatibility.md). Fable takes advantage of this to compile
66+
to native types that are more performant and reduce bundle size. You can also use this to improve interop when
67+
exchanging data between F# and Python. The most important common types are:
5768

5869
- **Strings and booleans** behave the same in F# and Python.
59-
- **Chars** are compiled as Python strings of length 1. This is mainly because string indexing in JS gives you another string. But you can use a char as a number with an explicit conversion like `int16 '家'`.
60-
- **Numeric types** compile to JS numbers, except for `long`, `decimal` and `bigint`.
61-
- **Arrays** (and `ResizeArray`) compile to JS arrays. _Numeric arrays_ compile to [Typed Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) in most situations, though this shouldn't make a difference for most common operations like indexing, iterating or mapping. You can disable this behavior with [the `typedArrays` option](https://www.npmjs.com/package/fable-loader#options).
62-
- Any **IEnumerable** (or `seq`) can be traversed in JS as if it were an [Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#Iterables).
63-
- **DateTime** compiles to JS `Date`.
64-
- **Regex** compiles to JS `RegExp`.
65-
- Mutable **dictionaries** (not F# maps) compile to [ES2015 Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map).
66-
- Mutable **hashsets** (not F# sets) compile to [ES2015 Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).
67-
68-
> If the dictionary or hashset requires custom or structural equality, Fable will generate a custom type, but it will share the same properties as JS maps and sets.
69-
70-
- **Objects**: As seen above, only record fields and interface members will be attached to objects without name mangling. Take this into account when sending to or receiving an object from JS.
70+
- **Chars** are compiled as Python strings of length 1. This is mainly because string indexing in Python gives you
71+
another string. But you can use a char as a number with an explicit conversion like `int16 '家'`.
72+
- **Numeric types** compile to Python integers, except for `long`, `decimal` and `bigint`.
73+
- **Arrays** (and `ResizeArray`) compile to Python lists. _Numeric arrays_ compile to [array](https://docs.python.org/3/library/array.html) in most
74+
situations, though this shouldn't make a difference for most common operations like indexing, iterating or mapping.
75+
- Any **IEnumerable** (or `seq`) can be traversed in Python as if it were an [Iterable](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable).
76+
- **DateTime** compiles to Python `datetime`.
77+
- **Regex** compiles to Python `re`.
78+
- Mutable **dictionaries** (not F# maps) compile to Python dictionaries.
79+
- Mutable **hashsets** (not F# sets) compile to a custom HashSet type.
80+
81+
> If the dictionary or hashset requires custom or structural equality, Fable will generate a custom type, but it will
82+
> share the same properties as JS maps and sets.
83+
84+
- **Objects**: As seen above, only record fields and interface members will be attached to objects without name
85+
mangling. Take this into account when sending to or receiving an object from Python.
7186

7287
```fsharp
7388
type MyRecord =
@@ -92,49 +107,52 @@ let createClass(value: float) =
92107
MyClass(value)
93108
```
94109

95-
```js
96-
import { createRecord, createClass } from "./Tests.fs"
110+
```py
111+
import tests import create_record, create_class
97112

98-
var record = createRecord(2);
113+
record = create_record(2)
99114

100-
// Ok, we're calling a record field
101-
record.Add(record.Value, 2); // 4
115+
# Ok, we're calling a record field
116+
record.Add(record.Value, 2) # 4
102117

103-
// Fails, this member is not actually attached to the object
104-
record.FiveTimes();
118+
# Fails, this member is not actually attached to the object
119+
record.FiveTimes()
105120

106-
var myClass = createClass(5);
121+
var myClass = create_class(5)
107122

108-
// Fails
109-
myClass.Value;
123+
# Fails
124+
myClass.Value
110125

111-
// Ok, this is an interface member
112-
myClass.Square(); // 25
126+
# Ok, this is an interface member
127+
myClass.Square() # 25
113128
```
114129

115130
## Functions: automatic uncurrying
116131

117-
Fable will automatically uncurry functions in many situations: when they're passed as functions, when set as a record field... So in most cases you can pass them to and from JS as if they were functions without curried arguments.
132+
Fable will automatically uncurry functions in many situations: when they're passed as functions, when set as a record
133+
field... So in most cases you can pass them to and from Python as if they were functions without curried arguments.
118134

119135
```fsharp
120136
let execute (f: int->int->int) x y =
121137
f x y
122138
```
123139

124-
```js
125-
import { execute } from "./TestFunctions.fs"
140+
```py
141+
from test_functions import execute
126142

127-
execute(function (x, y) { return x * y }, 3, 5) // 15
143+
execute(lambda x, y: x * y , 3, 5) # 15
128144
```
129145

130-
> Check [this](https://fsharpforfunandprofit.com/posts/currying/) for more information on function currying in F# (and other functional languages).
146+
> Check [this](https://fsharpforfunandprofit.com/posts/currying/) for more information on function currying in F# (and
147+
> other functional languages).
131148
132149
### Using delegates for disambiguation
133150

134-
There are some situations where Fable uncurrying mechanism can get confused, particularly with functions that return other functions. Let's consider the following example:
151+
There are some situations where Fable uncurrying mechanism can get confused, particularly with functions that return
152+
other functions. Let's consider the following example:
135153

136154
```fsharp
137-
open Fable.Core.JsInterop
155+
open Fable.Core.PyInterop
138156
139157
let myEffect() =
140158
printfn "Effect!"
@@ -149,9 +167,13 @@ let useEffect (effect: unit -> (unit -> unit)): unit =
149167
useEffect myEffect
150168
```
151169

152-
The problem here is the compiler cannot tell `unit -> unit -> unit` apart from `unit -> (unit -> unit)`, it can only see a 2-arity lambda (a function accepting two arguments). This won't be an issue if all your code is in F#, but if you're sending the function to JS as in this case, Fable will incorrectly try to uncurry it causing unexpected results.
170+
The problem here is the compiler cannot tell `unit -> unit -> unit` apart from `unit -> (unit -> unit)`, it can only see
171+
a 2-arity lambda (a function accepting two arguments). This won't be an issue if all your code is in F#, but if you're
172+
sending the function to JS as in this case, Fable will incorrectly try to uncurry it causing unexpected results.
153173

154-
To disambiguate these cases, you can use [delegates](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/delegates), like `System.Func` which are not curried:
174+
To disambiguate these cases, you can use
175+
[delegates](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/delegates), like `System.Func` which are
176+
not curried:
155177

156178
```fsharp
157179
open System
@@ -162,113 +184,4 @@ let useEffect (effect: Func<unit, (unit -> unit)>): unit =
162184
163185
// Works
164186
useEffect(Func<_,_> myEffect)
165-
```
166-
167-
## Call Fable source code from a JS file
168-
169-
Webpack makes it very easy to include files in different programming languages in your project by using loaders. Because in a Fable project we assume you're already using the [fable-loader](https://www.npmjs.com/package/fable-loader), if you have a file like the following:
170-
171-
```fsharp
172-
module HelloFable
173-
174-
let sayHelloFable() = "Hello Fable!"
175-
```
176-
177-
Importing and using it from JS is as simple as if it were another JS file:
178-
179-
```js
180-
import { sayHelloFable } from "./HelloFable.fs"
181-
182-
console.log(sayHelloFable());
183-
```
184-
185-
### Importing Fable code from Typescript
186-
187-
For better or worse, Typescript wants to check your imported modules and because it doesn't know a thing of F#, it will complain if you try to import an .fs file:
188-
189-
```ts
190-
// Typescript will complain because it cannot read the file
191-
import { sayHelloFable } from "./HelloFable.fs"
192-
```
193-
194-
To appease the Typescript compiler, we need a [declaration file](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html), which also gives us the opportunity to tell Typescript what is actually exported by our Fable code. If you place a `HelloFable.d.ts` file like the following, the import above will work:
195-
196-
```ts
197-
// Unfortunately declaration files don't accept relative paths
198-
// so we just use the * wildcard
199-
declare module "*HelloFable.fs" {
200-
function sayHelloFable(): string;
201-
}
202-
```
203-
204-
## Call Fable compiled code as a library
205-
206-
### ..from your web app
207-
208-
If your project is a web app and you're using Webpack, it just takes two lines of code in the Webpack configuration in the `output` section of `module.exports`:
209-
210-
```js
211-
libraryTarget: 'var',
212-
library: 'MyFableLib'
213-
```
214-
215-
For instance:
216-
217-
```js
218-
output: {
219-
path: path.join(__dirname, "./public"),
220-
filename: "bundle.js",
221-
libraryTarget: 'var',
222-
library: 'MyFableLib'
223-
},
224-
```
225-
226-
This tells Webpack that we want our Fable code to be available from a global variable named `MyFableLib`. That's it!
227-
228-
:::{note}
229-
Only the public functions and values in the **last file of the project** will be exposed.
230-
:::
231-
232-
#### Let's try!
233-
234-
Let's compile the HelloFable app from above with a webpack.config.js that includes the following:
235-
236-
```js
237-
output: {
238-
...
239-
libraryTarget: 'var',
240-
library: 'OMGFable'
241-
}
242-
```
243-
244-
Now let's try this directly in our `index.html` file:
245-
246-
```html
247-
<body>
248-
<script src="bundle.js"></script>
249-
<script type="text/JavaScript">
250-
alert( OMGFable.sayHelloFable() );
251-
</script>
252-
</body>
253-
```
254-
255-
Et voilà! We're done! You can find a [full sample here](https://github.com/fable-compiler/fable2-samples/tree/master/interopFableFromJS).
256-
257-
### ...from your Node.js app
258-
259-
Basically it's the same thing. If you want to see a complete sample using a `commonjs` output instead of `var`, please [check this project](https://github.com/fable-compiler/fable2-samples/tree/master/nodejsbundle). There you'll see that we've added the following lines to the Webpack config:
260-
261-
```js
262-
library:"app",
263-
libraryTarget: 'commonjs'
264-
```
265-
266-
and then you can call your code from JavaScript like this:
267-
268-
```js
269-
let app = require("./App.js");
270-
```
271-
272-
### Learn more about Webpack `libraryTarget`
273-
274-
If you want to know what your options are, please consult the [official documentation](https://webpack.js.org/configuration/output/#outputlibrarytarget).
187+
```

docs-src/communicate/py-from-fable.md

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -617,19 +617,11 @@ chart
617617
?width(768.)
618618
?height(480.)
619619
?group(speedSumGroup)
620-
?on("renderlet", fun chart ->
621-
chart?selectAll("rect")?on("click", fun sender args ->
622-
Browser.console.log("click!", args))
623620
624-
// chart
621+
// (chart
625622
// .width(768)
626623
// .height(480)
627-
// .group(speedSumGroup)
628-
// .on("renderlet", function (chart) {
629-
// return chart.selectAll("rect").on("click", function (sender, args) {
630-
// return console.log("click!", args);
631-
// });
632-
// });
624+
// .group(speedSumGroup))
633625
```
634626

635627
If you prefer member extensions rather than operators for dynamic typing, you can open `Fable.Core.DynamicExtensions` to

0 commit comments

Comments
 (0)