Skip to content

Commit 92b341b

Browse files
authored
Add Beam (Erlang) target documentation (#228)
* Add Beam (Erlang) target documentation
1 parent 5223453 commit 92b341b

File tree

6 files changed

+883
-0
lines changed

6 files changed

+883
-0
lines changed

docs/docs/beam/build-and-run.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
title: Build and Run
3+
layout: standard
4+
---
5+
6+
## Erlang/OTP Version
7+
8+
Fable targets Erlang/OTP 25 or higher.
9+
10+
## Architecture
11+
12+
Fable compiles F# to Erlang source files (`.erl`), following the same pipeline as all Fable targets:
13+
14+
```text
15+
F# Source
16+
| FSharp2Fable
17+
Fable AST
18+
| FableTransforms
19+
Fable AST (optimized)
20+
| Fable2Beam
21+
Erlang AST
22+
| ErlangPrinter
23+
.erl source files
24+
```
25+
26+
The generated `.erl` files are standard Erlang that can be compiled with `erlc` and run on the BEAM VM.
27+
28+
## Compiling to Erlang
29+
30+
```bash
31+
dotnet fable --lang beam
32+
```
33+
34+
By default, output goes to the project directory (where the `.fsproj` is). You can change this with `--outDir`:
35+
36+
```bash
37+
dotnet fable --lang beam --outDir /path/to/output
38+
```
39+
40+
## Output Structure
41+
42+
The output directory will contain:
43+
44+
```text
45+
output/
46+
program.erl # Your compiled F# modules
47+
fable_modules/
48+
fable-library-beam/
49+
fable_list.erl # F# List runtime
50+
fable_map.erl # F# Map runtime
51+
fable_string.erl # String utilities
52+
fable_seq.erl # Seq/IEnumerable support
53+
... # Other runtime modules
54+
```
55+
56+
## Compiling Erlang
57+
58+
After Fable generates `.erl` files, compile them with `erlc`:
59+
60+
```bash
61+
# Compile the runtime library
62+
erlc -o output/fable_modules/fable-library-beam output/fable_modules/fable-library-beam/*.erl
63+
64+
# Compile your project files
65+
erlc -pa output/fable_modules/fable-library-beam -o output output/*.erl
66+
```
67+
68+
## Running Erlang Code
69+
70+
Run your compiled module using the Erlang shell:
71+
72+
```bash
73+
erl -pa output -pa output/fable_modules/fable-library-beam \
74+
-noshell -eval 'program:main(), halt().'
75+
```
76+
77+
The `-pa` flag adds directories to the code path so Erlang can find both your modules and the Fable runtime library.
78+
79+
## Module Naming
80+
81+
Fable converts F# filenames to snake_case to match Erlang conventions:
82+
83+
- `Program.fs` becomes `program.erl` with `-module(program).`
84+
- `MyModule.fs` becomes `my_module.erl` with `-module(my_module).`
85+
86+
Erlang requires the module name to match the filename, so this conversion is automatic.
87+
88+
## Program vs Library
89+
90+
The Beam backend currently does not differentiate between `Exe` and `Library` output types. All top-level code is compiled into a `main/0` Erlang function regardless of the `OutputType` setting. The `[<EntryPoint>]` attribute is not used.
91+
92+
## Watch Mode
93+
94+
For development, use watch mode to recompile on changes:
95+
96+
```bash
97+
dotnet fable watch --lang beam
98+
```
99+
100+
## Custom fable-library Path
101+
102+
If you need a custom version of the runtime library (e.g., for development), use the `--fableLib` option:
103+
104+
```bash
105+
dotnet fable --lang beam --fableLib /path/to/custom/fable-library-beam
106+
```

docs/docs/beam/compatibility.md

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
---
2+
title: .NET and F# compatibility
3+
layout: standard
4+
toc:
5+
to: 4
6+
---
7+
8+
:::warning
9+
Beam target is in alpha meaning that breaking changes can happen between minor versions.
10+
:::
11+
12+
Fable provides support for some classes of .NET BCL (Base Class Library) and most of FSharp.Core library. When possible, Fable translates .NET types and methods to native Erlang types for minimum overhead.
13+
14+
## Common Types and Objects
15+
16+
Many F#/.NET types have natural counterparts in Erlang. Fable takes advantage of this to compile to native types that are more performant and idiomatic:
17+
18+
- **Strings** compile to Erlang binaries (`<<"hello">>`).
19+
- **Booleans** compile to Erlang atoms (`true` / `false`).
20+
- **Chars** are compiled as strings of length 1.
21+
- **Integers** use Erlang's native arbitrary-precision integers. Unlike Python and JavaScript, no wrapper types are needed for `int`, `int64`, or `bigint`.
22+
- **Floats** use Erlang's native `float()` type.
23+
- **Tuples** compile directly to Erlang tuples (`{A, B, C}`).
24+
- **Lists** (`list<T>`) compile to Erlang linked lists — both languages use cons cells, making this a perfect fit.
25+
- **Arrays** compile to process dictionary references wrapping Erlang lists (for mutability). Byte arrays use `atomics` for O(1) read/write.
26+
- **ResizeArray** compiles to process dictionary references with list mutation helpers.
27+
- Any **IEnumerable** (or `seq`) can be traversed using Erlang's iterator patterns.
28+
- **Maps** (`Map<K,V>`) compile to Erlang native maps (`#{}`).
29+
- **Sets** (`Set<T>`) compile to Erlang `ordsets` (sorted lists).
30+
- Mutable **dictionaries** compile to process dictionary references wrapping Erlang maps.
31+
- **Unit** compiles to the `ok` atom.
32+
33+
## .NET Base Class Library
34+
35+
The following classes are translated to Erlang and most of their methods (static and instance) should be available in Fable.
36+
37+
.NET | Erlang
38+
---------------------------------------|----------------------------
39+
Numeric Types | Native integers and floats
40+
Arrays | Process dict refs (lists / atomics for byte[])
41+
System.Boolean | `true` / `false` atoms
42+
System.Char | Binary (string of length 1)
43+
System.String | Binary (`<<"...">>`)
44+
System.Decimal | Custom fixed-scale integer
45+
System.DateTime | `{Ticks, Kind}` tuple
46+
System.DateTimeOffset | `{Ticks, OffsetTicks, Kind}` tuple
47+
System.TimeSpan | Ticks-based integer
48+
System.Guid | UUID v4 binary
49+
System.Uri | URI binary with parsing
50+
System.Text.RegularExpressions.Regex | Erlang `re` module (PCRE)
51+
System.Text.StringBuilder | Mutable binary builder
52+
System.Collections.Generic.List | Process dict ref (list)
53+
System.Collections.Generic.Dictionary | Process dict ref (map)
54+
System.Collections.Generic.HashSet | Process dict ref (map-based set)
55+
System.Collections.Generic.Queue | Process dict ref (Erlang queue)
56+
System.Collections.Generic.Stack | Process dict ref (list)
57+
System.Diagnostics.Stopwatch | `erlang:monotonic_time`
58+
Records | Erlang maps (`#{}`)
59+
Anonymous Records | Erlang maps (`#{}`)
60+
Tuples | Erlang tuples (`{}`)
61+
62+
## FSharp.Core
63+
64+
Most of FSharp.Core operators are supported, as well as formatting with `sprintf`, `printfn`, or `failwithf`.
65+
The following types and/or corresponding modules from FSharp.Core lib will translate to Erlang:
66+
67+
.NET | Erlang
68+
------------------|----------------------------------------------------------
69+
Tuples | Erlang tuples
70+
Option | (erased) — `Some(x)` = `x`, `None` = `undefined`
71+
String | Binary
72+
List | Erlang linked list (cons cells)
73+
Map | Erlang native map (`#{}`)
74+
Set | `ordsets` (sorted list)
75+
ResizeArray | Process dict ref (list)
76+
Record | Erlang map (`#{}`)
77+
Anonymous Record | Erlang map (`#{}`)
78+
Result | `{ok, Value}` / `{error, Error}`
79+
Async | CPS function `fun(Ctx) -> ... end`
80+
Task | Alias for Async on Beam
81+
82+
### Caveats
83+
84+
- Options are **erased** in Erlang (`Some 5` becomes just `5` and `None` becomes `undefined`). Nested options use wrapped representation (`{some, x}`) to avoid ambiguity.
85+
- **Records** compile to Erlang maps with snake_case atom keys.
86+
- **Anonymous Records** also compile to Erlang maps.
87+
- **Result** maps to Erlang's idiomatic `{ok, V}` / `{error, E}` convention.
88+
- **Unit** is represented as the `ok` atom, which is distinct from `undefined` (None) — unlike JS/Python where both map to similar concepts.
89+
90+
## Interfaces and Protocols
91+
92+
F# interfaces compile to Erlang maps of closures (dispatch maps):
93+
94+
.NET | Erlang | Comment
95+
--------------|------------------------------|------------------------------------
96+
`IEquatable` | Native `=:=` | Deep structural equality on all types
97+
`IEnumerator` | Iterator pattern | `next()` style iteration
98+
`IEnumerable` | List iteration | `for` loop / `lists:foreach`
99+
`IComparable` | Native `<`, `>`, `=<`, `>=` | Works on all Erlang terms
100+
`IDisposable` | Manual cleanup | No `with` statement equivalent
101+
`ToString` | `fable_string:to_string/1` | String formatting
102+
103+
## Object Oriented Programming
104+
105+
F# OOP features like interfaces, abstract classes, inheritance, and overloading are supported. Object expressions compile to Erlang maps of closures.
106+
107+
## Numeric Types
108+
109+
Erlang has native arbitrary-precision integers, making integer support much simpler than on JavaScript or Python targets. No wrapper types or Rust NIFs are needed:
110+
111+
F# | .NET | Erlang | Notes
112+
:----------------|:-----------|:-------------|----------------------------------
113+
bool | Boolean | `true`/`false` atoms | Native
114+
int | Int32 | `integer()` | Native arbitrary-precision
115+
byte | Byte | `integer()` | Native
116+
sbyte | SByte | `integer()` | Native
117+
int16 | Int16 | `integer()` | Native
118+
int64 | Int64 | `integer()` | Native (no BigInt library needed)
119+
uint16 | UInt16 | `integer()` | Native
120+
uint32 | UInt32 | `integer()` | Native
121+
uint64 | UInt64 | `integer()` | Native
122+
float / double | Double | `float()` | Native IEEE 754
123+
float32 / single | Single | `float()` | Native IEEE 754
124+
decimal | Decimal | Custom | Fixed-scale integer implementation
125+
bigint | BigInteger | `integer()` | Native (Erlang integers ARE arbitrary-precision)
126+
127+
### Sized Integer Overflow
128+
129+
Like Python, Erlang has native arbitrary-precision integers. Sized integer wrapping (for overflow semantics of `int32`, `int64`, etc.) uses Erlang's bit syntax:
130+
131+
```erlang
132+
%% Wrapping int32 arithmetic
133+
wrap32(N) ->
134+
<<V:32/signed-integer>> = <<N:32/signed-integer>>,
135+
V.
136+
```
137+
138+
## Reflection
139+
140+
Full `FSharp.Reflection` support is available via `fable_reflection.erl`:
141+
142+
- `FSharpType.IsTuple`, `IsRecord`, `IsUnion`, `IsFunction`
143+
- `FSharpType.GetTupleElements`, `GetRecordFields`, `GetUnionCases`
144+
- `FSharpValue.GetRecordFields`, `MakeRecord`
145+
- `FSharpValue.GetTupleFields`, `MakeTuple`, `GetTupleField`
146+
- `FSharpValue.GetUnionFields`, `MakeUnion`
147+
- `PropertyInfo.GetValue`
148+
149+
## Async and Concurrency
150+
151+
F# async workflows use CPS (Continuation-Passing Style) where `Async<T>` = `fun(Ctx) -> ok end` with a context map containing `on_success`, `on_error`, `on_cancel`, and `cancel_token`.
152+
153+
F# | Erlang
154+
----------------------------|---------------------------------------
155+
`async { return x }` | CPS function with `on_success` callback
156+
`let! x = comp` | `bind(Comp, fun(X) -> ... end)`
157+
`Async.RunSynchronously` | CPS invocation in same process
158+
`Async.Parallel` | `spawn` per computation, `receive` to collect
159+
`Async.Sleep` | `timer:sleep(Ms)`
160+
`task { return x }` | Same as async (alias on Beam)
161+
162+
:::info
163+
`task { }` and `async { }` compile to the same CPS representation on Beam. The hot-start semantics of .NET `Task` are not preserved. Downcasting from `obj` to `Task<T>` or `Async<T>` is not supported.
164+
:::
165+
166+
### CancellationToken
167+
168+
Full cancellation support via `fable_cancellation.erl`:
169+
170+
- `CancellationTokenSource` — create, cancel, cancel after timeout
171+
- `Register` / `Unregister` — listener management
172+
- `IsCancellationRequested` — poll cancellation state
173+
- Timer-based auto-cancel via `cancel_after`
174+
175+
## Observable
176+
177+
Full `Observable` module support:
178+
179+
- `subscribe`, `add`, `choose`, `filter`, `map`
180+
- `merge`, `pairwise`, `partition`, `scan`, `split`
181+
182+
## Sequence Expressions
183+
184+
Sequence expressions are supported and compile to lazy evaluation:
185+
186+
```fs
187+
let numbers = seq {
188+
yield 1
189+
yield 2
190+
yield! [3; 4; 5]
191+
}
192+
```
193+
194+
## String Formatting
195+
196+
Full F# format string support (`%d`, `%s`, `%.2f`, `%g`, `%x`, etc.) via `fable_string.erl` runtime:
197+
198+
- `sprintf` — format to string
199+
- `printfn` — format to stdout
200+
- `eprintfn` — format to stderr
201+
- `failwithf` — format and throw
202+
- `String.Format` — .NET-style positional format strings
203+
204+
String interpolation (`$"Hello, {name}!"`) compiles to `iolist_to_binary` with appropriate type conversions.
205+
206+
## Tail Call Optimization
207+
208+
Erlang has native tail call optimization, so recursive F# functions compile to efficient tail-recursive Erlang functions without needing a trampoline.

0 commit comments

Comments
 (0)