dotLang

A minimal but powerful programming language based on C, Go and Haskell

View the Project on GitHub

Perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away.
(Antoine de Saint-Exupéry, translated by Lewis Galantière.)

dotLang is a general-purpose programming language which is built upon a small set of rules and minimum number of exceptions and a consistent set of specifications.

Why dotLang?

Because it is:

  1. Simple: There are a few key concepts and features that you need to learn. With a very smooth learning curve, you will not need to keep in mind an endless array of rules and exceptions.
  2. Powerful: The fact that dotLang is simple, combined with orthogonality of the tools that the language gives you, makes it an immersely powerful tool. You can mix and match different concepts easily and all the way, compiler is with you to do as much as possible.
  3. Fast: dotLang is a compiled language. Because of its simplicity, compiler is able to quickly compile a large source code set and also by using LLVM, the binary output will have a high performance.

dotLang is very similar to C but with some significant improvements:

  1. Module system (no header files!)
  2. Support for generics
  3. Full immutability
  4. No reference type, no pointer, no manual memory management
  5. Powerful concurrency model
  6. First class functions
  7. Support for sum types and hash map types
  8. And much more!

Introduction

After having worked with a lot of different languages (C#, Java, Scala, Perl, Javascript, C, C++ and Python) and getting familiar with some others (including Go, D, Swift, Erlang, Rust, Zig, Crystal, Fantom, OCaml and Haskell) it still irritates me that most of these languages sometimes seem to intend to be overly complex with a lot of rules and exceptions to keep in mind. This doesn’t mean I don’t like them or I cannot develop software using them, but it also doesn’t mean I should not be looking for a programming language which is simple, powerful and fast.

That’s why I am creating a new programming language: dotLang.

dot programming language (or dotLang for short) is an imperative, static-typed, garbage collected, functional, general-purpose language based on author’s experience and doing research on many programming languages (namely Go, Java, C#, C, C++, Scala, Rust, Objective-C, Swift, Python, Perl, Smalltalk, Ruby, Haskell, Clojure, Eiffel, Erlang, Elixir, Elm, Falcon, Julia, Zig, F# and Oberon-2). I call the paradigm of this language “Data-oriented”. This is an imperative language which is also very similar to Functional approach and it is designed to work with data. There are no objects or classes. Only data structures and functions. We have first-class and higher-order functions borrowed from the functional approach.

Two main objectives are pursued in the design and implementation of this programming language:

  1. Simplicity: The code written in dotLang should be consistent, easy to write, read and understand. There has been a lot of effort to make sure there are as few exceptions and rules as possible. Software development is complex enough. Let’s keep the language as simple as possible and save complexities for when we really need them. Very few (but essential) things are done implicitly and transparently by the compiler or runtime system. Also, I have tried to reduce the need for nested blocks and parentheses, as much as possible. Another aspect of simplicity is minimalism in the language. It has very few keywords and rules to remember.
  2. Performance: The source will be compiled to native code which will result in higher performance compared to interpreted languages. The compiler tries to do as much as possible (optimizations, dereferencing, in-place mutation, sending by copy or reference, type checking, phantom types, inlining, disposing, reference counting GC, …) so runtime performance will be as high as possible. Where performance is a concern, the corresponding functions in core library will be implemented in a lower level language.

Achieving both of the above goals at the same time is impossible, so there will definitely be trade-offs and exceptions. The underlying rules of design of this language are Principle of least astonishment, KISS rule and DRY rule.

As a 10,000 foot view of the language, the code is written in files (called modules) organized in directories (called packages). We have bindings (immutable data which can be functions or values) and types (Blueprints to create bindings). Type system includes primitive data types, sequence, map, enum, struct and union. Concurrency and lambda expression are also provided.

Comparison

Language First-class functions Sum types Full Immutability Garbage Collector Module System Concurrency* Generics built-in data types Number of keywords
C No Partial No No No No No 14 32
Scala Yes Yes No Yes Yes Yes Yes 9 ~27
Go Yes No No Yes Yes Yes No 19 25
Java Yes No No Yes Yes No Yes 8 50
Haskell Yes Yes No Yes Yes No Yes 63 28
dotLang Yes Yes Yes Yes Yes Yes Yes 8 9

Components

dotLang consists of these components:

  1. The language manual (this website).
  2. dot: A command line tool to compile, debug and package source code.
  3. core library: This package is used to implement some built-in, low-level features which can not be simply implemented using pure dotLang. This will be a built-in feature of the compiler/runtime.
  4. std library: A layer above core which contains some general-purpose and common functions and data structures. This is optional to use by developers.

Grammar

Grammar of dotLang in a notation similar to EBNF can be found here

Main features

  1. Import a module: queue = import("/core/std/queue") (you can also import from external sources like GitHub).
  2. Primitive types: int, float, char, byte, bool, string, type, nothing.
  3. Bindings: my_var:int = 19 (type is optional, everything is immutable).
  4. Sequence: my_array = [1, 2, 3] (type of my_array is [int], sequence of integers).
  5. HashMap: my_map = ["A":1, "B":2, "C":3] (type of my_map is [string:int], hash map of string to integer)
  6. Named type: MyInt = int (Defines a new type MyInt with same binary representation as int).
  7. Type alias: IntType : int (A different name for the same type).
  8. Struct type: Point = struct(x: int, y:int, data: float) (Like C struct).
  9. Struct literal: location = Point{x:10, y:20, data:1.19}.
  10. Union type: MaybeInt = int | nothing (Can store either of two types, note that this is a named type).
  11. Function: calculate = fn(x:int, y:int -> float) { x/y } (Functions are all lambdas, the last expression in the body is return value).
  12. Concurrency: my_task := processData(x,y,z) (Start a new micro-thread and evaluate an expression in parallel).
  13. Generics: ValueKeeper = fn(T: type -> type) { struct{data: T} } (A function that returns a type)
  14. Generics: push = fn(x: T, stack: Stack(T), T: type -> Stack(T)) { ... }
  15. Enum: DayOfWeek = enum [saturday, sunday, monday, tuesday, wednesday, thursday, friday]
  16. Errors: result = validateData(a,b,c)@{makeError(InvalidArgument)}

Symbols

  1. # Comment
  2. . Access struct members
  3. () Function declaration and call
  4. {} Code block, multiple selection from module namespace, error check, struct declaration and literals
  5. [] Sequence and hashMap
  6. | Union data type
  7. -> Function declaration
  8. // Nothing-check operator
  9. : Type declaration (binding, struct field and function inputs), type alias, struct literal
  10. = Binding declaration, named type
  11. _ Place-holder (lambda creator and assignment)
  12. :: Function call composition
  13. @ Error check
  14. ? If operator
  15. & Type inference for struct literals
  16. := Parallel execution
  17. .. Access inside module

Reserved keywords

Primitive data types: int, float, char, byte, bool, string, nothing, type

Core data types: error

Operators: and, or, not

Data type identifiers: fn, struct, enum

Reserved identifiers: true, false, import

Coding style

  1. Use 4 spaces indentation.
  2. You must put each statement on a separate line. Newline is the statement separator.
  3. Naming: SomeDataType, someFunction, some_data_binding, some_module_alias.
  4. If a function returns a type (generic types) it should be named like a type.
  5. If a binding is a reference to a function, it should be named like that function.
  6. You can use 0x prefix for hexadecimal numbers and 0b for binary.
  7. You can use _ as digit separator in number literals.

Operators

Operators are mostly similar to C language: