FluentAI Language Specification
Table of Contents
- Core Philosophy
- Lexical Structure
- Statement Termination and Semicolons
- Primitive Types & Literals
- Modules and Visibility
- Definitions & Declarations
- Chaining, Lambdas, and Control Flow
- Traits & Polymorphism
- Concurrency
- Effects and Side Effect Management
- Recursive Functions
- Metaprogramming & Advanced Features
- Complete Example: Console Application
- Multi-Module Example: Stock Portfolio Rebalancer
- Current Limitations and Future Features
1. Core Philosophy
FluentAI is a general-purpose programming language designed for clarity, expressiveness, and safety. Its core philosophy is that all operations should, whenever possible, be expressed as a linear, left-to-right chain of transformations.
This is achieved by making method chaining the primary mode of expression, enhanced by powerful, inline lambda expressions (param => ...) that allow for complex logic without breaking the fluent flow. The syntax prioritizes consistency, unambiguity, and human ergonomics, making it ideal for both developers and AI-driven tooling.
FluentAI combines the familiarity of C#-style syntax with functional programming features, making it an easy transition for C# developers while providing modern language capabilities.
2. Lexical Structure
This section defines the basic grammar and tokens of the language.
Comments
// Single-line comment
/* Multi-line comment */
Identifiers & Naming Conventions
- snake_case: For variables and function names (
user_name,calculate_total) - PascalCase: For all types (structs, enums, traits, actors) (
User,RequestState) - SCREAMING_SNAKE_CASE: For constants and enum variants (
MAX_USERS,RequestState.Success)
Keywords
Definitions: private, public, function, struct, enum, trait, as, type, actor, effect, macro, extern
Control Flow: if, else, match, case, for, while, in, let, const
Concurrency: async, await, parallel, handle
Error Handling: try, catch, finally
Modules: use, mod
Other: true, false, self, super, unsafe, dyn, with, perform
Operators
- Chaining:
.(method call),|>(pipe-to),.?(optional chaining) - Lambda:
=> - Arithmetic:
+,-,*,/,%(modulo),**(exponentiation) - Comparison:
==,!=,<,>,<=,>= - Logical:
&&(and),||(or),!(not) - Assignment:
=,:=(mutation)
3. Statement Termination and Semicolons
FluentAI uses semicolons to clearly mark the end of statements, following a similar model to C# and other C-family languages. This approach eliminates ambiguity and makes the code structure explicit.
Semicolon Rules
Statements that REQUIRE semicolons:
- Variable declarations:
let x = 5; - Assignments:
x := 10; - Expression statements:
print("hello"); - Method calls as statements:
user.update_name("Alice"); - Import statements:
use Stock; - Return expressions (when not the last expression in a block)
Constructs that DO NOT require semicolons:
- Control flow blocks (if, for, while, match)
- Function/struct/enum/trait definitions
- Block expressions when used as values
- The last expression in a block (implicit return)
Examples
// Statements requiring semicolons
let name = "Alice";
let age = 30;
name := "Bob";
print_user(name, age);
// Control flow - no semicolon after blocks
if (age > 18) {
print("Adult");
process_adult(name);
} else {
print("Minor");
}
// Last expression in block - no semicolon (implicit return)
let result = {
let x = calculate();
let y = transform(x);
x + y // No semicolon - this is the return value
};
// Function definition - no semicolon
private function greet(name: string) {
$(f"Hello, {name}!").print();
}
4. Primitive Types & Literals
Primitive Types
- int: Integer (e.g.,
10,-5) - float: Floating-point number (e.g.,
3.14) - string: UTF-8 text (e.g.,
"hello") - bool: Boolean (
trueorfalse)
Collection Literals
- List:
[1, 2, 3] - Map:
{"key1": "value1", "key2": "value2"} - Set:
#{1, 2, 3}
String Formatting
F-strings are used for easy interpolation:
let name = "Alice";
let message = f"Hello, {name}!"; // "Hello, Alice!"
Console Output
The $(...) construct wraps a string in a "Printable" object. This is the idiomatic way to handle console I/O:
$("This will be printed to the console.").print();
let name = "World";
$(f"Hello, {name}!").print(); // Works with formatted strings
5. Modules and Visibility
Modules provide encapsulation and organization. Each .flc file is implicitly a module.
Module Declaration
// Optional module declaration at the top of a file
mod ModuleName;
// If no module declaration is provided, the module name defaults to the filename
// File: Stock.flc → module Stock
// File: price_service.flc → module price_service
Automatic Exports
- All
publicdefinitions are automatically exported from a module - All
privatedefinitions remain module-internal - No explicit
exportstatements needed
Importing
// Import a constructor or primary export
use Stock;
use Portfolio;
// Import specific public items
use Stock::{from_json, to_json};
use Portfolio::{calculate_value, rebalance};
// Import all public items from a module
use PriceService::*;
// Import with aliasing
use VeryLongModuleName as VLMN;
use SomeLongFunctionName as short_name from Utils;
// Relative imports
use ./utils/Helper; // From relative path
use ../shared/Common; // From parent directory
Module Path Resolution
// Standard library modules use std:: prefix
use std::collections::List;
use std::io::File;
// Local modules use relative paths or module names
use ./models/User; // Relative to current file
use Portfolio; // Searches module path
Visibility
- private ...: Default visibility. The definition is private to the current module.
- public ...: Public visibility. The definition is automatically exported and can be imported by other modules.
5.1 Object-Oriented Patterns
FluentAI supports object-oriented programming through closure patterns. This provides encapsulation and method chaining without requiring special class syntax.
Pattern Example
// Stock.flc
mod Stock;
public function Stock(symbol: string, shares: float, price: float) {
// Private state - not accessible from outside
let state = {
"symbol": symbol,
"shares": shares,
"current_price": price
};
// Private helper functions
let validate_shares = (shares) => shares >= 0.0;
// Create the public interface
let self = {
"get_symbol": () => state.symbol,
"get_shares": () => state.shares,
"get_value": () => state.shares * state.current_price,
"update_shares": (new_shares) => {
if (validate_shares(new_shares)) {
state.shares = new_shares;
}
self // Return self for method chaining
},
"update_price": (new_price) => {
if (new_price > 0.0) {
state.current_price = new_price;
}
self
},
"to_json": () => {
{"symbol": state.symbol, "shares": state.shares, "price": state.current_price}
}
};
return self;
}
// Additional module-level functions
public function from_json(data) {
Stock(data.symbol, data.shares, data.price)
}
Usage from Another Module
// main.flc
use Stock;
use Stock::{from_json};
let my_stock = Stock("AAPL", 100, 150.0);
my_stock
.update_price(155.0)
.update_shares(110);
let value = my_stock.get_value(); // 17,050.0
$(f"Stock value: ${value}").print();
// Using the additional function
let stock_data = {"symbol": "GOOGL", "shares": 50, "price": 2800.0};
let google = from_json(stock_data);
6. Definitions & Declarations
All top-level definitions start with a visibility keyword (public or private).
Functions
// Top-level function
public function create_user(name: string) -> User { ... }
// Private functions can be defined at top-level
private function helper() { ... }
// Note: Inside blocks/expressions, use let bindings for functions:
{
let local_func = (x) => x * 2;
local_func(5)
}
Structs (Value Types)
public struct User {
id: Uuid, // Private field
pub name: string, // Public field
}
Enums
public enum RequestState {
Pending,
Success(string),
Error { code: int, message: string }
}
Traits (Interfaces)
public trait Serializable {
private function to_json(self) -> string;
}
Trait Implementations
The as keyword is used to implement a trait for a type:
private User as Serializable {
private function to_json(self) -> string {
// implementation...
}
}
7. Chaining, Lambdas, and Control Flow
The core of FluentAI is the fluent chain.
Implicit Iteration
Collection processing methods like .map, .filter, and .for_each_async can be called directly on collections. There is no need for an explicit .iter() call.
// Correct:
[1, 2, 3].map(x => x * 2)
// Incorrect (unnecessary):
[1, 2, 3].iter().map(x => x * 2)
Lambda Expressions
Lambdas are passed as arguments to methods. The => operator separates parameters from the body.
// Single parameter
users.filter(user => user.age > 18)
// Multiple parameters
numbers.reduce(0, (acc, item) => acc + item)
// No parameters
on_done(() => $("Task complete!").print())
Destructuring in Lambdas
users.map(({name, email}) => f"{name} <{email}>")
Control Flow as Chainable Expressions
let message = if (user.is_active) { "Active" } else { "Inactive" };
// The `match` expression is a powerful tool in chains.
let status_text = response
.match()
// For simple value mapping
.case(200, "OK")
.case(404, "Not Found")
// For complex logic, the pattern binds variables for the lambda body.
// Note the lambda `=> { ... }` has no parameters of its own.
.case(Err(e), => {
log_error(e);
"An unexpected error occurred"
})
.get();
8. Traits & Polymorphism
Polymorphism is the ability to write code that operates on values of different types, achieved through traits.
Static Polymorphism via Generics
Zero-cost, compile-time polymorphism:
private function log_as_json(item: T) {
item.to_json() |> print()
}
Dynamic Polymorphism via Trait Objects
Runtime flexibility using dyn<Trait>:
let ui_elements: List>> = [ ... ];
for element in ui_elements {
element.draw(); // Call is dispatched at runtime
}
9. Concurrency
Async/Await
private async function fetch_user_data(id: Uuid) -> User {
http.get(f"/users/{id}").await()
}
// Async blocks
let result = async {
let data = fetch_data().await();
process(data)
}.await();
Actor Model
private actor Counter {
count: int = 0;
private handle Inc(amount: int) { self.count += amount; }
}
Channels and Message Passing
// Create a channel
let ch = channel();
// Send and receive (using macro syntax)
send!(ch, "message");
let msg = recv!(ch);
// Spawn concurrent tasks
spawn {
let result = expensive_computation();
send!(ch, result);
};
let result = recv!(ch);
10. Effects and Side Effect Management
Effects provide a structured way to handle side effects. The perform keyword executes effectful operations:
// Perform I/O operations
perform IO.print("Hello, World");
perform IO.println("With newline");
let input = perform IO.read_line();
// State management
perform State.set(42);
let value = perform State.get();
// Other effect types
perform Network.fetch(url);
perform Random.int(1, 100);
perform Time.now();
// Handle effects with custom handlers
handle {
perform IO.print("This will be captured");
perform State.set(42);
} with {
IO.print(msg) => captured_messages.push(msg),
State.set(value) => { current_state = value; }
}
11. Recursive Functions
Recursive functions can be defined using regular let bindings:
// Simple recursion
let factorial = (n) => {
if (n <= 1) { 1 }
else { n * factorial(n - 1) }
};
// Mutual recursion
let even = (n) => {
if (n == 0) { true }
else { odd(n - 1) }
};
let odd = (n) => {
if (n == 0) { false }
else { even(n - 1) }
};
12. Metaprogramming & Advanced Features
Derive Attributes
private struct User { ... }.derive(Debug, Clone)
Contract Specifications (Future Feature)
@contract {
requires: n >= 0,
ensures: result >= 1
}
private function factorial(n: int) -> int { ... }
Macros
// Macro invocation uses ! syntax
assert!(x > 0, "x must be positive");
println!("Value: {}", x);
// Custom macros (future feature)
macro rules! {
// macro definition
}
FFI (Foreign Function Interface)
extern "C" {
private function c_lib_function(input: i32) -> i32;
}
Effect Systems
private effect Database { ... }
private function get_user(id: Uuid).with(Database) { ... }
13. Complete Example: Console Application
This example demonstrates a simple console application that fetches user data from a mock API, showcasing many of the language's features working together.
// main.flc
// === Definitions ===
public struct User { pub id: int, pub name: string }
.derive(Debug, Clone)
public enum ApiError { NotFound, Network(string) }
.derive(Debug)
public trait ApiClient {
private async function fetch_user(self, id: int) -> Result;
}
private struct MockApiClient { db: HashMap }
// === Implementations ===
private MockApiClient as ApiClient {
private async function fetch_user(self, id: int) -> Result {
tokio.sleep_ms(100).await();
self.db.get(&id)
.cloned()
.ok_or(ApiError.NotFound)
}
}
private MockApiClient {
private function new() -> Self {
let db = HashMap.new()
.insert(1, User{id: 1, name: "Alice"})
.insert(2, User{id: 2, name: "Bob"});
Self{db: db}
}
}
// === Logic Functions ===
// This function processes the data and returns a list of result strings.
private async function fetch_and_format_results(client: MockApiClient) -> List {
[1, 3, 2] // List of IDs to fetch
.map_async(id => {
client.fetch_user(id)
.await()
.match()
// The pattern `Ok({name, ..})` binds the `name` variable.
.case(Ok({name, ..}), => f"Success! Found user: {name}")
// The pattern `Err(...)` handles the NotFound case.
.case(Err(ApiError.NotFound), => f"Handled Error: User with ID '{id}' was not found.")
.get() // Get the resulting string from the match expression
})
.await() // Wait for all async map operations to complete
}
// This function takes the results and prints them to the console.
private function print_results(results: List) {
$("--- Processing Complete ---").print();
results.for_each(result => $(result).print());
}
// === Main Application Entry Point ===
private async function main() {
MockApiClient.new()
.let(client => {
fetch_and_format_results(client)
.await()
|> print_results()
})
}
14. Multi-Module Example: Stock Portfolio Rebalancer
This example demonstrates how to structure a real application using FluentAI's module system.
The complete example spans multiple files showing how modules interact to create a portfolio rebalancing application. See the full specification document for the complete implementation including:
- Action.flc: Defines Buy, Sell, and Hold actions
- Stock.flc: Implements stock objects with encapsulation
- PriceService.flc: Mock price service for stocks
- Portfolio.flc: Portfolio management and rebalancing logic
- main.flc: Application entry point
15. Current Limitations and Future Features
Parser Limitations
- Recursive let bindings may not work in all contexts
- Macro syntax (!) is recognized but custom macros cannot be defined
- Contract specifications (@contract) are not yet parsed
- Some async block constructs may not be fully supported
Future Features
- Full macro system with macro rules!
- Contract-based programming with @contract
- More sophisticated pattern matching
- Algebraic effects beyond the current perform/handle system
- Compile-time reflection and code generation
Notes for Test Writers
When writing tests, prefer using features that are fully documented and implemented. Use #[ignore] attribute for tests that rely on future features.