The Advantages Of Common Lisp

Lisp is often touted as a language, having advantages over the others due to the fact that it has some unique, well-integrated and useful features.

Then follows an attempt to identify a set of features of standard Common Lisp, and briefly with examples.

This article will probably be most useful for those who have some experience in programming, interested in Lisp, and wants to better understand what makes him so attractive.

The text is by and large based on the list of CL features and CL review Robert Strand (Robert Strandh).

Rich and accurate arithmetic


Lisp provides a rich hierarchy of numeric types, which are well integrated with other parts of the language.br>
Long numbers (bignums) are created automatically as needed, reducing the risk of overflows and ensures accuracy. For example, we can quickly calculate the value 10↑↑4:

the
> (expt (expt (expt (expt 10 10) 10) 10) 10)
100000000000000000000000000000000000[...]

Rational number represented as fractions so that no rounding errors when using them is not happening. Exact rational arithmetic is language-integrated

the
> (+ 3/4 5/9)
47/36

Complex number are also built-in data type in Lisp. They can be presented in a concise syntax: #c(10 5) means 10 + 5i. Arithmetic operations can also be used with complex values:

the
> (* 2 (+ #c(10 5) 4))
#C(28 10)


General links


Forms or position (places) can be used as if they were separate mutable variables. Using SETF and other similar designs you can change the values that are conceptually associated with a given position.

For example, you can use SETF as follows:

the
> (defvar *colours* (list 'red 'green 'blue))
*COLOURS*
> (setf (first *colours*) 'yellow)
YELLOW
> *colours*
(YELLOW BLUE GREEN)

And PUSH:

the
> (push 'red (rest *colours*))
(RED BLUE GREEN)
> *colours*
(RED YELLOW BLUE GREEN)

Generalized links work not only in lists, but also to many other types of structures and objects. For example, in object-oriented programs one of the ways to change a field of the object using SETF.

Multiple values


Values may be combined without explicit creation of structure, such as a list. For example, (values 'foo 'bar) returns two values — 'foo and 'bar. Using this mechanism, functions can return multiple values, which can simplify the program.

For example, FLOOR is a standard function that returns two values:

the
> (floor pi)
3
0.14159265358979312d0

Under the agreement functions that return multiple values, default is used as if returning only one value.
the
> (+ (floor pi) 2)
5

In this case, you can explicitly get and use other values. In the following example, we share the whole and fractional part of PI, when rounded:

the
> (multiple-value-bind (fractional integral)
(floor pi)
(+ fractional integral))
3.141592653589793d0


Macros


A macro in Lisp is a sort function that receives as arguments, Lisp forms, or objects and typically generates code that is then compiled and executed. This happens before execution of the program, during the phase called scan macro (macroexpansion). Macros can perform some calculations during the scan, using the full capabilities of the language.br>
One use of macros is to convert any source code to view, correct in terms of already existing definitions. In other words, macros allow you to add new syntax to the language (this approach is known as syntactic abstraction).
This makes it easy to build in Lisp domain-specific languages (DSL) as a special syntax can be added to the language before executing the program.

The main benefit of using macros is that they extend the language by allowing the programmer to Express their ideas easier and with less code. You can add new language features as if they are built-in. Besides, if the macros to use for pre-calculating data or initialization, they can help optimize performance.

LOOP Macro


The LOOP macro is a powerful tool for representing cycles. It really is a small embedded language for describing iterative processes. LOOP provides all the necessary types of expressions for write cycles, from simple repetition to complex iterators and state machines.

the
> (defvar *list*
(loop :for x := (random 1000)
:repeat 5
:collect x))
*LIST*
> *list*
(324 794 102 579 55)

the
> (loop :for elt :in *list*
:when (oddp elt)
:maximizing elt)
579

the
> (loop :for elt :in *list*
:collect (elt log) :logs into
:finally
(return
(loop :for l in logs
:if (> l 5.0) :collect l :into ms
:else :collect l :into ns
:finally (return (values ms ns)))))
(5.7807436 6.6770835 6.3613024)
(4.624973 4.0073333)


FORMAT Function


The FORMAT function supports built-in language for describing how data should be formatted. In addition to simple text substitution instructions FORMAT-or in a compact form to Express different rules of text generation, such as conditions, loops, and handling edge cases.

We can format a list of names using the following function:

the
(defun format-names (list)
(format nil "~{~:(~a~)~#[.~; and ~:;, ~]~}" list))

the
 > (format-names '(doc grumpy happy sleepy bashful
sneezy dopey))
"Doc, Grumpy, Happy, Sleepy, Bashful, Sneezy and Dopey."
> (format-names '(fry laurie))
"Fry and Laurie."
> (format-names '(bluebeard))
"Bluebeard."

FORMAT transfers the result to the specified stream, whether the standard output to the screen, string or any other stream.

higher-order Functions


Functions in Lisp are the real essential of the first class. Function objects can be dynamically created, passed as parameters or returned as results. Thus, the functions higher order, that is, the arguments and return values which themselves can be functions.

Here you can see the call to the SORT function, arguments are a list and another function (in this case #'<):

the
> (sort (list 2 4 3 1) #'<)
(1 2 3 4)

Anonymous function, also known as lambda expressions can be used instead of the name of the transferred function. They are especially useful when you want to create a function for one time use, not clogging up the program with unnecessary name. In the General case they can be used to create lexical closures.

In this example, we create an anonymous function to use it as the first argument to MAPCAR:

the
> (mapcar (lambda (x) (+ x 10))
'(1 2 3 4 5))
(11 12 13 14 15)

When creating functions capture the context that allows us to use a full lexical closures:

the
(let ((counter 10))
(defun add-counter (x)
(prog1
(+ counter x)
(incf counter))))

the
> (mapcar #'add-counter '(1 1 1 1))
(11 12 13 14)
> (add-counter 50)
64


list Processing


Because lists are a fundamental built-in data type in Lisp, there is an extensive set of functions to manipulate lists. Thanks to these functions and macros, lists can be used for fast prototyping of other data structures.

For example, we can just work with the usual list:

the
> (defvar *nums* (list 0 1 2 3 4 5 6 7 8 9 10 11 12))
*NUMS*
> (list (fourth *nums*) (nth 8 *nums*))
(3 8)
> (list (last *nums*) (butlast *nums*))
((12) (0 1 2 3 4 5 6 7 8 9 10 11))
> (remove-if-not #'evenp *nums*)
(0 2 4 6 8 10 12)

As well, associative list
the
> (defvar *capital cities* '((NZ . Wellington)
(AU . Canberra)
(CA . Ottawa)))
*CAPITAL CITIES*
> (cdr (assoc 'CA *capital-cities*))
OTTAWA
> (mapcar #'car *capital cities*)
(NZ AU CA)


Lambda-lists


A lambda list specifies the parameters of the functions, macros, forms of linking and some other structures. Lambda lists define required, optional, named, the tail (rest) and optional parameters and default values, and the like. This allows you to define very flexible and expressive interfaces.

Optional parameters do not require caller to specify any value. There can be identified a default value, otherwise, the called code can check whether the supplied value and act according to the situation.

The following function takes an optional delimiter parameter, the default value of which is a whitespace character:

the
(defun explode (string &optional (delimiter #\Space))
(let ((pos (position delimiter string)))
(if (null pos)
(list string)
(cons (subseq string 0 pos)
(explode (subseq string (1+ pos))
delimiter)))))

When you call the function EXPLODE we can either provide the optional parameter, or you can omit it.

the
> (explode "foo, bar, baz" #\,)
("foo "" bar ""baz")

the
> (explode "foo, bar, baz")
("foo" "bar" "baz")

Named parameters is similar to optional parameters, but they can be passed in any order as they are defined by names. Using names improves the readability of the code and serves as a kind of documentation when you make a call with several parameters.

For example, compare these two function calls:

the
// In C:
xf86InitValuatorAxisStruct(device, 0, 0, -1, 1, 0, 1);

the
;; In Lisp:
(xf86-init-valuator-axis-struct :dev device :ax-num 0
:min-val 0 max-val -1
:min-0 res :max 1 res
:resolution 1)


Characters as entities of the first class


Symbols are unique objects, entirely defined by their names. For example, 'foo is a symbol whose name is "FOO". Characters can be used as identifiers or as a kind of abstract names. Comparison of symbols occurs for a fixed time.

Symbols, like functions, are first class entities. They can be dynamically created, the quota (quote, unevaluate), stored, passed as arguments to compare, convert to string, export and import, you can refer to them.

Here '*foo* is the identifier of the variable:

the
> (defvar *foo* 5)
*FOO*
> (symbol-value '*foo*)
5


Packages as entities of the first class


Packages that play the role of namespaces (namespaces) are also first class objects. Because they can create, store, return as result during the program execution, it is possible to dynamically switch the context, or convert the namespace dynamically.

In the following example, we use INTERN to include a symbol in some package:

the
> (intern "ARBITRARY"
(make-package :foo :use '(:cl)))
FOO::ARBITRARY
NIL

In Lisp has a special variable *package*, which points to the current package. For example, if the current package is FOO, you can do:

the
> (in-package :foo)
#<PACKAGE "FOO">
> (package-name *package*)
"FOO"


Special variables


Lisp supports dynamic context variables in addition to the lexical context. Dynamic variables in some cases may be useful, so that their support allows us to achieve maximum flexibility.

For example, we can redirect the output of some code in a non-standard stream, such as a file, by creating a dynamic link to the special variable *standard-output*:

the
(with-open-file (file-stream #p"somefile"
:direction :output)
(let ((*standard-output* file-stream))
(print "This prints to the file, not stdout."))
(print "And this prints to stdout, not the file."))

In addition to *standard-output*, Lisp includes several special variables that store the state of your program, including resources and parameters, such as *standard-input*, *package*, *readtable*, *print-readably*, *print-circle* etc.

Transfer of control


In Lisp there are two ways to transfer control to a point above it in the hierarchy of calls. This can be considered lexical or dynamic scope for local and non-local transitions, respectively.

Named blocks allow nested form to return control from any imenovanie parent form by using BLOCK and RETURN-FROM.

For example, here the nested loop returns a list of block early in the bypass outer loop:

the
> (block early
(loop :repeat 5 :do
(loop :for x :from 1 :to 10 :collect x :into xs
:finally (return-from early xs))))
(1 2 3 4 5 6 7 8 9 10)

Catch/throw is something like non-local goto. THROW produces the transition to the last encountered CATCH, and transmits the value that was specified as parameter.

In function THROW RANGE based on the previous example, we can use THROW and CATCH using the dynamic program state.

the
(defun throw-range (a b)
(loop :for x :from a :to b :collect x :into xs
:finally (throw :early xs)))

the
> (catch :early
(loop :repeat 5 :do
(throw-range 1 10)))
(1 2 3 4 5 6 7 8 9 10)

When enough to use lexical scope and catch/throw when you need to take into account the dynamic state.

restart


The system of conditions (conditions) in Lisp is a mechanism for transmitting signals between parts of the program.

One of the possible applications is to throw exceptions and handle them, about the same as is done in Java or Python. But, unlike other languages, during signal transmission in the Lisp stack is not set, so all the data is saved and the signal handler may restart the program from any point in the stack.

This approach to handling exceptional situations allows to improve the separation of tasks and thus achieve more structured code. But this mechanism has a broader scope, as the transmission system proizvolnykh messages (not just errors) between parts of the program.

Example of the system conditions can be seen in the article Common Lisp: A Tutorial on Conditions and Restarts.

Generalized functions


The object system of Common Lisp (Common Lisp Object System, CLOS) does not bind methods to classes, and allows the use of generalized functions.

Generic functions define the signatures, which can be satisfied by several different methods. When you call the select method that best matches the arguments.

Here we define a generalized function that processes keyboard events:
the
(defgeneric key-input (key-name))

Next we define several methods that satisfy different values of KEY-NAME.

the
(defmethod key-input (key-name)
;; Default case
(format nil "No keybinding for ~a" key-name))

(defmethod key-input ((key-name (eql :escape)))
(format nil "Escape key pressed"))

(defmethod key-input ((key-name (eql :space)))
(format nil "Space key pressed"))

Look at the calling methods in action:

the
> (key-input :space)
"Space key pressed"
> (key-input :return)
"No keybinding for RETURN"
> (defmethod key-input ((key-name (eql :return)))
(format nil "Return key pressed"))
> (key-input :return)
"Return key pressed"

We without designs a La the switch and clear the table methods. Thus, we can add new treatment of individual cases independently, dynamically, as needed and in General at any point in the program. This, in particular, ensures the development of programs in Lisp, bottom-up.

Generic functions define some General characteristics of the methods. For example, methods of combination methods of selecting specialization, and other properties can be specified aggregate function.

Lisp provides many useful standard generalized functions; an example is the PRINT-OBJECT, which can be specialized for any class, to set its text representation.

Combinations


Combinations of methods allow you to call any method to do a chain methods, either in some order or so that some functions have processed the results of others.
There are built in methods the combination of methods building methods in the specified order. Methods, with key words :before, :after, or :around are placed in the appropriate place in the call chain.

For example, in the previous example, each KEY-INPUT output repeats the phrase "key pressed". We can improve the code by using the combination of the type :around

the
(defmethod key-input :around (key-name)
(format nil "~:(~a~) key pressed"
(call-next-method key-name)))

After that we will redefine methods of KEY-INPUT, each of them putting only one line:

the
(defmethod key-input ((key-name (eql :escape)))
"escape")

When the call KEY IS INPUT, the following occurs:
the
    the
  • method is called with a label :around
  • the
  • it calls the following method, that is one of the specialized versions of the KEY-INPUT,
  • the
  • which returns a string, and this string format method with :around.

It should be noted that the default can be treated in different ways. We can just use a couple THROW/CATCH (more advanced implementation could use conditions):

the
(defmethod key-input (key-name)
(throw :default
(format nil "No keybinding for ~a" key-name)))

(defmethod key-input :around (key-name)
(catch :default
(format nil "~:(~a~) key pressed"
(call-next-method key-name))))

As a result, the built-in method combination of methods allows us to generalize event handling from the keypad in a modular, extensible, easy adjustable mechanism. This technique can be augmented with user-defined modes of combination; for example, it is possible to add method combination, which will carry out the summation or result sorting methods.

Multiple inheritance


Any class can have a lot of ancestors that allows you to create richer models and to achieve more effective code reuse. The behavior of the child classes is determined in accordance with the order of which is based on the class definitions of the ancestors.

By means of combinations of methods, the meta Protocol, and other features of CLOS, you can circumvent the traditional problems of multiple inheritance (such as fork-join).

Meta Protocol


Meta Protocol (Meta-object protocol, MOP) is a software interface to CLOS, which is itself implemented with the CLOS. MOP gives programmers the ability to explore, use, and modify the internal structure of a CLOS CLOS itself.

Classes as entities of the first class


Classes themselves are also objects. With MOP you can modify the definition and behavior classes.

Let the class FOO is a child class BAR, then we can use the function ENSURE-CLASS to add, say, the class BAZ to the list of the ancestors of FOO:

the
(defclass bar () ())
(defclass foo (bar) ())
(defclass baz () ())

the
> (class-direct-superclasses (find-class 'foo))
(#<STANDARD-CLASS BAR>)
> (ensure-class 'foo :direct-superclasses '(bar baz))
#<STANDARD-CLASS FOO>
> (class-direct-superclasses (find-class 'foo))
(#<STANDARD-CLASS BAR > # < STANDARD-CLASS BAZ>)

We used the function CLASS-DIRECT-SUPERCLASSES to obtain information about the ancestors of a class; in this case, it takes as argument a class object obtained by FIND-CLASS.

The following code illustrates the mechanism by which classes can be modified during program execution, which allows, among other things, to dynamically add classes impurity (mixins).

Dynamic override


Lisp is a very interactive and dynamic environment. Functions, macros, classes, packages, parameters, and objects can be overridden at almost any time, and thus the result will be adequate and predictable.

So, if you have overridden the class during program execution, the changes immediately apply to all objects and subclasses of a given class. We can define the BALL class with property radius and a subclass of the TENNIS-BALL

the
> (defclass ball ()
((%radius :initform 10 :accessor radius)))
#<STANDARD-CLASS BALL>
> (defclass tennis-ball (ball) ())
#<STANDARD-CLASS TENNIS-the BALL>


the
> (defvar *my-ball* (make-instance 'tennis-ball))
*MY-BALL*
> (radius *my-ball*)
10

Now we can override the class BALL, adding another slot volume:

the
> (defclass ball ()
((%radius :initform 10 :accessor radius)
(%volume :initform (* 4/3 pi 1e3)
:accessor volume)))
#<STANDARD-CLASS BALL>

And *MY-BALL* updated automatically, getting a new slot that was defined in a class-ancestor.

the
> (volume *my-ball*)
4188.790204786391d0


access to the compiler during program execution


Thanks to the functions COMPILE and COMPILE-FILE compiler Lisp you can use directly from your executing program. Thus, functions that are created or changed during the operation of the program also compiled.

Goes, programs can be compiled in phases, making the development of interactive, dynamic and fast. The programs can change, debug and grow gradually.

Macros compile


The macros compile to define an alternative strategy to compile a function or macro. Unlike normal macros, the macro compilation does not extend the language syntax and can only be applied at compile time. Therefore they are mainly used in order to determine the ways to optimize your code separately from the code itself.

typedefs


Though Lisp is dynamically typed language — which is quite handy during rapid prototyping — the programmer can explicitly specify types of variables. This and other directives allow the compiler to optimize the code as if the language was statically typed.

For example, we can define the types of parameters in our function EXPLODE, like so:

the
(defun explode (string &optional (delimiter #\Space))
(declare (type character delimiter)
(type string string))
...)


Programmable parser


The parser of Lisp makes it easy to parse the input. He gets the text from the input stream and creates a Lisp object, usually called S-expressions. This greatly simplifies parsing the input.

The parser can be used through multiple functions, such as READ, READ-CHAR, READ-LINE, READ-FROM-STRING, etc. Input stream can be a file, keyboard input and so on, but, in addition, we can read from strings or sequences of symbols using the corresponding functions.

Here is a simple example of read with READ-FROM-STRING, which creates an object (400 500 600), that is the list from the string "(400 500 600)".

the
> (read-from-string "(400 500 600)")
(400 500 600)
13
> (type-of (read-from-string "t"))
BOOLEAN

Macros read (reader macros) allow you to define special semantics for the given syntax. This is possible because the parser, Lisp is a programmable. Macros reading is another way to extend the syntax of the language (they are usually used to add syntactic sugar).

Some standard macros are read:
the
    the
  • #'foo — function
  • the
  • #\\ — symbols (characters),
  • the
  • #c(4 3) — complex number
  • the
  • #p"/path/" — the path to the file.

The parser can generate any object for which you define the rules of reading; in particular, these rules can be specified using the macros read. In fact, the parser in question is used for interactive interpreters (read-eval-print loop, REPL).

So we can read the number in hexadecimal of the recording by using the standard macro read:

the
> (read-from-string "#xBB")
187


Programmable print


System text output in Lisp provides the ability to print structures, objects or any other data in different forms.

PRINT-OBJECT is a built — in aggregate function that takes as arguments an object and a stream, and the method outputs to the stream a textual representation of this object. In any case, when you need the text representation of an object, you use this function, including FORMAT, PRINT in the REPL.
Consider a class JOURNEY:

the
(defclass journey ()
((%from :initarg :from :accessor from)
(%to :initarg :to :accessor to)
(%period :initarg :period :accessor period)
(%mode :initarg :mode :accessor mode)))

If we try to print an object of class JOURNEY, we will see something like:

the
> (defvar *journey*
(make-instance 'journey
from "Christchurch" to "Dunedin"
:period 20 :mode "bicycle"))
*JOURNEY*
> (format nil "~a" *journey*)
"#<JOURNEY {10044DCCA1}>"

You can define method PRINT-OBJECT for a class JOURNEY, and use it to specify some kind of text representation of an object:

the
(defmethod print-object ((journey j) (s stream))
(format s "~A to ~A (~A hours) by ~A."
(from j) (to j) (period j) (mode j)))

Our object will now use the new text representation:

the
> (format nil "~a" *journey*)
"Christchurch to Dunedin (20 hours) by bicycle."
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Why I left Google Zurich

2000 3000 icons ready — become a sponsor! (the table of orders)

New web-interface for statistics and listen to the calls for IP PBX Asterisk