Skip to content

Instantly share code, notes, and snippets.

@FunctionDJ
Last active April 7, 2026 14:27
Show Gist options
  • Select an option

  • Save FunctionDJ/7e279ba44123500577399fd4a4893ab6 to your computer and use it in GitHub Desktop.

Select an option

Save FunctionDJ/7e279ba44123500577399fd4a4893ab6 to your computer and use it in GitHub Desktop.
fun language

fun

Ideas for a programming language similar to TypeScript that compiles to JavaScript. Somewhat of an inversion of DreamBerd.

Strict comparison only, no truthy/falsy

// === and !== don't exist
"1" == 1 // TypeError
if (someString) {} // TypeError, must use someString != ""

Immutable variables by default, explicit mutability

inspired by Rust

const x = 1 // ❌
let x = 1 // ✅
x++ // ❌
let mut y = 1
y++ // ✅
var z = 1 // ❌

Object literals are frozen by default (includes arrays)

let myObj = { bar: 1 }
myObj.bar++ // ❌

let myObj2 = mut { bar: 1 }
myObj2.bar++ // ✅
myObj2 = {} // ❌ only properties are reassignable, since variable is not mut

No semicolons, method calling parentheses and array indexing must not have a gap

Maybe there are other cases in JS that need the no-gap rule in fun to get rid of semicolons

let x = 0; // ❌
let someValue = myArray
	[0] // interpreted as an array literal, not an array accessor
someFunction
( // will be interpreted as an expression wrapper (e.g. IIFE)
	someArgument
)

No hoisting

let x = foo() // ❌
fn foo() => "hi"

Arrow functions always have implicit return and must be single-line, named functions use fn keyword

let myFunction = () =>
	otherFunction(
		evenAnotherFunction(
			longComplicatedValue
		)
	) // ❌

fn myFunction() {
	return otherFunction(
		evenAnotherFunction(
			longComplicatedValue
		)
	)
} // ✅

Errorful function signatures

inspired by Java and Effect

fn foo() => JSON.parse(someString)
// ^ () => unknown, throws JsonParseError

Simple error mapping and error literals

fn parseUserJson(someString: string) {
	return JSON.parse(someString)
		.catch(JsonParseError, new UserJsonParseError)
					// ^ UserJsonParseError has never been created before (one-off),
					// must not collide with any other symbol
}
// ^ (someString: string) => unknown, throws UserJsonParseError

Type-safe error handling

fn foo() {
	let fileData = readFileSync(...)
	let networkResponse = fetchSomethingSync(...)
}
// ^ foo() => void, throws FSReadError, FetchNetworkError

fn bar() {
	let _, error = foo()
	catch (error, FSReadError) {
		console.log(error.details)
	}
}
// ^ bar() => void, throws FetchNetworkError

Types that are not inferred or derived are runtime schemas

fn strHasLength16orMore(str: string) => str.length >= 16
fn strHasNumbers(str: string) => str.match(/\d/) !== null

interface SignupDTO {
	password: string([strHasLength16orMore, strHasNumbers]) // list of validators
}

fn signup(requestBodyJson: unknown) {
	let { password } = SignupDTO.parse(requestBodyJson)
	//    ^ string
}
// ^ signup(requestBodyJson: unknown) => void, throws SchemaError

Platform-specific globals must be access through a single global

document.createElement() // ❌
navigation.back() // ❌
window.document.createElement() // ✅ (this will get me cancelled)

HOF shorthand

inspired by Java method references

foo.filter(x => x.bar)
foo.filter(x => x.quux())

// equivalent
foo.filter(::bar)
foo.filter(::quux())

Ternaries can't be nested

let foo = cond1 ? (cond2 ? "a" : "b") : "c" // ❌

Named function arguments must be used if signature has >=2 arguments

fn foo(width: number, height: number) => ...
foo(8, 2) // ❌
foo(height: 2, width: 8) // ✅

Modules

inspired by Python

Import variant 1: Import a named export (no side-effects allowed)

from "package" import Foo, Bar as MyRenamedBar // default exports dont exist

Import variant 2: Namespace import (no side-effects allowed)

import "foo" // exports function "bar"
foo.bar()
import "package" as MyRenamedPackage
MyRenamedPackage.Foo

Import variant 3: Side-effects

import effect "someFile.css" // discouraged, should be solved with a custom loader to create a `<link>` element for frontend stuff

Functions with side-effects are called with #

This might be impossible to implement because something as innocent as a network request to get some data changes a lot in the overall system. The idea is more targeted towards having no practical consequences for the program state, like modifying variables.

let myModule = {
  noSideEffect: () => "hi",
  fn sideEffect() {
    console#log("bye")
  }
}

myModule.noSideEffect()
myModule#sideEffect()

To do

  • remove continue?
  • remove labelled statements? (for etc)
  • replace switch with match (if keeping switch, enforce break and remove it as keyword for switch)
  • make if-else syntactic sugar for ternary and remove a ? b : c keywords?
  • scoped exports: exported symbols should not be globally importable by default. e.g. by default they're only importable by files in the same folder, with different, looser or global scopes requiring explicit annotation. language servers (auto-complete) should naturally respect these restrictions. scoping should also be configurable for dependencies (e.g. banning the import of a library in some files/folders) but this might be more of a concern for linters, like can be done today using eslint with JS/TS.
  • re-throwing errors should have a proper, type-safe way to include the previously catched error so it can be properly carried upwards and handled and inspected there if needed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment