If you're familiar with JS and want to understand how to do something in Foi instead, the following is a lookup list of JS features/idioms and their (somewhat) equivalents in Foi:
JS | Foi | Notes |
---|---|---|
// line comment /* block comment */ | // line comment /// block comment /// | Code Comment |
var .. = let .. = const .. = | def .. : | Variable Definition |
null undefined | empty | Empty Value Literal |
true false | true false | Boolean Literal |
123 3.14 -42 | 123 3.14 -42 | Number Literal |
0o127 0h3a6 0b10110 | \o127 \h3a6 \b10110 | Octal/Hex/Binary Literal |
1_234_567.890_123 | \1_234_567.890_123 | Readable Number Literal |
12345678901234567890n | \@12345678901234567890 | Big Integer Literal |
"hello" 'hello' `hello` | "hello" | String Literal |
`string ${interpolation}` | \`"string `interpolation`" | Interpolated String Literal |
{ x: 2 } | < x: 2 > | Object (Foi Record) |
{ [propName]: 2 } | < %propName: 2 > | Computed Property Name |
[ 2, 3 ] | < 2, 3 > | Array (Foi Tuple) |
arr.includes(x) | x ?in arr | Array Includes |
propName in obj | obj ?has propName | Object Property Exists |
{ ...obj } | < &obj > | Object Spread (Foi Record Pick) |
{ prop: obj.prop } | < &obj.prop > | Object Pick |
[ ...arr ] | < &arr > | Array Spread (Foi Tuple Pick) |
{ x } | < :x > | Concise Property |
obj.prop obj[propName] | obj.prop obj[propName] | Object Property Access |
arr[3] | arr.3 arr[3] | Array Index Access |
arr.at(-1) | arr.-1 | Relative Array Index Access |
arr.slice(2,6) | arr.[2..5] | Array Range Access |
new Map([[ obj, 42 ]]) | < %obj: 42 > | Map (Foi Record) |
new Set([ 1, 1, 2, 2 ]) | <[ 1, 1, 2, 2 ]> | Set (Foi Tuple -- with duplicate filtering) |
= | := | Assignment |
var { x } = .. var [ x ] = .. | def < x >: .. | Destructuring |
!x | !x | Boolean Negate |
!!x Boolean(x) | ?x | Boolean Coerce |
x && y | x ?and y | Boolean AND |
x || y | x ?or y | Boolean OR |
!(x && y) | x !and y | Boolean NOT AND (Foi NAND) |
!(x || y) | x !or y | Boolean NOT OR (Foi NOR) |
== | ?= | Equality |
!= | != | Inequality |
v == null | ?empty v | Is null (Foi empty ) |
v != null | !empty v | Is Not null (Foi != empty ) |
x > y | x ?> y | Greater Than |
!(x > y) | x !> y | Not Greater Than |
x >= y | x ?>= y | Greater Than / Equal |
!(x >= y) | x !>= y | Not Greater Than / Equal |
x < y | x ?< y | Less Than |
!(x < y) | x !< y | Not Less Than |
x <= y | x ?<= y | Less Than / Equal |
!(x <= y) | x !<= y | Not Less Than / Equal |
y > x && y < z | (?<>)(x,y,z) | In Between, Not Inclusive |
y >= x && y <= z | (?<=>)(x,y,z | In Between, Inclusive |
y < x || y > z | (!<>)(x,y,z) | Not In Between, Not Inclusive |
y <= x || y >= z | (!<=>)(x,y,z) | Not In Between, Inclusive |
+ | + | Plus/Concatenation |
- | - | Minus |
* | * | Multiply |
/ | / | Divide |
import .. from .. import { .. } from .. | def .. : import from .. def < .. >: import from .. | Module Import |
export .. export { .. } | export { .. } | Module Export |
function => | defn | Function Definition |
return => .. | ^ | Function Return |
function myFunc(x = 0) .. | defn myFunc(x: 0) .. | Function Parameter Default |
function myFunc(...params) .. | defn myFunc(*params) .. | Function Rest Parameter |
myFunc = x => y => x * y | defn myFunc(x) ^defn(y) ^x * y | (Strict) Curried Function Declaration |
myFunc(1,2,3) | myFunc(1,2,3) | Function Call |
myFunc(...args) | myFunc(...args) | Function Call Argument Spread |
myFunc({ x: 1, y: 2 }) | myFunc(x:1, y:2) | "Named Arguments" (at call-site) Idiom |
x > 0 ? y : z if (x > 0) y else z switch (true) case (x > 0): y; break; default: z | ?{ ?[x ?> 0]: y; ?: z } ?(x){ ?[?> 0]: y; ?: z } | Decision Making (Foi Pattern Matching) |
if (x > 0) myFunc(x) | ?[x ?> 0]: myFunc(x) | Statement Guard Clause |
for (..) while (..) do .. while (..) | ~each | Imperative Loop |
.map(..) | ~map | Map (Foi Comprehension) |
.flatMap(..) | ~flatMap ~bind ~chain ~< | Flat-Map (Foi Comprehension) |
.filter(..) | ~filter | Filter (Foi Comprehension) |
.reduce(..) .reduceRight(..) | ~fold ~foldR | Reduce (Foi Fold) |
[...new Array(4)].map((v,i)=>i) | 0..3 | Integer Range List: 0,1,2,3 |
(async function(){ var x = await y; .. })() | Promise ~<< (x:: y) { .. } | Async..Await (Foi Promise Do Comprehension) |
Promise.resolve(42) | Promise@42 | Resolved Promise |
new Promise(res => { .. }) | Promise(defn(res){ .. }) | Promise Constructor |
const subj = {}; subj.pr = new Promise(res => { subj.resolve = res; }) | def subj: PromiseSubject@; | Promise Subject |
function* async function* | Gen@ .. | Generator |
for (let x of it) { .. } for await (let x of it) { .. } | it ~<* (x) { .. } | Iterator/Async Iterator Consumption (Foi Promise Do Loop Comprehension) |
Observable Stream | PushStream PullStream | Lazy/Concurrent Data |
42 |> myFunc(#) |> anotherFunc(#,2) | 42 #> myFunc #> anotherFunc(#,2) | Pipeline (proposed for JS) |
var x: int = 42 | def x: 42 :as int | TypeScript Static Annotation (Foi Value-Type Annotation) |
type MyType = .. | deft MyType .. | Custom Type Definition |
---------------- | -------------------- | -------------------- |
NaN Infinity -Infinity | (not in Foi) | |
new super class .. extends static this instanceof | (not in Foi) | |
delete void typeof yield | (not in Foi) | |
try .. catch | (not in Foi) | |
x === y x++ ++x x-- --x x += y x -= y x *= y x /= y x %= y x &= y x |= y x **= y x &&= y x ||= y x ??= y x <<= y x >>= y x >>>= y x ** y x << y x >> y x >>> y ~x x % y x & y x | y x ^ y x?.y x?.[y] x?.(y) x ?? y | (not in Foi) | |
---------------- | -------------------- | -------------------- |
(not in JS) | (*)(2,3,4) | N-Ary Operator (as function) Invocation |
(proposed for JS) | myFn|1| myFn|1,,3| | Function Partial Application |
(not in JS) | myFn'(3,2,1) | Function Reverse Application |
(not in JS) | arr.<0,2,3> obj.<first,last,email> | Record/Tuple Subset Selection |
(not in JS) | 2..5 "a".."f" | Range Of Sequential Values |
(not in JS) | :over (..) | Closure Side Effect Declaration |
(not in JS) | defn myFunc() ?[ .. ]: .. | Function Precondition |
(not in JS) | Promise ~<* { .. } | Promise Looping |
(not in JS) | def myFunc: incVal +> doubleVal +> formatNum def myFunc: formatNum <+ doubleVal <+ incVal | Function Composition |
(not in JS) | defn myFunc(v) #> incVal #> doubleVal #> formatNum | Pipeline Function |
(not in JS) | (#>)(42,...fns) | Dynamic Pipeline |
(not in JS) | defn add(x)(y) ^x + y | (Loose) Curried Function Declaration |
(not in JS) | Id Value Number None Maybe Either Left Right List IO Gen PushStream PullStream Channel (CSP) ~ap ~foldMap ~cata ~<< | Monads, Monadic Comprehensions |
Here are some screenshots showing typical JS code (on top or left, in each image) to the equivalent Foi code (on bottom or right, in each image).
|
|
The syntax highlighting of the Foi code in these screenshots is produced by the Foi-Toy tool included in this repository.
You can find some more examples of Foi code here.
Foi more strongly favors the usage of symbolic operators over generic keywords (e.g. Foi ^
vs JS return
). Symbol re-use in compound (multi-character) operators is also prioritized for creating a visual symmetry between related features (e.g., the common ?
in boolean operators like ?>
, ?<
, and ?=
). And Foi uses symbol visual semantics to signal behavior (e.g. +>
pointing in left-to-right direction to indicate the flow of data). Lastly, Foi makes some choices in its operators (especially compound operators) that are unique/rare compared to other languages (e.g., ~<*
), and will thus be less familiar at first glance.
As such, Foi code may indeed appear to be more heavy/dense in its syntax when you first start reading it.
While it's important to admit the intentionality in these design decisions, it's also important to keep a full context for such analysis and opinion forming.
The following table is a comprehensive list of all non-alphanumeric symbols in JS, as well as in Foi, presented respectively in side-by-side columns. In many cases, the row pairings in this table attempt to match like features, but there are many JS or Foi features that are distinct and don't match up; those "random" row pairings are only to slot things into a table for counting purposes.
As a summary of the symbol counts below:
-
JS has 79 distinct symbol sequences (single-character, or compound multi-character); there are another 12 proposed JS symbols (Stage 2 or 3 proposals, somewhat likely to happen).
- 28 of them are single-character (plus 1 proposed)
- 30 of them are double-character (plus 2 proposed)
- 12 of them are triple-character (plus 3 proposed)
- 9 of them are four+ characters (plus 6 proposed)
- 182 total characters (plus 71 proposed characters)
- Regular expression literals can't be length-analyzed
-
Foi has 88 distinct symbol sequences.
- 26 of them are single-character
- 21 of them are double-character
- 21 of them are triple-character
- 20 of them are four+ characters
- 260 total characters
As you can see, JS has 79 distinct symbols (91 including likely-to-land future proposals), whereas Foi has 88 distinct symbols. Overall character count of all symbols is 253 in JS (proposals included) versus 260 for Foi.
So, is Foi more syntactically heavy/verbose than JS? Perhaps a little bit, yes. But probably not significantly moreso, no.
Of course, these symbol/character counts don't tell the whole story. It's also important to understand the usage frequency of each type and length of symbol/operator. Moreover, the "symbol density" of a code base depends on how many of these symbols would appear near/adjacent to each other, with other intervening alphanumeric characters "spreading" them out or not.
Both of these factors are highly dependent on the code base/style, so can't be objectively compared in a universal sense.
JS Symbol/Operator | Description | Foi Symbol/Operator | Description | |
@ | Decorator (proposed) | @ | Monad Constructor | |
# | Private Member | # | Pipeline Topic | |
_ | Identifier Character | _ | Identifier Character | |
$ | Identifier Character | $+ | Set Append | |
, | Comma | , | Comma | |
. | Property Access | . | Property Access | |
; | Statement End Semicolon | ; | Statement End Semicolon | |
: | Property Definition | : | Initial Assignment, Property Definition | |
` | Template String Delimiter | ` | Interpolated String Delimiter, Interpolated Expression Delimiter | |
" | String Delimiter | " | String Delimiter | |
' | String Delimiter | ' | Function Call Argument Reversal | |
\ | Escape | \ | Escape | |
~ | Bitwise Negate (1's complement) | \@ | Large Number Escape (monadic) | |
!! ! | Boolean Cast / Negate | ? ! | Boolean Cast / Negate | |
% %= | Remainder / Assignment | % | Computed Property Name | |
^ ^= | Bitwise Negate / Assignment | ^ | Return (function value) | |
& &= | Bitwise And / Assignment | & | Pick (record/tuple value) | |
+ += | Addition / Assignment | + | Addition | |
- -= | Subtraction / Assignment | - | Subtraction | |
* *= | Multiplication / Assignment | * | Multiplication | |
/ /= | Divide / Assignment | / | Divide | |
* *= | Multiplication / Assignment | * | Multiplication | |
( ) | Expression Grouping, Function Call Arguments | ( ) | Expression Grouping, Function Call Arguments | |
[ ] | Array Literals, Property Access | [ ] | Property Access | |
{ } | Object Literals, Blocks | { } | Blocks | |
| |= | Bitwise Or / Assignment | | | | Function Call Partial Application | |
#{ } #[ ] | Record / Tuple (proposed) | < > | Record, Tuple | |
** **= | Exponentiation / Assignment | <[ ]> | Set | |
= | (Re-)Assignment | := | Re-Assignment | |
== != | Equality / Inequality | ?= != | Equality / Inequality | |
=== !== | Strict Equality / Inequality | ?$= !$= | Set Equality / Inequality | |
< !(x < y) | Less Than / Not Less Than | ?< !< | Less Than / Not Less Than | |
<= !(x <= y) | Less Than Or Equal / Not Less Than Or Equal | ?<= !<= | Less Than Or Equal / Not Less Than Or Equal | |
> !(x > y) | Greater Than / Not Greater Than | ?> !> | Greater Than / Not Greater Than | |
>= !(x >= y) | Greater Than Or Equal / Not Greater Than Or Equal | ?>= !>= | Greater Than Or Equal / Not Greater Than Or Equal | |
=> | Arrow Function | ?<=> !<=> | Between (inclusive) / Not Between | |
&& &&= | Boolean And / Assignment | ?<> !<> | Between (non-inclusive) / Not Between | |
|| ||= | Boolean Or / Assignment | .. | Range / Slice | |
// /* */ | Line Comment / Multiline Comment | // /// | Line Comment / Multiline Comment | |
... | Spread / Rest | ... | Spread / Gather | |
\u | Unicode Escape | \u | Unicode Escape | |
0o 0O | Octal Number Prefix | \o | Octal Number Prefix | |
0b 0B | Binary Number Prefix | \b | Binary Number Prefix | |
0x 0X | Hexadecimal Number Prefix | \h | Hexadecimal Number Prefix | |
|> | Pipeline (proposed) | #> | Pipeline | |
%% | Pipeline Topic Marker (proposed) | :: | Do-Style Chain Assignment | |
? | Ternary Conditional | ?{ } | Pattern Match Expression (Independent) | |
?. ?.[ ?.( | Optional Property Access | ?( ){ } | Pattern Match Expression (Dependent) | |
?? ??= | Nullish-Coalescing / Assignment | ?[ ] | Guard Clause, Pattern Match Conditional, Function Precondition | |
>> >>= | Bitwise Shift Right / Assignment | ~< | Chain/Bind/FlatMap Comprehension (Terse) | |
>>> >>>= | Bitwise Shift Right (Zero Padded) / Assignment | ~<* | Looping Do-Syntax (monadic) | |
<< <<= | Bitwise Shift Left / Assignment | ~<< | Do-Syntax (monadic) | |
++ | (Pre, Post) Increment | +> | Compose (left-to-right) | |
-- | (Pre, Post) Decrement | <+ | Compose (right-to-left) | |
function* yield* import.meta new.target (proposed) !in !instanceof function.sent await.all await.race await.allSettled await.any | Keyword+Symbol (hybrid) | ~each ~map ~filter ~fold ~foldMap ~foldR ~chain ~bind ~flatMap ~ap ~cata :as :over ?empty !empty ?and !and ?or !or ?as !as ?in !in ?has !has | Keyword+Symbol (hybrid) | |
${ } | Interpolated Expression Delimiter | |||
/p*[^a](?<tt>er){1,3}n/ | Regular Expression Literals |
The question that matters the most is: "Does Foi allow me to express and understand programs better?" And you'll only be able to judge that properly once you learn and practice with it, so don't rush to judgement at first glance!
Now check out the Foi Guide for a detailed exploration of the language.
For implementers or language design enthusiasts, a formal grammar specification is in progress.
All code and documentation are (c) 2022-2023 Kyle Simpson and released under the MIT License. A copy of the MIT License is also included.