Skip to content
Noodle
InstallLearnPlayground
GitHub

Language overview

The Language basics section is a complete starting point, not a preview of every feature. After finishing it, you can write multi-file programs with typed functions, local state, structured data, datatypes, pattern matching, tests, and package dependencies.

Advanced abstraction features are kept in Advanced topics. Collection types and other reusable APIs are documented under the Standard library.

A small program consists of declarations. Execution begins at a selected source file’s main function:

func square(value : Int) -> Int do
value * value
end
func main() -> Unit do
answer = square(6);
Console.println(answer.to_string());
end

This short program already shows several important rules:

  • function parameters and results have explicit types;
  • local bindings such as answer are inferred;
  • = introduces a binding rather than assigning to an existing one;
  • intermediate statements end with ;;
  • the final expression of a value-producing block has no semicolon;
  • end closes the function regardless of indentation.

Noodle checks every expression before generating JavaScript. Reusable boundaries stay explicit, while local values are inferred from nearby information:

func discount(price : Int, percent : Int) -> Int do
amount = price * percent / 100;
price - amount
end

There are no implicit conversions between distinct types. An Int does not become a Double merely because another expression expects one.

Noodle source files use UTF-8 and the .nl suffix. Spaces, tabs, and line endings separate tokens, but indentation has no syntactic meaning. Line breaks do not insert semicolons or close blocks.

Names are case-sensitive and use casing to communicate their role:

  • lower names such as total_price name functions, parameters, bindings, and fields;
  • upper names such as Invoice and T name types and type parameters;
  • constants use uppercase snake case, such as MAX_RETRIES;
  • a single _ discards a value and does not create a binding.

Keywords can be used in lower-name positions only when escaped with backticks. For example, `end` is an ordinary name, while end closes a construct.

The basic literal forms are:

42 Int
0x2a Int, hexadecimal
0b101010 Int, binary
3.5 Double
1e6 Double
true Bool
'N' Char
'😀' Char
"Noodle" String
() Unit

A minus sign is an operator rather than part of a numeric literal. String and character escapes include \n, \r, \t, \\, hexadecimal escapes such as \x41, and Unicode escapes such as \u{1F600}.

Blocks contain semicolon-terminated statements followed by an optional tail expression. A block without a tail expression produces Unit. An expression statement discards its result; explicitly bind to _ when a non-Unit value is intentionally ignored.

func rectangle_area(width : Int, height : Int) -> Int do
checked_width = if width < 0 then 0 else width end;
checked_height = if height < 0 then 0 else height end;
checked_width * checked_height
end

Comments beginning with // document the declaration that follows. The raw comment marker //\ is intended for temporarily commenting out source. Top-level source files contain declarations such as functions, constants, type aliases, datatypes, interfaces, extensions, and tests; arbitrary statements do not run at the top level.

Many constructs, including if and switch, produce values. State changes use separate syntax: immutable bindings use =, while mutable storage uses mut and :=. Expected failures and asynchronous tasks are also explicit; their introductory guides are Error handling and Asynchronous code.

Read the basics in order:

  1. Basic types
  2. Values and bindings
  3. Functions
  4. Control flow
  5. Records and tuples
  6. Datatypes and basic patterns
  7. Error handling
  8. Asynchronous code
  9. Tests
  10. Modules and packages

The examples assume the default standard library is enabled. The Prelude and modules chapter later explains which names come from that library.