Learn the Enso Syntax.
Enso's syntax is beautifuly crafted in order to stand outamongst both visual and textual programming languages.
Click it to learn more →
from Standard.Base import all

import Standard.Table

polyglot java import java.lang.Math


## A shape type that can be either a circle
or a rectangle.

type Shape


## A type representing the circle shape.

type Circle radius


## A type representing the rectangle
shape.

? No Squares
Of course, a square is just a type
of rectangle, so we don't need a
separate type for this.

type Rectangle width height


## Calculate the area of the shape.

area : Number
area = case this of
Circle r -> here.pi * r^2
Rectangle w h -> w * h


## Checks if a given shape is a square.

is_square : Boolean
is_square = case this of
Circle _ -> False
Rectangle w h -> w == h


## An extension method that sorts a table
on the column called "area", without
changing the order of missing items.

Table.sort_by_area : Table
Table.sort_by_area = this.sort "area" (missing_last = False)


## Calculates the volume of an object
from its cross-sectional area and height.

Arguments:
- area: The cross-sectional area of the
shape.
- height: The height of the 3D shape.

! Volume Calculation and Cross Sections
This volume calculation assumes that
the cross section of the solid doesn't
change at any point. If your solid's
cross section _does_ change then you
will find that this produces wrong
results.

foreign js calculate_volume area (height = 10) = """
return area * height;


## The mathematical constant pi, accurate
to 10 decimal places.

We have it defined in Enso, but let's
get it from Java!

pi : Decimal
pi = Math.pi


# Main is where our program starts.

main =

# We create a function that generates
# a shape based on the input number.
# We can write type signatures on local
# functions and variables but don't
# have to.

make_shape : Number -> Shape
make_shape width =
circ = Circle width/2
rect = Rectangle width width
if width > 100 then circ else rect


# We then generate a vector of numbers
# and turn each of them into a shape at
# random using the function we defined
# above.

shapes = 1.up_to 10 . to_vector . map .noise . map make_shape


# We then calculate the areas of these
# shapes.

areas = shapes.map .area


# We then generate a table that contains
# both the shape and its area.

table = Table.new [["shape", shapes], ["area", areas]]


# This table then gets sorted in
# ascending order with our extension
# method.

sorted_shapes = table.sort_by_area


# We then calculate the volumes of
# simple prisms based on the areas of
# our shapes.

volumes = sorted_shapes.at "area" . map (x -> here.calculate_volume x)


# Finally, we combine the results of
# our volume calculations with our
# original table, ensuring it is called
# "volume".

with_area = table.join (volumes.rename "volume")


# We can print the table to see it in
# the terminal.

with_area.print

Enso. Designed for People.

Enso is both the culmination and solidification of many years of research on visual and functional languages. It is easy to use, highly-performant, and is based on a solid mathematical foundation. The design of the Enso language is based on three fundamental assumptions:

People need an immediate connection to what they are making.

First introduced by Brett Victor in his talk "Inventing on Principle", the human-computer interaction visionary states that any violation of this principle alienates the user from the actual problems that they are trying to solve. Consequently, this decreases their understanding, and increases the number of mistakes they make.

“The problem is that software engineers don’t understand the problem they’re trying to solve, and don’t care to,” says Leveson, the MIT software-safety expert. The reason is that they’re too wrapped up in getting their code to work.

You can read more about his idea in the article "The Coming Software Apocalypse", by The Atlantic.

Enso provides a collaborative canvas to address this problem, letting you rapidly prototype ideas, interactively inspect and understand results, and test your hypotheses in real time. See our demo videos to learn more.

The syntax has to be natural to read and write.

We believe that the ease with which you can ready and write code directly affects the speed at which you can solve problems and innovate.

As a motivating example, consider the following code in the Python programming language, widely considered to be one of the most approachable programming languages in the world. It first takes a range of numbers between 10 and 100, uses them as seeds for a perlin-noise function, sorts the results, takes the 10 smallest values, and finally, computes their sum:

sum(sorted(map(lambda x:noise(x), range(10,100))[:10]))

Now, let's see the same example in Enso:

10.up_to 100 . map .noise . take_start 10 . sort . sum

The language has to be based on a proven theoretical foundation.

The code in Enso is not just much easier to read and write; because Enso is purely functional and uses immutable memory, it is also safer, less error-prone, and easier to reason about. Moreover, it runs much faster, running up to 80x faster than Python.

Enso incorporates many recent innovations in programming language design. Some of them are so unique, that they can't be found in any other language. These include seamless syntax-level polyglot operation, atom-based algebraic data types, and flow-based error handling. You will learn about these concepts in this reference. For further information, please read our syntax developer docs.

Enso Syntax Reference

This reference aims to provide you with a quick and easy guide to Enso's syntax. You can click on sections of the code to go to the relevant section of the guide, and the code will also highlight as you scroll.

Getting Started

If you're coming here without already trying Enso, below are some quick tips for how you can get started!

Download Enso

The Enso IDE can be downloaded from here. The installation process can be different depending on your operating system, so please find detailed instructions here. Once it is installed, you can double-click on the application icon to start it.

The Enso Command Line Interface (CLI) can be downloaded here. The CLI lets you write and run Enso code like a normal programming language in your shell. You want to download the "bundle" release for your platform, as this comes with everything you need to get started. The CLI is portable, so you can use it from anywhere you want; just extract the archive and run it from your command line: ./enso --help. For more information on the commands it supports, take a look here.

Watch the Tutorials

If you're looking for a tutortial or a getting started guide for Enso, you might prefer our tutorial videos.

Naming Convention

Enso follows a naming convention for language constructs that differentiates between identifiers for variables, functions, and methods, and those for types and modules.

  • Identifiers can be named with multiple words separated by _.
  • Numbers can be used as words, except for as the first word in a name.
  • All words in variables, functions, and methods start with a lowercase letter.
  • All words in types and modules start with an uppercase letter.

Enso Entities

The Enso expression language is made up of a small set of core entities that nevertheless bring significant power.

Modules

Code in Enso is organised into modules. A module is defined by the file in which you write code. If you are writing in Foo.enso, the associated module is called Foo.

Modules in Enso are first-class types, and can be assigned to variables and passed around. To refer to the current module when defining a method we use the here keyword.

The structure of the modules in your Enso program is defined by the structure of folders in your src directory. If your project is called My_Project, and its src directory contains a folder Foo with a module Bar.enso, you can refer to that module as My_Project.Foo.Bar. For a more in-depth description of how an Enso package is organised, see here.

Imports

Imports in Enso are how we make other components available in your current module. There are two main types and they bring particular names into scope.

Unqualified Imports

An unqualified import has three parts:

  • The module to be imported from, using from followed by the module name.
  • An optional rename of the module being imported, using as.
  • The items to be imported, using import followed either by all or the names to be imported. Names can also be hidden by adding hiding, followed by the names.
from Standard.Base import all
from Standard.Table.Data.Table as Data_Table import Table

Unqualified imports mean that the imported items (such as Math from the above import of Standard.Base) can be referred to without a prefix.

We generally recommend against using unqualified imports when designing your APIs. Most libraries remain much more self-contained when designed to be imported qualified, much like Standard.Table. Furthermore, using all is a very easy way to bring too many names into scope at once. Significant care should be taken when doing this, and we only recommend its use with the Standard.Base module.

Qualified Imports

A qualified import uses import followed by the module name.

import Standard.Table
import Standard.Table as Std_Table

Qualified imports require you to use the name of the module you've imported (e.g. Table or Std_Table) above as a prefix for your method call (e.g. Table.join).,

Imported modules can be renamed by adding as New_name after the imported module name.

Qualified imports are generally the preferred type of import in Enso.

Documentation

Enso comes with a built-in mechanism for documenting your code.

Documentation comments in Enso begin with ## and are indented from the baseline.

## Calculates the volume of an object from its cross-sectional area and height.

   Arguments:
   - area: The cross-sectional area of the
     shape.
   - height: The height of the 3D shape.

We support a variety of formatting syntax such as _ for italics and * for bold, as well as a number of special "sections" that are shown specially. For a comprehensive overview of the documentation comment syntax, please see here.

Comments

Much like any programming language, Enso has support for comments in code.

Comments in Enso are prefixed with a #, and treat anything after them on a line as if it doesn't exist in your program. You can use these to temporarily disable code, or write internal notes in your program.

Algebraic Data Types

It may sound scary, but simply put, an algebraic data type is a data type that is made up of other data types! Even though you may have encountered them under different names, a class is a "product type", much like Enso's Atoms, and a union or enum is a "sum type".

Atoms

Atoms are a novel concept introduced in Enso. They let you define named containers for data, where the data is stored in named fields. For those familiar with Haskell, Scala, or Rust, atoms are like constructors, but unlike in these languages, atoms in Enso can be shared between different algebraic data types. Formally, atoms are product types.

Defining an atom is very simple. You use the type keyword, followed by the name for your type, and optionally some fields. For example, the following atom defines a type Point with three named components:

type Point x y z

Atoms are both values and types in Enso. They are the most primitive building block for the upcoming Enso type system. For example, the type of Vector 1 2 3 is both Vector 1 2 3 and Vector Number Number Number. You can read more about this and the upcoming type-level design in the Enso design documentation.

Sum Types

Sum types aggregate one or more atoms under a common name. For example, the type Shape is either a Circle or a Rectangle.

Defining a sum type is very similar to defining an Atom. It uses the type keyword and follows it with a type name. Where it differs is that it must be followed by a code block that contains at least one atom and optionally some associated methods.

A very unique feature of Enso's Algebraic Data Types is the ability to include the same atom into several sum types. For example, in the Enso standard library there is a definition of the Nothing atom:

type Nothing

This atom is a very common structure in Enso programs. It is, for example, the result of a function call that does not return a value. It is also included as a part of the Maybe sum type, and hence gets useful methods defined on it:

type Maybe
    type Just value
    Nothing

Methods and Functions

A function describes a set of steps to be performed by the computer when the function is evaluated.

The most common kind of function in Enso are methods, which are functions that are dynamically dispatched based on the type of their first argument. Even top-level functions are methods, as they are associated with the module in which they are defined (this is why we have to use here to call them).

The only functions in Enso that are not methods are those that are defined inside the body of another function.

Defining Methods

A method in Enso must be defined at the top level of either a module or a sum-type definition.

is_square :  Boolean
is_square = case this of
    Circle    _   -> False
    Rectangle w h -> w == h

To the left of the equals sign is the name of the method, followed by zero or more function arguments. In addition to the arguments you define, methods have a special argument called this that is always the first argument. It is the type of this that defines how a method is dispatched. When defining a method on a module, here and this refer to the same thing. The former exists so that it is possible to refer to the current module inside a method on an algebraic data type.

Arguments in method definitions are automatically named, and can be referred to by those names. In addition, you can provide a default value for an argument by adding an equals afterward and providing a value. The value for defaulted arguments can then be omitted when calling the method.

my_method a (b = 10) = a + b

Here we have defined a method called my_method that takes two arguments called a and b, where b has a default value of 10.

Defining Functions

Functions are defined just like methods, except that they do not have an implicit this argument added to the start of their argument list. Just like methods, their arguments are named and can have default values.

Anonymous Functions

As Enso is a functional programming language it is very important to be able to define functions without having to give them a name.

Anonymous functions, or lambdas, are defined using the -> operator. A lambda takes one argument on the left-hand-side, and a function body on the right-hand-side. Their arguments, too, are named by default, and can have default values.

(x = 10) -> here.calculate_volume x

In order to create a lambda with multiple arguments, you create a lambda that returns another lambda: a -> b -> a + b.

In Enso we often want to treat a method like a lambda. It would be quite heavy weight to always have to define a lambda, so instead we provide a shorthand for that. Simply writing .method_name is the same as writing x -> x.method_name.

Sum Type Methods

Methods defined in the body of a sum-type are automatically defined for each atom in the body of that sum type. This provides a convenient syntax to define methods across a family of atoms.

Extension Methods

One of the most flexible features of Enso is that you can define a method on an existing type, even if you do not have access to its definition. These are known as "extension methods".

Extension methods are created using the method syntax .. When the name of the method you are defining contains a type name (e.g. My_Type.my_method), this creates an extension method for the type name in question.

Table.Table.sort_by_area = this.sort "area" (missing_last = False)

Extension methods are useful for adding behaviour to types provided by others without needing access to the definition of that type.

Enso makes an important distinction between methods defined "with a type", and methods defined elsewhere. If a method is defined in the same module as the type on which it is defined, those methods are always available on instances of that type. If, however, a method is defined in a different module to the type, it is only available if the module where the extension is defined is imported.

When importing a module all extension methods it defines are brought into scope.

Defining a method on a sum type is actually just syntactic sugar for defining methods using the extension method syntax.

Polyglot

Enso is capable of rich interoperation with other programming languages. We call this "polyglot". Using polyglot in Enso doesn't require you to write wrappers or incur any extra overhead.

There are two kinds of polyglot functionality built into Enso: imports, and "foreign syntax".

Imported Polyglot

One of the most useful features of Enso is its ability to seamlessly use any library that runs on the JVM. This means that you can import and use types from Java just as if they were defined in Enso.

To import a java type it must be available in the polyglot/java/ directory in your project as a .jar file. Then, you write a normal import, but prefix it with polyglot java:

polyglot java Import java.lang.Math

This works just like a normal import and brings the Java class Math into scope. We can call methods on it (e.g. Math.PI) just like we would a method defined in Enso. Arguments are passed just like you would when calling an Enso method.

For Java types that need to be constructed to be used, you can just call .new as a method on the class.

Foreign Methods

Even better than import-based polyglot is the ability to simply write another language in an Enso file as a first-class citizen. Enso currently supports JavaScript, R, and Python as foreign syntax.

Defining a foreign method is just like defining a normal method, except that you prefix it with the keyword foreign and a language name (js, r, or python). The body of the foreign method consists of a multi-line text block containing the code in the foreign language. We hope to add proper syntax support for these definitions in the future.

foreign js calculate_volume area (height = 10) = """
    return area * height;

