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 = expressionidentifier : type = expressionExamples
#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.