A minimal but powerful programming language based on C, Go and Haskell
Types are blueprints which are used to create values for bindings.
Types can be basic (integer number, character, …) or compound (sequence, map, struct, union).
Syntax: int, float, byte, char, string, bool, nothing
Notes:
int
type is a signed 8-byte integer data type.float
is double-precision 8-byte floating point number.byte
is an unsigned 8-bit number.char
is a single unicode character.
'a'
).string
is a sequence of characters.
\"
.bool
type is same as int but with only two valid values. true
is 1 and false
is 0.nothing
is a special type which is used to denote empty/invalid/missing data. This type has only one value which is the same identifier.Examples
int_val = 12
float_val = 1.918
char_val = 'c'
bool_val = true
str1 = "Hello world!"
str2 = "Hello" + "World!"
n: nothing = nothing
byte_val: byte = 119 #note that it is optional to mention type of a binding after its name
T
, and is shows with [T]
notation.x[i]
notation where i
is index number.[]
represents an empty sequence.slice, map, reduce, filter, anyMatch, allMatch, ...
Examples
x = [1, 2, 3, 4]
#a 2D matrix of integer numbers
x: [[int]] = [ [1, 2], [3, 4], [5, 6] ]
#merging multiple sequences
x = [1, 2]+[3, 4]+[5, 6]
int_var = x[10]
#this is definition of string type
string = [char]
[KeyType:ValueType]
to define a map type.[:]
notation.slice, map, reduce, filter, anyMatch, allMatch, ...
Examples
pop = ["A":1, "B":2, "C":3]
data = pop["A"]
enum
keyword and it will be an enum type.MyEnumType = enum [sequence of literals]
switch
on enums which make sure all cases are covered.Examples
saturday=1
sunday=2
...
DayOfWeek = enum [saturday, sunday, ...]
x = [saturday: "A", sunday: "B", ...][my_day_of_week]
#definition of type bool
true=1
false=0
bool = enum [true, false]
T1|T2|T3|...
.Examples
int_or_float: int|float = 11
int_or_float: int|float = "ABCD"
my_int = int(int_or_float) #this will fail if input binding does not have an int
maybe_int = int|nothing(int_or_float) #if binding has a float, you will get a nothing as a result of this cast
Type{field1:value1, field2:value2, ...}
.nothing
, they will be set to nothing
.&{...}
notation (Example B).Examples
#defining a struct type
Point = struct {x:int, y:int}
#create a binding of type Point, defined above
point2 = Point{x:100, y:200}
point2new = Point{100, 200}
#update an existing struct binding and save as a new binding
point4 = Point{x:point3.x, y : 101}
process = fn(x: struct {id:int, age:int} -> int) { x.age }
#A
another_point = Point{my_point, x:10}
third_point = Point{point1, z: 10, delta: 9}
#B
switchOnValue(my_number, &{value: 10, handler: AAA}, &{12, BBB}, &{13, CCC})
NewType = UnderlyingType
.Examples
MyInt = int
IntArray = [int]
Point = struct (x: int, y: int)
T : X
notation to define T
as another spelling for type X
.T
and X
will be exactly the same thing.X
on the right must be a type name. It cannot be definition of a type.Examples
MyInt : int
process = fn(x:MyInt -> int) { x }
int|string
vs int|string
).T(value)
notation to cast value to a specific type.T|nothing
to do a safe cast. You will get nothing
it type T is not inside the union binding.1
or "Hello world"
) will get value of the most primitive type inferred by the compiler (int
, string
, …).1
literal is an int
literal not a named type that maps to int
.Examples
MyInt = int
myint_var = MyInt(12)
Functions (or lambdas) are a type of binding which can accept a set of inputs and gives an output.
For example fn(int,int -> int)
is a function type (which accepts two integer numbers and gives an integer number) and fn(x:int, y:int -> int) { x+y }
is a function literal.
For generics (types and functions) see Advanced section.
functionName = fn(name1: type1, name2: type2... -> OutputType1, OutputTyp2, ...) { code block }
_
to ignore one of them.nothing
(Example C)._test
and have no input/output are considered unit test functions. You can later instruct compiler to run them (Example D).assert
core function that can be used for checking assertions. You can disable assertions with a compiler flag.::
operator (Example E).Examples
myFunc = fn(x:int, y:int -> int) { 6+y+x }
tester = fn(x:int -> int, string) {x+1, "a"}
int1, str1 = tester(100)
int1, _ = tester(100)
_, str1 = tester(100)
log = fn(s: string -> nothing) { print(s) } #this function returns nothing, pun not intended
process2 = fn(pt: Point -> int,int) { pt.x, pt.y } #this function returns a struct
process = fn(x: int|Point -> int) { ... } #this function can accept either int or Point or int|Point as input
process = fn(x:int -> int)
{
#if x<10 return 100, otherwise return 200
[x<10: 100, x>=10: 200][true]
}
#A
process = fn(x:int -> int) { x+1 }
process2 = process
sorted = sort(my_sequence, fn(x,y -> int) { x-y })
Adder = fn(int,int->int) #defining a named type based on a function type
sort = fn(x: [int], comparer: fn(int,int -> bool) -> [int]) {...} #this function accepts a function
map = fn(input: [int], mapper: fn(int -> string) -> [string]) ...
#B
process = fn{ 100 }
#C
seq = fn(start_or_length:int, end:int|nothing -> ...)
...
x = seq(10)
y = seq(1,10)
add = fn(a:int, b:int ->int) { a+b }
g = add(1,2)
#D
_testProcessWithInvalidInput = fn{...}
#E
resut = f(g(x))
result = x :: g :: f
# calculate average score for new good students
student :: filter(isGoodStudent, _) :: map(createNewStudent, _) :: calculateAverage
MyInt = int
, you cannot call a function which needs an int
with a MyInt
binding.int | nothing
and parameter is an int
it is a valid function call (But not the other way around).fn(T->U)
any function that can accept T (or more) and returns U (or less) works fine._
to define a lambda based on an existing function. Just make a normal call and replace the lambda inputs with _
(Example A).Examples
rr = fn(nothing -> int) { x + y } #here x and y are captures from parent function/struct
test = fn(x:int -> PlusFunc) { fn(y:int -> int) { y + x } } #this function returns a lambda
fn(x:int -> int) { x+1} (10) #you can invoke a lambda at the point of declaration
#A
lambda1 = process(10, _, _) #defining a lambda based on existing function
#B
ff = fn(x:int -> int) { ff(x+1) }
=
(Example B).Syntax:
identifier = expression
identifier : type = expression
Examples
#A
x : int = 12
#type is inferred
g = 19.8
#B
a,b = Tuple{1, 100}
#C
a,_ = point
a,_ = single_element_struct
There are different types of resources which needs handling but we can categorize them into two main groups: Memory and others (sockets, network connections, DB connections, files, threads, …).
The first type is handled via GC and the second type is automatically freed by runtime when the corresponding binding goes out of scope. These are similar to C++ destructors, but implemented only for scarce system resources and handle deallocation of those resources when they are no longer needed.
Any other mechanism to free/cleanup a resource, exposes mutation to the language which causes a lot of confusion.