Most programming languages ship with a bare skeleton and tell you to install packages for everything else. Need to format a date? npm install moment. Need to hash a password? pip install bcrypt. Need to validate an email? Google "regex email validation" and pray.
FLIN takes the opposite approach. Every function you need for building a real web application is built into the language itself. No imports. No package manager. No dependency tree. Four hundred and nine functions, available from the first line of every program.
This is the story of how we designed, categorized, and implemented a standard library large enough to replace dozens of third-party packages -- and why that decision defines everything FLIN stands for.
The Philosophy: Everything You Need, Nothing You Do Not
When Thales and I sat down to define FLIN's standard library, we started with a question that most language designers never ask: what does a developer building a real web application actually need?
Not a toy. Not a script that prints "Hello, World!" and exits. A real application with user authentication, form validation, data transformation, HTTP requests, date formatting, and string manipulation. The kind of application that, in JavaScript, requires 400 packages in node_modules before you write your first line of business logic.
We catalogued every function call in three real-world projects -- an e-commerce platform, a SaaS dashboard, and a content management system. Then we asked: which of these functions should be built-in?
The answer was almost all of them.
// In JavaScript, this requires 3 packages:
// - moment (date formatting)
// - validator (email check)
// - lodash (string manipulation)// In FLIN, it requires zero: name = " juste gnimavo ".trim.title email = "[email protected]" valid = email.is_email joined = now.format("MMMM D, YYYY")
print("{name} ({email}) - Valid: {valid} - Joined: {joined}") // "Juste Gnimavo ([email protected]) - Valid: true - Joined: March 26, 2026" ```
Four functions. Four different categories (text, validation, time, output). Zero imports.
The Numbers: 409 Functions Across 11 Categories
The complete standard library breaks down as follows:
| Category | Functions | Examples |
|---|---|---|
| Text | 31 | upper, trim, split, replace, pad_start |
| Number | 28 | abs, round, sqrt, random, clamp |
| List | 34 | map, where, reduce, sort, group_by |
| Map | 16 | keys, values, merge, filter, invert |
| Time | 26 | now, format, is_before, start_of_month |
| File | 14 | read_file, write_file, file_exists |
| JSON | 3 | parse_json, to_json |
| HTTP | 5 | http_get, http_post, http_put, http_delete |
| Security | 18 | hash_password, verify_password, jwt_sign |
| Validation | 42 | is_email, is_url, is_numeric, sanitize_html |
| Utility | 22 | type_of, clone, env, print, assert |
| Math (advanced) | 48 | sin, cos, mean, median, std_dev |
| Introspection | 15 | fields_of, type_name, has_field |
| HOF | 12 | map, filter, reduce, flat_map, zip_with |
| Error/Debug | 18 | log_info, log_error, timer_start, measure |
| Geometry | 24 | distance, area_circle, rotate_point |
| Performance | 8 | track_error, measure_latency, memory_usage |
| Sanitization | 25 | sanitize_html, escape_sql, strip_tags |
| Total | 409 |
Every single one is available without an import statement. Every single one handles none values gracefully instead of crashing. Every single one follows the same naming convention: lowercase, underscores for multi-word names, method-style calls on values.
The Calling Convention: Methods and Functions
One of the earliest design decisions was supporting two calling styles for every function. You can call a function on a value using method syntax, or you can call it as a standalone function with the value as the first argument:
// Method style (preferred for readability)
name = "hello world"
result = name.upper
length = name.len
words = name.split(" ")// Function style (same result) result = upper(name) length = len(name) words = split(name, " ") ```
Both styles compile to the same bytecode. The method style exists because it reads naturally -- name.upper flows better than upper(name) -- and because it enables chaining:
result = " Hello, World! "
.trim
.lower
.replace(" ", "_")
.slice(0, 20)
// "hello,_world!"This chain compiles to a sequence of opcodes that operate on the stack without any intermediate allocations for the pipeline itself. The method syntax is not syntactic sugar over function calls -- it is a first-class feature of the bytecode emitter.
Null Safety: The Foundation
Every built-in function handles none gracefully. This is not optional. It is not a convention. It is enforced at the VM level.
name: text? = none
result = name.upper // Returns none (does not crash)
length = name.len // Returns none
contains = name.contains("x") // Returns none// Safe chaining formatted = user.name?.trim.title // If user.name is none, the entire chain returns none ```
In JavaScript, null.toUpperCase() throws a TypeError. In Python, None.upper() throws an AttributeError. In FLIN, calling any method on none returns none. Period. No exceptions. No special handling required.
This decision alone eliminates an entire class of runtime errors that plague every web application. The infamous "Cannot read properties of null" is the single most common JavaScript error in production. In FLIN, it does not exist.
Category Deep Dive: Text Functions
Text manipulation is the most heavily used category in any web application. FLIN ships 31 string methods, covering everything from basic operations to validation to encoding.
The text functions are organized into logical groups:
Basic operations -- the functions you call dozens of times per file:
text.len // Length in characters (not bytes)
text.upper // "hello" -> "HELLO"
text.lower // "HELLO" -> "hello"
text.trim // " hello " -> "hello"
text.trim_start // " hello" -> "hello"
text.trim_end // "hello " -> "hello"Case transformations -- essential for generating slugs, CSS classes, and variable names:
text.capitalize // "hello world" -> "Hello world"
text.title // "hello world" -> "Hello World"
text.snake_case // "helloWorld" -> "hello_world"
text.camel_case // "hello_world" -> "helloWorld"
text.pascal_case // "hello_world" -> "HelloWorld"
text.kebab_case // "hello_world" -> "hello-world"Validation -- because checking input is something every application does:
text.is_empty // true if ""
text.is_blank // true if only whitespace
text.is_numeric // true if all digits
text.is_alpha // true if all letters
text.is_email // Basic email validation
text.is_url // Basic URL validationThese validation methods are not regular expressions that developers copy from Stack Overflow. They are compiled Rust functions that run at native speed. is_email does not just check for an @ sign -- it validates the structure according to RFC 5322 (with the pragmatic simplifications that every real email validator uses).
Category Deep Dive: List Functions
Lists are the second most used data structure in web applications (after strings). FLIN's list functions are designed for method chaining, enabling a functional programming style without the ceremony:
// Find the top 5 active users by score
top_users = users
.where(u => u.is_active)
.sort_by(u => u.score)
.reverse
.take(5)
.map(u => u.name)This reads like English. Filter active users. Sort by score. Reverse (highest first). Take five. Extract names. Each method returns a new list, so the chain is immutable -- the original users list is never modified.
The aggregation functions handle common mathematical operations:
numbers = [10, 20, 30, 40, 50]
numbers.sum // 150
numbers.average // 30.0
numbers.min // 10
numbers.max // 50
numbers.product // 12000000// Predicate counting active = users.count(u => u.is_active) ```
Category Deep Dive: Time Functions
Time handling is notoriously difficult. JavaScript's Date object is widely regarded as one of the worst APIs in the language. Python's datetime module requires importing three different classes. FLIN makes time simple.
// Current time
right_now = now
today_start = today
yesterday_start = yesterday// Time components right_now.year // 2026 right_now.month // 3 right_now.day_of_week // 3 (Wednesday) right_now.is_weekend // false
// Time arithmetic with natural syntax next_week = now + 7.days two_hours_ago = now - 2.hours deadline = today + 3.months
// Formatting right_now.format("YYYY-MM-DD") // "2026-03-26" right_now.format("MMMM D, YYYY") // "March 26, 2026" right_now.from_now // "just now" ```
The duration constructors -- 7.days, 2.hours, 3.months -- are built into the number type. This is not method chaining on numbers; these are genuine duration literals that the type system understands. You cannot add 7.days to a string. You cannot subtract 2.hours from a boolean. The compiler catches these mistakes before the code runs.
Category Deep Dive: HTTP Client
Building web applications means making HTTP requests. In most languages, this requires a third-party library. In JavaScript, the built-in fetch API took years to standardize and still requires polyfills in some environments. In Python, you need requests or httpx.
FLIN includes an HTTP client as a built-in:
// GET request
response = http_get("https://api.example.com/users")// POST with body and headers response = http_post("https://api.example.com/users", { body: { name: "Juste", email: "[email protected]" }, headers: { "Authorization": "Bearer {token}" }, timeout: 30.seconds, retry: 3 })
// Response handling {if response.ok} users = response.json print("Got {users.len} users") {else} print("Error: {response.status}") {/if} ```
The HTTP client supports GET, POST, PUT, PATCH, and DELETE. It handles JSON serialization automatically (if the body is a map or entity, it serializes to JSON and sets the Content-Type header). It supports timeouts, retries, and custom headers. All built-in. All zero-import.
Why Not a Package Manager?
The obvious question: why build 409 functions into the language instead of shipping a package manager and letting the community create libraries?
Three reasons.
First, dependency hell is real. The average JavaScript project has 1,200 transitive dependencies. The average Python project has 40. Every dependency is a potential security vulnerability, a potential breaking change, and a potential licensing issue. When we built Deblo.ai, the backend's requirements.txt had 89 entries. The frontend's node_modules contained 1,847 packages. For an educational platform. FLIN eliminates this entirely.
Second, consistency matters. When every developer uses the same is_email function, every FLIN application validates emails the same way. When every developer uses the same format function for dates, every application formats dates consistently. There is no debate about which validation library to use. There is no blog post titled "Top 10 Date Libraries for FLIN." There is one way to do it, and it works.
Third, FLIN targets developers who do not want to manage infrastructure. The same developer who writes without an import statement does not want to manage a package.json with 47 dependencies. FLIN's promise is: write your application logic, and the language handles everything else.
// This is a complete FLIN application.
// No package.json. No requirements.txt. No go.mod.
// Just this file.entity User { name: text email: text where is_email joined: time = now }
users = User.all.sort_by(u => u.joined).reverse.take(10)
Recent Users
{for user in users}Database, validation, time formatting, UI components, and reactive rendering. One file. Zero dependencies.
Implementation: How 409 Functions Fit in a Single Binary
Building 409 functions into a language runtime sounds expensive. It is not -- if you do it correctly.
Each built-in function is implemented as a Rust function that operates on FLIN's value stack. The dispatch mechanism is a simple match statement over opcode bytes:
match opcode {
0x30 => self.exec_string_upper(),
0x31 => self.exec_string_lower(),
0x32 => self.exec_string_trim(),
0x34 => self.exec_string_index_of(),
// ... 405 more entries
}Each implementation is typically 5-20 lines of Rust. The entire standard library adds approximately 8,000 lines to the VM -- less than 10% of the total codebase. The compiled binary size increase is roughly 200KB. That is the cost of eliminating every third-party dependency a FLIN developer would ever need.
The type checker knows the signature of every built-in function. When you write name.upper, the checker verifies that name is a text value, that upper is a valid method on text, and that the result is also text. If you write 42.upper, you get a compile-time error, not a runtime crash.
The Method Call Flow
Understanding how a method call like text.split(",") becomes executable bytecode reveals the elegance of the system:
Source: text.split(",")
|
Parser: Expr::Call { callee: Expr::Member { object, property }, args }
|
TypeChecker: check_member() returns Function type signature
|
Emitter: try_emit_string_method() emits StringSplit opcode
|
VM: Executes opcode, pushes result list onto stackThe parser sees text.split(",") and produces a member-access expression wrapped in a call expression. The type checker looks up split on the text type, confirms it takes a text argument and returns a [text] list, and validates the argument. The emitter recognizes this as a built-in string method and emits the corresponding opcode directly -- no function call overhead, no closure allocation, no vtable dispatch. The VM pops the string and the separator from the stack, performs the split using Rust's str::split, allocates the result list, and pushes it back onto the stack.
Total overhead compared to a hand-written Rust split operation: one opcode dispatch. That is the cost of having a programmable language instead of hardcoded logic.
Lambda Syntax for Higher-Order Functions
Many built-in functions accept predicates or transformation functions. FLIN uses a concise lambda syntax:
// Single parameter
list.map(x => x * 2)
list.where(x => x > 0)// Multiple parameters list.reduce(0, (acc, x) => acc + x)
// Block body for complex logic list.map(x => { temp = x * 2 temp + 1 }) ```
The lambda x => x * 2 compiles to a closure that captures no variables and takes one argument. The VM allocates it on the heap exactly once, and the map implementation calls it for each element. There is no intermediate list creation -- map builds the result list as it iterates, element by element.
What We Learned
Designing a standard library for a programming language is an exercise in restraint. The temptation is to include everything. The discipline is to include only what developers actually need.
We drew the line at "functions required to build a production web application without any external dependencies." That line gave us 409 functions. Not 50 (too few -- developers would need packages for basic operations). Not 2,000 (too many -- the API surface would be impossible to learn).
Four hundred and nine. Enough to build anything. Small enough to fit in your head.
---
This is Part 71 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO built a programming language that ships with everything a web developer needs.
Series Navigation: - [70] Persistence in the Browser - [71] 409 Built-in Functions: The Complete Standard Library (you are here) - [72] 31 String Methods Built Into the Language - [73] Math, Statistics, and Geometry Functions