Migrating from TypeScript
Floe is designed to be familiar to TypeScript developers. This guide covers the key differences.
What Stays the Same
Section titled “What Stays the Same”- Import/export syntax
- Template literals
- JSX
- Async/await
- Type annotations
- Generics
What Changes
Section titled “What Changes”fn instead of function
Section titled “fn instead of function”// TypeScriptfunction greet(name: string): string { return `Hello, ${name}!`}
// Floefn greet(name: string) -> string { `Hello, ${name}!`}fn(x) instead of =>
Section titled “fn(x) instead of =>”// TypeScriptconst result = items.filter(x => x.active)onClick={() => setCount(count + 1)}
// Floeconst result = items |> Array.filter(.active)onClick={fn() setCount(count + 1)}-> for return types and function types
Section titled “-> for return types and function types”// TypeScriptfunction add(a: number, b: number): number { ... }type Transform = (s: string) => number
// Floefn add(a: number, b: number) -> number { ... }type Transform = fn(string) -> numberconst only
Section titled “const only”// TypeScriptlet count = 0count += 1
// Floe - no let, no mutationconst count = 0const newCount = count + 1== is ===
Section titled “== is ===”Floe’s == compiles to ===. There is no loose equality.
// Floex == y // compiles to: x === yx != y // compiles to: x !== yPipes instead of method chains
Section titled “Pipes instead of method chains”// TypeScriptconst result = users .filter(u => u.active) .map(u => u.name) .join(", ")
// Floeconst result = users |> Array.filter(.active) |> Array.map(.name) |> String.join(", ")Pattern matching instead of switch
Section titled “Pattern matching instead of switch”// TypeScriptswitch (action.type) { case "increment": return state + 1 case "decrement": return state - 1 default: return state}
// Floematch action.type { "increment" -> state + 1, "decrement" -> state - 1, _ -> state,}try instead of try/catch
Section titled “try instead of try/catch”// JSON.parse is in the stdlib - it already returns Result, no try neededconst result = JSON.parse(input)match result { Ok(data) -> process(data), Err(e) -> Console.error(e),}For external TypeScript imports, the try keyword wraps any expression in a try/catch and returns a Result<T, Error>. All TypeScript imports are treated as potentially throwing by default. The compiler requires try when calling them. For TS functions you know won’t throw, use trusted:
// TypeScripttry { const data = parseYaml(input) return data} catch (e) { return null}
// Floe - wrap throwing TS imports with `try`import { parseYaml } from "yaml-lib"const result = try parseYaml(input)match result { Ok(data) -> Some(data), Err(_) -> None,}import { trusted capitalize, fetchUser } from "some-lib"
capitalize("hello") // string, no try neededconst user = try fetchUser(id) // Result<User, Error>
// Or mark the whole import as trusted:import trusted { capitalize, slugify } from "string-utils"Option instead of null
Section titled “Option instead of null”// TypeScriptfunction find(id: string): User | null { return users.find(u => u.id === id) ?? null}
// Floefn find(id: string) -> Option<User> { match users |> find(.id == id) { Some(user) -> Some(user), None -> None, }}What’s Removed
Section titled “What’s Removed”| Feature | Why | Alternative |
|---|---|---|
let / var | Mutation bugs | const only |
class | Complex inheritance hierarchies | Functions + records |
this | Implicit context bugs | Explicit parameters |
any | Type safety escape | unknown + narrowing |
null / undefined | Nullable reference bugs | Option<T> |
enum | Compiles to runtime objects | Union types |
interface | Redundant | type |
switch | No exhaustiveness, fall-through | match |
for / while | Mutation-heavy | Pipes + map/filter/reduce |
throw | Invisible error paths | Result<T, E> |
function | Verbose | fn |
=> | Two function syntaxes | fn(x) for closures |
return | Implicit returns | Last expression is the return value |
Incremental Adoption
Section titled “Incremental Adoption”Floe compiles to .ts/.tsx, so you can adopt it file by file:
- Add
floeto your project - Write new files as
.fl - Compile them alongside your existing
.tsfiles - Your build tool (Vite, Next.js) treats the output as normal TypeScript