Basics

The first program to write is "Hello, world!". Here it is in Nara. Press the "Run" button to see the output.

print("Hello, world!")

Nara syntax is C-like, however, statements do not require semicolons at the end. Instead, newlines are used to separate statements.

print("Hello")
print("Bye")

What is Nara

Nara is a modern, safe, functional & strongly typed programming language.

  • Functional first: it is design with functional programming in mind, immutable by default. However, Nara is not purely functional. It allows mutation where useful, but mutability is opt-in.
  • Statically typed: all values in the language have a well defined type, at all times. This also means that the compiler will reject programs with invalid types.
  • Strongly typed: the type system cannot be overriden, and most type operations are explicit.

Safe

Nara has the following caracteristics in favor of safety:

  • Garbage Collection: All dynamic memory is managed by a garbage collector, freeing the programmer from errors like leaks, double free, etc.
  • No pointers: Prevents having invalid pointers to data, and reduces attack vectors.
  • Nullability: Operations that involve null are explicit and must be marked. There's syntax like ?Type and others to make it easier.
  • Errors as values: Errors are explicit and must be managed. However, the language provides syntax sugar for ease of use.
  • Strong types: Since all data has defined types, we can't fail on invalid shape of data.

Modern

Bit by bit, new languages introduce new syntax and semantics for better developer experience. Nara has the basics, plus:

  • Sum types (unions):
type PaymentMethod = union {
    Check(String),
    Card(number: String, cvv: u64),
    Cash,
}
  • Errors as values: Operations with a recoverable error explictly define them, and callers must handle them.
type EditUserError = error {
    NotFound,
    DbError(reason: String),
    ConcurrencyError,
}

pub fun edit_user(data: EditData) EditUserError!void {
    // ...
    val user = try db.Users.get_by_id(data.id) orelse {
        return EditUserError.NotFound
    }
    // ...
}

Nara has syntax such as try, catch, orelse that makes it easy to handle errors.

  • Nullability: Values that can be null are explicitly marked, and can be easily worked with. Although the language could support a Option[T] type, we have dedicated syntax for nullable values, and we use null as usual.
val maybe_user: ?User = try db.Users.get_by_id(data.id)
if (maybe_user) |user| {
    // do things with user, which is not null here
    user.name = ...
    user.age  = ...
}

This runs code when there's a value. The opposite can also be done, run code when there's no value:

val maybe_user: ?User = try db.Users.get_by_id(data.id)
maybe_user orelse {
    // This runs when `maybe_user` is null
    // ...
}
  • Pattern matching: