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.
Because it is:
- 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.
- 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.
- 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:
- Module system (no header files!)
- Support for generics
- Full immutability
- No reference type, no pointer, no manual memory management
- Powerful concurrency model
- First class functions
- Support for sum types and hash map types
- And much more!
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:
- 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.
- 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.
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 |
- Concurrency means built-in language level support.
dotLang consists of these components:
- The language manual (this website).
dot
: A command line tool to compile, debug and package source code.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.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 of dotLang in a notation similar to EBNF can be found here
- Import a module:
queue = import("/core/std/queue")
(you can also import from external sources like GitHub). - Primitive types:
int
,float
,char
,byte
,bool
,string
,type
,nothing
. - Bindings:
my_var:int = 19
(type is optional, everything is immutable). - Sequence:
my_array = [1, 2, 3]
(type ofmy_array
is[int]
, sequence of integers). - HashMap:
my_map = ["A":1, "B":2, "C":3]
(type ofmy_map
is[string:int]
, hash map of string to integer) - Named type:
MyInt = int
(Defines a new typeMyInt
with same binary representation asint
). - Type alias:
IntType : int
(A different name for the same type). - Struct type:
Point = struct(x: int, y:int, data: float)
(Like Cstruct
). - Struct literal:
location = Point{x:10, y:20, data:1.19}
. - Union type:
MaybeInt = int | nothing
(Can store either of two types, note that this is a named type). - Function:
calculate = fn(x:int, y:int -> float) { x/y }
(Functions are all lambdas, the last expression in the body is return value). - Concurrency:
my_task := processData(x,y,z)
(Start a new micro-thread and evaluate an expression in parallel). - Generics:
ValueKeeper = fn(T: type -> type) { struct{data: T} }
(A function that returns a type) - Generics:
push = fn(x: T, stack: Stack(T), T: type -> Stack(T)) { ... }
- Enum:
DayOfWeek = enum [saturday, sunday, monday, tuesday, wednesday, thursday, friday]
- Errors:
result = validateData(a,b,c)@{makeError(InvalidArgument)}
#
Comment.
Access struct members()
Function declaration and call{}
Code block, multiple selection from module namespace, error check, struct declaration and literals[]
Sequence and hashMap|
Union data type->
Function declaration//
Nothing-check operator:
Type declaration (binding, struct field and function inputs), type alias, struct literal=
Binding declaration, named type_
Place-holder (lambda creator and assignment)::
Function call composition@
Error check?
If operator&
Type inference for struct literals:=
Parallel execution..
Access inside module
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
- Use 4 spaces indentation.
- You must put each statement on a separate line. Newline is the statement separator.
- Naming:
SomeDataType
,someFunction
,some_data_binding
,some_module_alias
. - If a function returns a type (generic types) it should be named like a type.
- If a binding is a reference to a function, it should be named like that function.
- You can use
0x
prefix for hexadecimal numbers and0b
for binary. - You can use
_
as digit separator in number literals.
Operators are mostly similar to C language:
- Conditional operators:
and, or, not, ==, !=, >=, <=
- Arithmetic:
+, -, *, /, %, %%, >>, <<, **
- Note that
==
will do a comparison based on contents of its operands. A // B
will evaluate to A if it is notnothing
, else it will be evaluated to B (e.g.y = x // y // z // 0
).A ? B
will evaluate boolean expression A first, if true will evaluate to B, otherwise will evaluate tonothing
. This can be mixed with//
to provide ifElse construct.- Example:
home_dir = (is_root ? "/root") // (is_default_user ? "/default") // (is_unknown ? "unknown") // "/tmp"
- Conditional operators return
true
orfalse
which are equal to1
and0
respectively when used as index of a sequence. - Comments can appear anywhere in the code and start with
#
. Anything after#
till end of the line is comment. - Meta comments start with
##
as first two characters of the line and can be defined for a binding, type or function. These will be scanned with tools to automatically generate documentation. If##
is the only thing in the line, it starts a block comment until another##
appears in the file.
##
some docs about this function
x:int - comments about this input
-> string - comments about the output
##
process = fn(x:int -> string) { ... }
##Meta comment in one line
x = 12
##
some docs about this type
x:int - comments about this field
y: string - comments about the y
##
DataTypeX = struct {x:int, y:string}