5. Description of Functions, Operators and Libraries - naver/lispe GitHub Wiki

Indexes

Formalism

A few notes on the particularities of LispE regarding its formalism:

Compiling

When a LispE program is compiled, it is stored in a List, with __root__ as the top function:

(setq v (iota 10))
(println (car v))

Thus, the above code is compiled into the following list:

(__root__ 
   (setq v (iota 10)) 
   (println (car v)))

__root__ behaves as a block with some specific initialisations to execute a program.

It is possible to access the whole program in memory with _root, a variable that contains a pointer to this list.

Composition: "."

The composition operator . in LispE allows for more concise and readable code by reducing the need for parentheses. This operator enables the combination of multiple function calls into a single embedded expression.

For example, a typical LispE expression that applies two functions, filterlist and maplist, to a list generated by iota 10 would require nested parentheses:

(filterlist (\(x) (< x 10)) (maplist (\(x) (* 2 x)) (iota 10)))

However, with the composition operator . , you can simplify this expression by composing the filterlist and maplist functions:

(filterlist (\(x) (< x 10)) . maplist (\(x) (* 2 x)) (iota 10))

This version of the code is easier to read and understand, as it clearly shows that you're applying filterlist to the result of maplist.

Moreover, there's no limit to the number of functions you can compose using the . operator. For instance, you can calculate the square root of the sum of a sequence of numbers with:

(sqrt . sum . iota 10) ; 7.4162

Furthermore, the composition operator . in LispE is not only limited to composing functions but also allows these functions to have their own arguments. This means you can pass arguments to the functions being composed.

For example, in the expression:

(filterlist (\(x) (< x 10)) . maplist (\(x) (* 2 x)) (iota 10))

The filterlist function is being composed with the maplist function. Both of these functions have their own arguments: \(x) (< x 10) for filterlist and \(x) (* 2 x) for maplist.

With flip

You can use the flip operator in expression such as:

