Jina

1, Jina

Jina is a programming language with sane memory management, actors, and a coherent type system and syntax
Mahsa Jina Amini was a 22 years old girl, murdered by the evil Islamic regime in Iran
at her funeral, these words were written on a stone above her grave:
beloved Jina, you will not die, your name will become a code
Jina has sane memory management, ie there is no garbage collector
https://www.toptal.com/software/eliminating-garbage-collector
to achieve that, Jina introduces four special markers for types:
, immutable data: T
, shared immutable data (static, or reference counted): T$
, immutable borrow: T&
, mutable borrow: T!&
, mutable data: T!
aliasing rules in variable definitions
when a variable of type "T" is aliased by:
, a variable of type "T", move (do not copy heap) if not used in the following code; copy otherwise
, a variable of type "T$", move or copy
, a variable of type "T&", borrow
, a variable of type "T!&", it's compile'time error
, a variable of type "T!", move or copy
when a variable of type "T$" is aliased by:
, a variable of type "T", copy
, a variable of type "T$", create reference, and (if not static) increase reference count
, a variable of type "T&", borrow
, a variable of type "T!&", it's compile'time error
, a variable of type "T!", move or copy
when a variable of type "T&" is aliased by:
, a variable of type "T", copy
, a variable of type "T$", copy
, a variable of type "T&", borrow
, a variable of type "T!&", it's compile'time error
, a variable of type "T!", copy
when a variable of type "T!&" is aliased by:
, a variable of type "T", copy
, a variable of type "T$", copy
, a variable of type "T&", borrow
, a variable of type "T!&", borrow
, a variable of type "T!", copy
when a variable of type "T!" is aliased by:
, a variable of type "T", move or copy
, a variable of type "T$", move or copy
, a variable of type "T&", borrow
, a variable of type "T!&", borrow
, a variable of type "T!", move or copy
assignment rules
when a value of type "T" or "T!!" is assigned to a variable of type "T!" or "T!!": move or copy
when a value of type "T$", "T&" or "T!" is assigned to a variable of type "T!" or "T!!": copy
borrow types will be tagged by their owner
borrow tags will be inherited by further borrows
function arguments with borrow types are tagged with integers, starting from one
for functions with multiple borrow arguments, that also return a borrow type,
and the tag of returned type is inherited from any argument other than the first one,
in the return type, it must be indicated that which borrow argument will be inherited
A, B&, C& -> C&2
the borrow tag of variables captured in a closure, will be prefixed with "PARENT_"
only borrows with matched tags can be aliased by each other (checked at compile'time)
this simple rule automatically guarantees correct lifetimes, cause it means:
, we can't assign a locally created (or moved) value, to a mutable borrow variable captured in a closure
, we can't return a borrow from a function, unless it's inherited from one of the function's arguments
types containing borrow types, will have a borrow marker themselves
this is necessary for their borrow tags to be statically determined
also types containing mutable types, will be mutable themselves
in other words, borrow and mutability markers can't be wrapped, they always leak out
in addition, borrow and shared markers are penetrative, ie:
, borrow types can't contain non'borrow types
, share types can't contain non'share types (though all of them use the parent's reference counter)
type definitions (immutable types) can only be made of immutable and share types
A := B, C$
share types can only contain share types
a :A$ = ...
a.1 :B$
a.2 :C$
immutable borrow types can only contain immutable borrow types
a :A& = ...
a.1 :B&
a.2 :C&
mutable borrow types can only contain borrow types
a :A!&
a.1 :B!&
a.2 :C&
mutable types can only contain mutable and shared types
a :A!
a.1 :B!
a.2 :C$
functions in Jina are closures, ie they capture their environment
each closure has its own distinct type that includes the type of the captured variables
but we do not work with closure types directly
what we care about is the interface that they implement
c[t::(A,B->C)] :t! = { a :A, b :B -> C | ... }
c[t::(A,B->C)] :t = { a :A, b :B -> C || ... }
a mutable borrow closure (with type "t!") can mutably borrow captured variables
but an immutable non'borrow closure (with type "t") can't
sharing mutable data in concurrent parts of a program is problematic
a data race happens when these three behaviors occur:
, two or more pointers access the same data at the same time
, at least one of the pointers is being used to write to the data
, there's no mechanism being used to synchronize access to the data
to deal with it, programming languages choose different approaches:
, some implement complicated and error prone lock mechanisms
, some abandon concurrency, and make single threaded programs
, functional programming languages avoid mutability as much as possible
and when mutability is necessary, they use monads or algebraic effects to control shared mutability
avoiding mutability, and the need for aggressive garbage collection, results in bad performance
Jina uses actors for asynchronous (including concurrent) programming,
and controls aliasing (sharing) and mutability using type markers,
ie the same approach used to deal with memory management
messages sent to actors are own closures, and can't capture borrows
thus messages can safely be called concurrently, since they can't change their captured environment
actors are opaque types, ie their internal components are not accessible,
and are referred to by an ID, instead of a memory address
actors (and their messages) can only be destroyed explicitly
this implies that an ID can refer to a deallocated actor
when a message is sent to a deallocated actor, it will be ignored
reference counting combined with interior mutability can create reference cycles
https://doc.rust-lang.org/book/ch15-06-reference-cycles.html
in Jina interior mutability is only possible using actors
and since actors are not reference counted, there is no reference cycles in Jina
Jina does not hide inherent complexity; in fact it bolds it, so it can be seen and avoided
by inherent complexity i mean a complexity which can not be abstracted away completely
ie if we try to hide it, it will re'emerge somewhere else
in fact, hiding inherent complexity usually leads to choosing the wrong approach
maybe the most prominent example of an inherent complexity in programming is memory management
rather than hiding it behind a garbage collector, Jina uses borrow type markers,
to eliminate the need for a garbage collector in synchronous programming
and for asynchronous parts, we use shared type marker and actors
types show us what we can do with the data, ie which operations are valid
subtyping is problematic:
https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
https://www.tedinski.com/2018/06/26/variance.html
i feel that this problem was the motivation behind dynamic typing (another bad idea)
to avoid this problem, some languages (eg Rust) have two kinds of types:
, concrete types can be instantiated, but cannot have subtypes
, abstract types (also called traits or interfaces) cannot be instantiated, but can have subtypes
although Rust has trait objects (dynamic interfaces) with the same problem regarding variance
https://users.rust-lang.org/t/vector-covariant-in-its-element-type/80582
https://stackoverflow.com/questions/55200843/what-does-it-mean-that-box-is-covariant-if-boxdyn-b-is-not-a-subtype-of-boxdy
in Jina we have interfaces, but there is no dynamic interfaces
instead of subtyping, in Jina we have convertible types
it's like in mathematics where we can easily do arithmetic across integer, rational, real and complex numbers
the idea is to only do this for types that can be converted with negligible run'time overhead
compile'time polymorphism (eg templates and generics) has different linking semantics
ie each compilation unit generates its own specialized version,
which can create duplicates, and thus increase the size of the produced binary
but run'time polymorphism (eg trait objects, or inheritance plus virtual functions) has serious problems too:
, it destroys covariance
, there is some run'time overhead (3 times regular function calls)
Jina also has an unsafe portion to interoperate with the low level system, and existing C/C++ libraries
C++ (unlike Rust) allows us to have packages that are compiled independently as dynamic libraries,
then imported and used in other packages
C was one of the first attempts to make programming easier by introducing types and functions
after that, there was generally two approaches in creating more advanced programming languages
, garbage collection (the wrong approach): Java, C# ...
, C++: keep pointers for systems programming, but otherwise use references, move semantic, and RAII
C++ may look ugly on the outside, but to quote its creator:
"within C++, there is a much smaller and cleaner language struggling to get out"
and i think that language is "Jina"
Jina can be built and installed using UPM

2, syntax

comments:
;; comment line
;; comment
block
;;
comment
block
;
identifiers (variable names) start with an alphabetic character, and can include numbers and apostrophe:
abc'efg0123
defining an immutable variable:
v :T = ...
v = T()
defining a shared variable ($ is optional for variables defined outside of funtions, aka static variables):
v :T$ = ...
v$ = T()
defining a immutable borrow variable:
v :T& = ...
v& = T()
defining a mutable variable:
vm :T! = ...
vm! = T()
defining a mutable borrow variable:
v :T!& = vm
v!& = vm
assignment to mutable variables:
v. = ...
numeric types:
, Float: floating'point numbers with arbitrary precision and ball arithmetic
, Float2: floating'point numbers with double precision ("double" in C++)
, Float1: floating'point numbers with single precision ("float" in C++)
, Int: arbitrary sized integer
, Int8: 8 bytes integer ("std::int64_t" in C++)
, Int4: 4 bytes integer ("std::int32_t" in C++)
, Int2: 2 bytes integer ("std::int16_t" in C++)
, Int1: 1 byte integer ("std::int8_t" in C++)
, Int'u: word'sized unsigned integer ("std::size_t" in C++)
, Int'u4 4 bytes unsigned integer ("std::uint32_t" in C++)
, Int'u2 2 bytes unsigned integer ("std::uint16_t" in C++)
, Int'u1 1 bytes unsigned integer ("std::uint8_t" in C++)
numeric literals:
1.0
1.23e-4
0x123p-6 ;; point can't be used in hexadecimal floats, due to ambiguity with method calls
1'234'567'890
0x1234'5678'9ABC'DEF0
complex numbers can be made of any type implementing the "Num'r" interface
c :Complex[Int4] = 1 + 1i
c = 1 + 0i
c = Complex[Int4] 1 0
lists:
l :List[Int4] = [1, 2, 3]
indexing:
e :Maybe[Int4] = l_1
note that the type of the result of indexing above is actually "Maybe[Int4]&",
which is copied into the variable with type "Maybe[Int4]"
mutating lists:
l.put 4 at: 3 ;; [ 1, 2, 3, 4 ]
l.put 0 at: 0 ;; [ 0, 2, 3, 4 ]
text:
, Txt'b: a byte containing an ASCII code (equivalent to "char" in C++)
, Txt: UTF8 encoded text, implemented as a list of "Txt'b" values
character literals:
'a' ;; a byte whose value is the ASCII code for character "a"
' ' ;; a byte whose value is the ASCII code for the character with escape code " "
'\x12' ;; a byte with hexadecimal value 0x12
text literals:
t :Txt = "abc def"
text interpolation: "abc{x}"
alternative syntax that makes writing single word text easier:
'abc
multiline text, equivalent to "first line second line":
s = "
first line
second line
"
it's better to split long text into a list of lines:
s = """
first line
second line
"""
which is equivalent to:
["first line", " second line"]
dictionaries are indexed using text (instead of "Int'u" as in lists):
d :Dict[Int4] = [a: 1, b: 2, c: 3]
indexing:
d_'a
tuples:
r :(Int4, Int4, c: Int4) = 1, 2, c: 3
to access elements:
r.a
r.0
multiple assignment using tuple expansion:
a, b = 1, 2, 3
tagged fields can be expanded too, if the name of variables match:
a, b = a: 1, b: 2
mutating a tuple field:
r.a = 10
small tuples will be kept on stack, big ones on the heap
function:
f[t::(A,B->C)] :t = { a :A, b :B -> C || ... }
f ::(A,B->C) = { a :A, b :B -> C || ... }
f = { a :A, b :B -> C || ... }
function that can borrow from its environment:
f ::(A,B->C)!& = { a :A, b :B -> C | ... }
f ::(->)!& = { ... }
f = { ... }
function call:
f x y
which is equivalent to these forms:
f(x, y)
f b: y a: x
x, y >> f
default values for parameters:
f = { a = 1 , b = 2 || ... }
f b: 22
f 11
f()
conditional expression:
condition .then {} .else {}
condition1 .then {} .elif {condition2} {} .elif {condition3} {} .else {}
and or:
a && b
a \ b
which are equivalent to:
a .and {b}
a .or {b}
not: -a
there is no loop construct in Jina
instead we have iterators (types that implement Iter interface):
iter.each { x | ... }
actor:
a :Actor[S] = Actor.new S.new()
a.do { b :S!& || ... }
modules are files containing definitions
any definition whose name is the module's name, will be exported (ie are accessible outside of the module)
exported names can have these kind of extensions:
, a number
, a postfix starting with an apostrophe
, letters inside brackets (generics)
to access the definitions in a module which is inside a directory: dir.Definition
to hide a module so it can't be accessed from modules in the parent directory, or outside of the package,
append an apostrophe at the end of its file name
a package is a collection of modules
in a project directory, we can have multiple package directories
the name of the package directory is the package name, plus a ".jin" extension
packages are of two kinds:
, application packages that contain a file named "1.jin" (which must contain the init function)
, library packages
to use a library package in a another package, create a file named "your'chosen'name.p" with this format:
, first line is the name of the package
, second line is the URL of the project containing the package
, the format of the URL (where protocol can be gnunet, git):
protocol://address'of'the'project'containing'package'directory
, the third line can contain a public key, which will be used to check the signature provided by the project
if there is no URL, it refers to the current project
to use the definitions of a package:
dot'p'file'name.Definition
definitions in "jinit" package, are directly accessible
so there is no need for a "jinit.p" file, and prefixing with "jinit."
type definition:
T := a: A, b: B
defining methods (using a namespace):
;ns T
new = { a :A -> T ||
b = ...
a, b
}
m1 = { self!&, x :X -> Y ||
...
}
m2 = { self ||
...
}
to create an instance of the type:
i :T = a: x, b: y
i = T a: x, b: y
i = T.new a: x
accessing a member:
i.a
calling a method (note that there is no tuple field named "m", otherwise this will not work):
i.m x
which is equivalent to:
T.m i x
if a method has no argument (other than self), it can be called like this: i.m
enums:
Bool := #true #false
x :Bool = #true
x = Bool#true
type "?A" is a shortcut for "Maybe[A]"
Maybe[t] := #result t #null
x :?A = #null
x >> {
#result x | ...
#null | ...
}
intefaces:
;i I
m1 ::(self!&, X -> Y)
m2 = { self!& ||
;; default implementation
}
interface inheritance:
;i I ::I1 ::I2
...
defining the methods of a type that implements some interfaces:
;ns T ::I1 ::I2
m1 = ...
m2 = ...
;; I1
m3 = ...
m4 = ...
;; I2
...
generics:
T[g] := a: g, b: g
bounded generics:
T[x::I] := a: x, b: x
unsafe code to interoperate with low level system, and existing C/C++ code:
;sys ...
...
or:
;sys
...
;
beware! with great power comes great responsibility