The arguments to your foreign method are automatically made available in the code of the other language, including the implicit this argument mentioned before.

Variables

A variable in Enso is a way of giving a name to the result of a given expression. They are useful for making sure that your code stays clean as they serve as a way to document what the code is doing.

In Enso, a variable assignment is as simple as using the = operator with a name on the left-hand side, and an expression on the right-hand-side.

Main Entry Point

Every Enso project starts from a method called main. This should be located in a module called Main (which exists in a file called Main.enso). This method is the "entry point" for the Enso program.

TODO: More info - that this entry point is being evaluated when running from command line, that Main.enso needs to be in src dir, that when running IDE the default view is showing the main function.

Writing Enso Expressions

In contrast to many programming languages, Enso is expression oriented. This means that every piece of a program returns a value implicitly. There is no need to write a return statement as you might see in Python.

Literals

A literal is a piece of the program that represents data that is known when you write the program. Enso has three kinds of literals.

Numbers

Enso supports number literals simply by writing numbers in your code. You can write both integer numbers (e.g. 1234) or decimal numbers (e.g. -12.5) directly in Enso expressions.

Text

Enso also supports writing text directly in Enso expressions. Text literals start and end either with a " or a ' (called "delimiters").

my_text = 'Hello, world!'

The first kind of literal is known as a "raw" literal, and it will contain exactly the text that is written in it. The second kind is known as a "format" literal, and it supports using escape sequences such as \n.

