A purely functional, dynamically typed, parallel evaluated, lisp-like programming language
stack installInstall GHC 7.10.* and cabal and run the following commands:
git clone https://github.com/soupi/pureli
cd pureli
cabal sandbox init
cabal installTo run the pureli REPL, write:
pureliTo run a pureli program, write:
pureli <filepath>To run with parallel execution, add +RTS -N<n> to the end of an execution command, where is the number of core you would like to use
Adding a -s flag will print run time information at the end of the execution.
pureli examples/exponent.pli +RTS -N4 -sOr
pureli examples/exponent.pli +RTS -N2Pureli is a purely functional, dynamically typed, parallel evaluated, lisp-like programming language. These are all very big words, so let's analyze them one by one:
(if #t 1 "hello") possible to run.We will continue to explore these qualities that makes Pureli interesting and unique in future chapters.
Lets start by learning about Pureli's Atomic Expressions.
Atomic expressions are expressions that cannot be evaluated any further. In Pureli, you can find:
123, 0, -55192293384175798123471)12.0, -51.5, 0.01432)#t for true, #f for false. In Pureli every value other than #f is #t."Hello world!", "Pureλi is Great!")x, lines->str, println!) are symbols that evaluates to variables and functions.:hello, :x, :always-start-with-colons)void, Unit or () in other languages. (nil, ())Pureli derives it's syntax from LiSP, a family of programming languages with similar syntax and attributes.
LiSP, as it's name, is about List Processing. In lisp, everything is either an atomic expression or a list, which is denoted by parenthesis.
For example:
(this is a list)Lists can be nested:
(this is a (list inside (a list)))Lisp uses polish notation. When evaluated, the first argument serves as a command and the rests are arguments:
(+ 32 1 942 444)Expressions can be nested as well:
(+ 32 1 942 (+ 111 222 1 110))To stop an expressions from evaluating we use the command quote:
(quote (+ 2 2 3)) ;; => '(+ 2 2 3)It's result will be a data structure with these arguments, which can be evaluated using eval.
(eval (quote (+ 2 2 3))) ;; => 7Since the result of quote expression is a data structure, a list, we can use list operations such as
car, cdr, and ++ to manipulate it:
(eval (++ '(*) (cdr (quote (+ 2 2 3))))) ;; => 12In Pureli (and Lisp), code is data!
We use define to bind names to expressions:
(define x (+ 1 2 3))We can create our own anonymous functions using lambda:
((lambda (x y) (+ x y)) 2 3) ;; => 5If we want to create a function and give it a name, we can use define:
(define f
(lambda (x y) (+ x y)))Or use another form of define which is just a syntactic sugar:
(define f (x y)
(+ x y))define can only be used to introduce a name at the top level of a module, which will make it available to the entire module.
For local definitions, We can use let and letrec:
(let ([x 5] [y 1]) (+ x y)) ;; => 6
(letrec ([loop (lambda () (loop))]) (loop)) ;; => <infinite-loop>
(let ([loop (lambda () (loop))]) (loop)) ;; => error. cannot find loop in environmentIt is also possible to define functions with receives unevaluated parameters
(module main)
(define unless (test ~true-branch ~false-branch)
(if
test
false-branch
true-branch))
(define main
(println!
(unless
#t
(error "not thrown")
(+ 1 1)))) ;; => 2
Functions with Unevaluated parameters are useful when we want to expand the language and are Pureli's alternative for simple macros.
It is possible to define multiple modules per file. In order to run a file, a 'main' module must be present.
Syntax: (module <name> ?(<exported definitions>))
(<exported definitions>) will only export definitions listed. Optional.It is possible to import a source file using the require keyword at the top of the module. Cyclic imports are currently not allowed.
Syntax: (require <filepath> <module name> ?(<imported definitions>) <?new name>)
Or when required module is in the same file: (module <name>)
(<imported definitions>) will only imported definitions listed. Optional.<new name> will give the module a new name. Optional./.I/O handling in Pureli might be a bit different from what you are used to. Since most functions in pureli are pure, the cannot have side-effects. This means that you need to separate pure computations from impure computations in your code, this is how you do it:
main in the module main, starts as impuredo!
pure
let! is used to bind a result from impure context to a nameExample:
(module main)
(define main
(do!
(println! "calculating the sum of 1, 2 and 3:") ;; println! is impure
(let! result (pure (+ 1 2 3))) ;; pure lifts (+ 1 2 3) into impure context and let! binds it to result
(println! result))) ;; println! prints the result
This separation is useful for a number of things:
+, -, *, /, mod)zero?, empty?, nil?, number?, integer?, real?, list?. string?, procedure?, symbol?, keyword?)=, <>, >, <, >=, <=)list, car, cdr)++, slice, length)str->lines, str->words, lines->str, words->str, to-upper, to-lower)round operation on realsshow expressionif expressionlet and letrec
quote, eval and read-str
error, try and trace
lambda expressiondo! sequence IO actionslet! binds an IO result to a variableread! reads a line from the standard inputread-file! reads a fileprint! writes to the standard output without newlineprintln! writes to the standard output with newlineprint-file! writes a string to a filepure raises a pure computation into IO contextstdlistboolnumbersdictUse (require "stdlib/std.pli" <module-name>) to import modules from the standard library.
Code examples for Pureli can be found here.