(flip . / 5 . car '(1))

This expression is then a composition of three functions: flip, / 5, and car. Here's how it works:

  • It takes the first element of the list (1), which is 1.
  • It feeds this into / 5, but because of flip, it actually computes 1 / 5.
  • The result is 0.2.

Therefore, (flip . / 5 . car '(1)) returns 0.2. It's a concise way to perform a series of operations on an input using function composition in LispE.

This feature further enhances the flexibility and power of function composition in LispE, allowing for more complex operations to be performed in a concise and readable manner.

Strings of characters

LispE manipulates strings as expressions between two "...".

The '\' is used as escape character. LispE also recognizes expressions: \n, \r and \t

(setq s "This and that and there")

Long strings

You can also use the character accent grave (or backquote): `` to identify long strings containing all kinds of characters:

(setq s `This is one " and one " in the same string`)

Byte strings

By default, LispE manipulates Unicode strings, where each character is encoded on 32 bits. You can also manipulate byte strings, which are created either with stringbyte or with the notation b"..".

(setq s (stringbyte "test"))
(setq sb b"test")

A byte strings is a UTF-8 encoded string, where most Latin characters are encoded on 8 bits. This encoding is usually much more compact than regular Unicode strings, but is also slower to handle.

You can convert strings in both format with string or stringbyte:

; Initialisation as a string byte
(setq s b"Test")

; Conversion to a regular Unicode string
(setq r (string s))

; And back 
(setq u (stringbyte r))

Automatic Parentheses Completion

When you have a long list of parentheses that closes an expression, you can use ] to generate the right amount of closing parentheses...


; this will automatically generate all the missing parentheses
(setq x (cons 'e '(]

Dictionaries {x:v y:u etc.}

LispE accepts dictionary definitions with braces (à la Python). Keys and values must be separated by a ":". There are three types of dictionaries:

  • Indexed on strings (implemented as a std::unordered_map, keys are NOT sorted out)
  • Indexed on integers (implemented as a std::unordered_map, keys are NOT sorted out)
  • Indexed on numbers (implemented as a std::unordered_map, keys are NOT sorted out)

Depending on the description provided, LispE can create either one or the others. More precisely, if all the keys are numeric, it will be a dictionary indexed on numbers, conversely if at least one of the keys is a string, it will be a dictionary indexed on strings. If you are not sure that your description leads to the right type of dictionary, it is better to use key, keyi or keyn to create your dictionaries.

Important: this way of declaring a dictionary leads to the construction of a set of key instructions that will be evaluated to generate a dictionary later on. It is then possible to add variables in this description, since the evaluation will be take later.


; Numerical dictionary
(setq d {12:45 45:67 90:20})


; Alphabetical dictionary, numeric keys become strings
; If a key is anything but a number, then the whole definition
; becomes an alphabetical dictionary
(setq d {"a": 2 "b":10 12:45 45:67 90:20 "k":9})

; or you can use the dictionary type instruction:

(setq d (dictionary "a" 2 "b" 10 "12" 45 "45" 67 "90" 20 "k" 9))

Note that the key:value are separated from each other by a space .

Trees

Trees are dictionaries that are based on: std::map. Keys are automatically ordered when inserted. As for dictionaries, LispE provides three different types of trees, which are created with one of the following instructions: dictionarytree, dictionarytreei or dictionarytreen:

  • dictionarytree: Indexed on strings (implemented as a std::map, keys are sorted out)
  • dictionarytreei: Indexed on integers (implemented as a std::map, keys are sorted out)
  • dictionarytreen: Indexed on numbers (implemented as a std::map, keys are sorted out)

(setq tr (dictionarytree "a" 1 "b" 2 "c" 3 "o" 4 "p" 5)) ; {"a":1 "b":2 "c":3 "o":4 "p":5}

; Note that in case of a dictionary, key ranks are not ordered:

(setq d (dictionary "a" 1 "b" 2 "c" 3 "o" 4 "p" 5)) ; {"p":5 "c":3 "b":2 "o":4 "a":1}

Note that the key:value are separated from each other by a space .

Data Dictionary: @{k:v ..}

The following formalism: @{k:v..} is also used to create a dictionary. However, the difference with the description above, is that in this case a dictionary is directly constructed, while the other formalism compiles the expression into a set of instructions.

Important Since the evaluation is done at compile time, you cannot have variables in this expression...

Furthermore, a data dictionary is by definition indexed on strings.

; This expression is directly compiled into a dictionary
(setq d @{"a": 2 "b":10 12:45 45:67 90:20 "k":9})

Sets: {v1 v2 ...}

LispE also provides sets, which can either contains strings, integers or numbers (sets, seti, setn). It also provides set to create sets of objects.

A set can also be created with: {...}.

(setq st {1 2 3 4})
(in st 2) ; true
(in st 100) ; nil

Heaps

LispE provides a heap structure, which can be used to store elements in specific order defined by a operator or a lambda expression.

(see heap for more information)

shorts, floats, numbers, integers, strings

LispE offers some specific types to handle lists of values, instead of lists of objects, which means that values can be stored and removed without any kind of reference counting management.

Each of these methods also corresponds to a function that will transform a regular list into a list of values.

These objects are returned by methods such as range or iota but also by many string methods.

(iota 10.0) ; yields the 'list of numbers': (1 2 3 4 5 6 7 8 9 10)

(numbers '(1 2 3 4)) ; transforms this list containing numbers into a list of numbers.

Complex numbers

LispE also provides a full integration of complex numbers. It is possible to add, multiply, divide and put to the power complex numbers together or with other numerical values.

A complex number is created with the type: (complex real imaginary).

This type takes two float arguments and returns a complex number.

It is also possible to use the following pattern: 12,6i to create specific complex numbers, with actual values.

  • Note that this is also the way a complex number is displayed as a string.
  • Note that i can be written either as: (complex 0 1) or 0,i.
  • Note that -i can be written either as: (complex 0 -1) or 0,-i.

;These two declarations are equivalent
(setq c (complex 12.23 1))
(setq cc 12.23,i)

(setq cc (+ cc 0,i)) ; cc is now 12.23,2i

matrix_[number|float|short|integer|string|stringbyte]

Matrix is another type, which is provided to handle matrices as a list of lists. However, you need to provide its size in rows and columns.

  • (matrix_number r c initial): this will create a matrix with r rows and c columns, initialised with the value initial.

Note that initial is optional. The default value is 0.

(matrix_integer 2 3 1) ; yields ((1 1 1) (1 1 1))
(matrix_integer 3 5) ; yields ((0 0 0 0 0) (0 0 0 0 0) (0 0 0 0 0))

A matrix object can also be created with a rho instruction.

(rho 3 3 (integers 1 3 4)) ; yields a matrix_integer: ((1 3 4) (1 3 4) (1 3 4))

Note that a matrix_integer was created because we provided rho with a list of integers.

(rho 3 3 (numbers 1 3 4)) ; yields a matrix_number: ((1 3 4) (1 3 4) (1 3 4))

Note that rho can also returns the matrix dimension:

(rho (matrix_float 5 7)) ; yields (5 7)

To access an element of a matrix, you can use the operator at with two indexes.

; we create a matrix with rho
(setq m (rho 4 5 (integers 1 4 9 10 8))) ; m is ((1 4 9 10 8) (1 4 9 10 8) (1 4 9 10 8) (1 4 9 10 8))

; we display the element at row: 2, column 3
(println (at m 2 3)) ; yields 10

; We modify it
(at m 2 3 100) ; m is now ((1 4 9 10 8) (1 4 9 10 8) (1 4 9 100 8) (1 4 9 10 8))

tensor_[number|float|short|integer|string|stringbyte]

A tensor object is a multidimensional matrix beyond NxM. A tensor can either be created with all values set to 0, or through a rho instruction. Note that the outer-product can also create a tensor objects.

(tensor_float 2 3 3) ; yields (((0 0 0) (0 0 0) (0 0 0)) ((0 0 0) (0 0 0) (0 0 0))

(rho 2 3 3 (iota 18)) ; yields a tensor_integer:(((1 2 3) (4 5 6) (7 8 9)) ((10 11 12) (13 14 15) (16 17 18)))

Default Parameters and Variadic Functions

When defining the parameters of a function, via defun or lambda, it is possible to declare certain parameters as optional.

To do this, simply declare the parameters as a list. If these lists contain two items, then the second one is considered as the default value.


(defun test (i (j -2)) (+ i j))

(print test(10 20))
30

(print test(10))
8

Variadic Functions

LispE provides a specific notation for functions that can take a variable number of arguments. This notation is also a list where the first element is the empty list and the second is a parameter in which supernumerary arguments are stored as in a list. This description should be the last element of your parameter definition.

; All arguments after 'x' are stored in 'l'
(defun variadic(x (() l)) ...)

Example


; this function takes at least two arguments
(defun test(x y (() l))
    (println x y l)
)

(test 10 20 30 45 90 900 10) ; displays: 10 20 (30 45 90 900 10)

Important: These list items should always be declared at the end of the parameter list, not in the middle.

Predefined variables

_args

This variable contains the list of values passed as arguments on the command line.

_current

This variable contains the path name of the current file being executed.

_pi, _e, _phi, _tau

These values are predefined with:


_pi = 3.14159
_tau = 2 * _pi
_e = 2.71828
_phi = 1.61803


Operators

Description of the different operators in operators

Infix

Note that LispE allows for an infix notation of mathematical expressions: see Infix