In addition to the "inline" text literals shown above, we also have "block" literals. These make it easy to write multiple lines of text as part of the same literal.

A block literal begins with three of the delimiter for the type of literal you want to make. Then, you begin a new-line and start an indented block. All of the text that is in that indented block becomes part of the literal and has any space at the start removed (up to the indentation of the first line).

my_block_literal = """
    Hello, world!

    It is such a lovely day today!

Vectors

Enso also supports vector literals for easy creation of vectors! To create a vector it is as simple as writing the items you would like to put in the vector between square brackets ([]).

my_vector = [1, 2.0, "text!"]

Vectors in Enso are heterogeneous, which means that the items in them do not need to be of the same type.

Code Blocks

Enso code is structured using blocks. A block is created when one or more lines are indented relative to a "parent" line. Code at the same indentation level is considered to be a part of the same block.

This is most commonly used in the definition of function or method bodies.

area : Number
area = case this of
    Circle r -> 2 * here.pi * r^2
    Rectangle l w -> l * w

When defining a function or method, a block's return value is the value of the last line in the block. Take care to remember that the value of an assignment is Nothing, and so ending a block with an assignment will return Nothing.

When writing Enso code we recommend keeping lines no longer than 80 characters. This helps readability. We have a few additions to our block syntax that are coming soon in order to make it easier to write neat code.

Blocks with a Trailing Operator

If a block has a trailing operator on its parent line, all of the child lines form a code block.

make_shape width =
    circ = Circle width/2
    rect = Rectangle width width
    if width > 100 then circ else rect

Blocks with Leading Operators (Coming Soon)

If each child line in a block begins with a leading operator, this will soon mean that you can easily break long expressions over multiple lines.

nums = 1.up_to 100
    . map .noise
    . sort
    . take_start 100

Each line in these blocks is treated as if the leading operator has the lowest possible precedence. That means that the above will be equivalent to:

nums = 1.up_to 100 . map .noise . sort . take_start 100

Argument Blocks Blocks (Coming Soon)

Due to the way that Enso API design favours lots of arguments, we want to make it much nicer to apply functions with lots of arguments. In the future a block that has no trailing operator on its parent line, or no leading operators on its child lines, will be interpreted as function application.

geometry = Geometry.sphere
    radius   = 15
    position = Point 10 0 10
    color    = rgb 0 1 0

This is very nice to read, and will be equivalent to:

geometry = Geometry.sphere (radius = 15) (position = Point 10 0 10) (color = rgb 0 1 0)

Operators

Operators in Enso are binary functions with non-alphanumeric names. Operators can only be defined as methods on types, and their precedence and associativity is defined by the Enso parser based on their "shape".

If an operator "looks like" a left arrow (which usually means beginning with <), it associates right. If it "looks like" a right arrow (ending with >), it associates left.

An important exception to this rule is the operator ->. This ensures that defining anonymous functions is simple and easy!

Defining an operator is as simple as defining a method with a non-alphanumeric name.

type My_Type a

My_Type.+ that = My_Type (this.a + that.a)

main =
    x = My_Type 1
    y = My_Type 2
    x + y # == My_Type 3

Much like functions can be partially applied, operator sections provide a convenient way to do this with operators.

There are three kinds of operator sections:

  • Left Sections: When only the left argument is applied.
  • Centre Sections: When no arguments are applied.
  • Right Sections: When only the right argument is applied.

Like all uses of operators, operator sections are subject to the variable operator precedence rules.

(5 +) # left section - a function that takes `x` and returns `5 + x`
(+) # centre section - a function that takes `x` and `y` and returns `x + y`
(+ 5) # right section - a function that takes `x` and returns `x + 5`
[1, 2, 3] . map (+ 5)
[1, 2, 3] . map +5

Operator Precedence

In Enso, operator precedence is whitespace-sensitive. An operator without surrounding spaces has higher precedence than an operator with surrounding spaces.

This seems at first a strange idea, but allows long and complex expressions to be written in a visually cleaner way than other languages. This is important for comprehension of expressions on components in the IDE. Here's an example of this in action:

Without whitespace-sensitive operator precedence:

result = (((1.up_to 100).to_vector).map (.noise)).sort

With whitespace-sensitive operator precedence:

result = 1.up_to 100 . to_vector . map .noise . sort

Remember that you can always write an expresion with parentheses if you find the whitespace-senstivity to be difficult.

Control Flow

Control flow is one of the core parts of an Enso program. Enso offers two main kinds of control-flow structure, from which you can build many others.

  • Pattern matching via case expressions that allow for matching on the type and structure of values.
  • if ... then ... else: The standard conditional construct that determines what to do based on a logical condition.

Pattern matching with case

The case expression allows for inspection of a value's type, and the selection of the subsequent computation to perform. Each branch of a case expression must match on both a type and its fields.

Branches are created using the -> operator, with the pattern to match on the left-hand side, and a code-block to evaluate on the right-hand side. You can use _ to ignore a field in the type.

is_square = case this of
    Circle _ -> False
    Rectangle l w -> l == w

Branches are evaluated from top to bottom, and the first branch with a matching pattern will be evaluated. No other branches will be executed.

The case expression returns the value of the branch that is selected.

In the future we have plans to support other types of patterns such as type-only patterns and matching on a subset of an atom's fields.

if ... then ... else

The definition of if in Enso is an expression. Like any other block in Enso, it returns the value of the last expression in its body.

The if a then b else c expression takes three arguments:

  • a: An expression that evaluates to a Boolean (logical condition).
  • b: A computation to perform if the expression a evaluates to true.
  • c: A computation to perform if the expression a evaluates to false.
if x > 0.5 then Circle x else Rectangle x x

Currently we are limited in that only the body of the else portion of the expression can be on a new line. We hope to lift this restriction soon.

Calling Functions

Unlike many other languages that call functions using parentheses, Enso does its function calls using whitespace. To apply the function f to arguments a and b, you simply write f a b. In particular, if a function does not accept any arguments, it is called simply by writing its name, like [1,2,3].sum.

Argument Names

When created, function arguments are automatically named. These names are used to refer to the function arguments in the body of the function definition. Argument names can also be used when calling a function to pass a value to a particular argument explicitly, rather than relying on ordering.

sum left right = left + right

sum (right = 1) (left = 2)

Named arguments simplify working with complex APIs, as the order in which arguments are applied no longer needs to be remembered.

Argument names can be used when calling foreign methods as well (but not Java methods).

Default Arguments

Functions can be defined with defaults for the function's arguments. Default arguments make it easier to work with complex APIs, allowing users to set values for only certain arguments.

Defaults are specified by using the assignment operator when defining the argument, as shown below:

sum (a = 1) (b = 2) = a + b

Arguments omitted when the function is called will then use their default values:

sum 10 # returns 12, as b=2
sum b=10 # returns 11, as a=1

The ... operator prevents application of default arguments, so that the function can be curried:

sum 3 ... # returns a function expecting an argument for b, `b -> sum 3 b`

Default arguments can be used when calling foreign methods as well.

In the future default arguments will be used to improve the display of Enso expressions in the Enso IDE. They will help us to enable highly interactive widgets on components.

Partial Application and Currying

When a function is called with less arguments than it expects it will return a function that expects the missing arguments. This means two things:

  • Functions in Enso can have only a subset of their arguments applied, making it very easy to customise function behaviour.
f a b = a + b
add_one = f (b = 1)
  • Functions are curried by default.
f a b = a + b
add_to_one = f 1

Underscore Arguments

Replacing a function argument with _ creates a lambda that takes an argument and passes it to the function. This can be used when arguments are provided either by position, or as named arguments.

f a b = a + b
add_one = f _ 1 # this is equivalent to `x -> f x 1`

This operation only "looks through" one level of parentheses. This means that (f _ a) is the same as x -> (f x a), rather than (x -> f x a).

Learn More

If, after reading all of this, you are craving more information, you can check out the Enso Developer Docs. These are not always the most up-to-date pieces of documentation, but the contain the vision for what we are planning on implementing, as well as useful and deep info about how we build Enso.

Chapter 'community'
100% - 100vh
Get the latest updates
to your email.

Join the Community!

Enso is a community-driven open source project which is, and will always be, open and free to use. Join us, help us to build it, and spread the word!