Introduction

Komodo is a programming language to test ideas in a fast and easy way. It is ideal for problems with basic discrete structures, like numbers and words. Komodo tries to make operating with these entities as easy as possible while minimizing the amount of code needed to achieve a successful implementation. Komodo is a scripting language regarding its flexibility, but it is a pretty specific language with a lot of assumptions about how things should be. Komodo is an experimental language, and some of its features are there because I had a personal preference for them.

Komodo draws inspiration from Picat, SETL and the Wolfram Language (I blame my university homework for the last one). It definitely has influence from (wait for it) Python and JavaScript, which are languages that I used for several years before learning more programming paradigms. Komodo is in part a result of my frustrations with these languages.

Komodo is unstable (I just add what I want) and in active development. Here is the source code.

This document is a guide to show how to use Komodo.

Tip: You can search for terms in this document using the 🔍 icon at the top of the page, or pressing the s key.

Installation

Currently, you can install Komodo downloading binaries or building from source.

Download binaries

There are pre-compiled binaries for x86-64 Linux here. If this is not your architecture, you will have to build from source.

You can easily download Komodo with these commands:

wget https://github.com/danilopedraza/komodo/releases/download/v0.0.1/komodo
chmod +x komodo
mv komodo $HOME/.local/bin

Build from source

You can build and install Komodo using the Rust infrastructure. Komodo is not published in crates.io, so you will have to clone the GitHub repository. To run the following commands you must have Rust installed.

git clone https://github.com/danilopedraza/komodo.git
cd komodo/core
cargo build --release
chmod +x target/release/komodo
cp target/release/komodo $HOME/.local/bin

Now you should be able to use Komodo from the command line with the komodo command.

Alternatively, you can try Komodo by running cargo run in the komodo/core directory.

Using Komodo

Using the REPL

To start the REPL, you just have to type komodo in the terminal. It will show you something like this:

>>> 

You can type a Komodo expression, and it will evaluate it. Let's try something simple:

>>> "Hello, world!"
"Hello, world!"

You can do arithmetic:

>>> 2 + 2
4

You can store values with a let expression:

>>> let a := 1 + 0.5
1.5
>>> a + 0.5
2.0

Executing files

The Komodo interpreter can read files provided by you. You just have to type komodo <path>. Create a file called fib.komodo and put the following in it:

let fib(0) := 0
let fib(1) := 1
let fib(n) := fib(n - 1) + fib(n - 2)

println(fib(10)) # 55

Now, in the same directory where the file is, type komodo fib.komodo.

VSCode Extension

There is a VSCode extension available for Komodo. Right now, it just adds syntax highlighting. It is published in the VS Marketplace and OpenVSX, so you can easily search it in VSCode and VSCodium.

Code examples

If you like to learn by doing or looking at examples, you can find some in the code repository and Rosetta Code.

Programming with Komodo

In this chapter, we will see the basic properties of Komodo and how to use them to build programs.

Core elements

What is Komodo good at?

The aim of Komodo is to easily express experimental solutions to problems.

What does experimental mean here? They are probably not complete nor correct solutions. When people solve a problem, they usually get to the solution iteratively. They start by some intuition-based guess, and tweak until it satisfies all of the problem's constraints. Komodo wants to make this process easier, making assumptions that are reasonable and offering tools that are convenient.

What is Komodo bad at?

Everything else. We also have to confess that, currently, Komodo is painfully slow. This will probably hurt the user experience and limit the use cases of the language.

Expression-oriented

Komodo is an expression-oriented language, which means that almost everything that you write returns something.

For example, languages like Python or JavaScript have a clear distinction between statements (pieces of code that declare something) and expressions (pieces of code that describe a value). JavaScript has statements like for, if, and let, and expressions like 5 or () => null. You can't do something like let a = (let b = 5);, because the value in the right does not describe a value, and the point of the let statement is to save a value!

In Komodo, we think of this differently. Every piece of code that you write returns something. For example, the expression let a := 1 returns 1, and the expression let chr := 'x' returns 'x'.

Komodo does have some statements, but they are a few.

With pattern matching

Pattern matching is the main mechanism to write rules in Komodo. You can use it when declaring functions. For example, this code computes the 10th Fibonacci number:

let fib(0) := 0
let fib(1) := 1
let fib(n) := fib(n - 1) + fib(n - 2)

fib(10) # 55

This code gives the last element of a list:

let last([val]) := val
let last([first|tail]) := last(tail)

last([1, 2, 3]) # 3

The [first|tail] expression represents a list whose first element is first and tail is a list with the rest. This syntax exists in languages like Prolog, Erlang, and Picat.

With weak typing

komodo does not enforce type rules, so you can pass any type to a function. This feature is motivated for two reasons:

  • Komodo does not have mechanisms like polymorphism. Weak typing (with pattern matching) allows to (kinda) solve the same problem that polymorphism solves.

  • It enriches pattern matching! And invites you, dear programmer, to use it.

Caching/memoization by default

You can save the result of certain function calls, to avoid repetitive calculations. This is very useful when writing recursive functions. Take the fibonacci example again:

let fib(0) := 0
let fib(1) := 1
let fib(n) := fib(n - 1) + fib(n - 2)

fib(10) # 55

The recursive call repeats a lot of calculations because is calling the same recursive function two times, and they will do the same (leading to exponential growth on the number of calls!). With large enough inputs (something like fib(50)), the program will be unacceptably slow. We can fix this just by adding a word:

let fib(0) := 0
let fib(1) := 1
let memoize fib(n) := fib(n - 1) + fib(n - 2)

fib(10) # 55

Adding memoize before defining a function will save the results computed from that call, so they can used later, when the function is called with the same arguments again.

Appendix

This chapter contains reference material you may find useful when learning and using Komodo.

Builtin types

Komodo has a few built-in types:

  • Integers: Signed, arbitrary precision integers. You can write them in decimal form, or the usual prefixed binary (0x), octal (0o) or hex (0x) form.

  • Decimals: Signed, arbitrary precision decimal numbers. You can write them in decimal form with a dot.

  • Fractions: Signed, arbitrary precision fractions, made with Integers. You can write them like this: 1 // 2.

  • Characters: Old-fashioned ASCII characters. You can write them like this: 'x', where x is a representation of some ASCII value. (Support for escaped characters is pretty bad right now!)

  • Strings: A bunch of characters, ordered. You can write them like this: "Hello, world!".

    Note: Many programming languages use the built-in list type to define strings. Komodo does not do this. The string type is completely different and defined independently in the interpreter.

  • Lists: An ordered collection of anything. You can write them in two ways:

    • By extension: [1, 2, 4, 8]
    • By comprehension: [ 2**k : k in 0..4 ]

    Both of these examples describe the same thing! I should also remind you that the elements of a list can be of different type. This is valid: [1, '2', "3"].

    Note: You probably noticed the 0..4 expression. This is a range, and it behaves exactly like you expect: It goes from 0 to 3 (it always excludes the last number).

  • Tuples: An ordered collection of anything. Wait... That's the same as lists! Yes. There are differences, though. You can't do operations with tuples just like you can with lists, and you can't compose them like we did above with lists. You can write them like this: ('1', 11, "111").

  • Sets: An unordered and extendable collection of anything. You can write them like this: { 1, 2, 3 }.

  • Functions: Pieces of code that receive values and return a value. There are two ways of writing them:

    • Named, with patterns:
    let f(0) := 25
    let f(n) := f(n - 1) + 1
    
    • Anonymously:
    n ->
        let x = n + 2
        x*x
    

    With this syntax, the last expression (i.e x*x) is returned as the result. Of course, this is an expression and you can save it to a value, or execute it in place like this: (x -> x * 2)(1).

    Note: You may have noticed that we used parenthesis to denote a series of steps. This is the only case where something that could be a tuple will be interpreted in another way.

  • Dictionaries: A collection of key-value pairs. There are no restrictions on the keys or the values. You can use anything at the same time! You can write them like this: {"a" => 5, [] => 'b'}.

Data structures

Apart from the primitive types, Komodo has three main data structures: Lists, Sets and Dictionaries.

List

Lists are ordered, possibly mutable and non-growable. They can be written exhaustively:

let evens := [0, 2, 4]

Or by comprehension:

let evens := [2*i for i in 0..3]

You can create a new list by putting a new element at the beggining of an existing list:

[5|[1, 2]] = [5, 1, 2]

They can be concatenated with each other:

[1, 2, 3] + [4, 5, 6] = [1, 2, 3, 4, 5, 6]

And multiplied:

[1, 3]*2 = [1, 3, 1, 3]

Set

Sets are unoredered, inmutable and non-growable. They can be written exhaustively:

let Z3 := {0, 1, 2}

Or by comprehension:

let Z3 := {i for i in 0..3}

They can be joined (with the addition operator):

{1, 2} + {1, 3} = {1, 2, 3}

Dictionary

Dictionaries are unordered, inmutable collections of key-value pairs. The pairs can be anything. They can be defined exhaustively:

let dict := {
    5 => 7,
    "1" => 1,
    [] => 0
}

Control flow structures

Komodo includes some control flow structures from imperative languages.

if

These are expressions, and always are complete.

if 5 % 2 = 0 then
    "5 is even"
else
    "5 is odd"

for

Although these are expressions, they are not meant to return anything. They always return an empty tuple ().

for i in 0..5 do
    let x := i*i
    println(x)

Keywords

The following list contains all the keywords that Komodo uses. You can't name anything with them. Komodo is a small language, so it's not that bad of a problem to remember these.

  • as: to define aliases of imported modules
  • do: part of the for loop syntax
  • else: part of if expressions
  • false: Boolean false literal
  • for: loop over items
  • from: for importing some names from modules
  • if: for if expressions
  • import: for importing modules
  • in: operator for membership
  • let: declare an inmutable value
  • then: part of if expressions
  • true: Boolean true literal
  • var: declare a mutable value

Operators and symbols

Infix operators

This list contains all the infix operators, an example of how the operator would be used, a short explanation, and its precedence. An operator with a higher precedence will be grouped before an operator with less precedence.

OperatorExampleExplanationPrecedence
ina in []Membership operator1
..0..10Range generation2
||true || falseLogic OR3
&&true && falseLogic AND4
>1 > 0"Greater" comparison5
>=1 >= 0"Greater or equal" comparison5
<1 < 0"Less" comparison5
<=1 <= 0"Less or equal" comparison5
/=9 /= 9Negated equality5
='a' = 'a'Equality5
^2 ^ 3Bitwise XOR6
&1 & 0Bitwise AND7
<<1 << 2Left shift8
>>8 >> 2Right shift8
-8 - 3Arithmetic substraction9
+1 + 1Arithmetic addition9
/1 / 2Arithmetic division10
%5 % 7Division remainder10
*5 * 7Arithmetic multiplication10
**2 ** 5Exponentiation11

Prefix operators

This list contains all the prefix operators, an example of how the operator would be used, and a short explanation.

OperatorExampleExplanation
~~1Bitwise negation
!!falseLogic negation
--5Additive inverse