clojure “lisp reloaded”. 2 versions of lisp lisp is an old language with many variants lisp is...
TRANSCRIPT
Clojure
“Lisp Reloaded”
2
Versions of LISP
Lisp is an old language with many variants LISP is an acronym for List Processing language
Lisp is alive and well today Most modern versions are based on Common Lisp Scheme is one of the major variants Racket is the latest version of Scheme Clojure is the latest in a long line of dialects
Clojure uses a new approach to concurrency, Software Transactional Memory (STM)
3
Lisp syntax vs. Clojure syntax
4
Basic data types I Numbers: Clojure has integers, floating point numbers, and ratios
Integers include decimal integers (255), octals numbers (012), hexadecimal numbers (0xff) and radix numbers (2r1111), where the radix is an integer between 2 and 36, inclusive
Floating point numbers include standard notation (3.1416) and scientific notation(1.35e-12).
Ratios are “fractions” such as 1/3; they are not subject to roundoff error Strings are enclosed in double quotes, and can contain escaped characters
Within a string, \n represents a newline, as usual Character literals are written as \c to indicate the character c
Since \n represents the character n, \newline is used to represent a newline Keywords are not reserved words, but are like “atoms” in Prolog or Erlang; a
keyword begins with a colon (:foo) and stands for itself
5
Basic data types II A list is a sequence of values enclosed in parentheses, for example,
(one 2 "buckle my shoe") Elements of the list are separated by whitespace or commas A list represents either data or a function call, depending on context
A vector is a sequence of values enclosed in brackets, for example, [one 2 "buckle my shoe”]
Elements of the vector are separated by whitespace or commas A map or hash is a sequence of key/value pairs, enclosed in braces, for
example,{:ace 1, :deuce 2, "trey" 3}
Elements are separated by whitespace or commas It is helpful to use commas between key/value pairs
A set is a pound sign (#) followed sequence of values enclosed in braces, for example #{a b c}
6
Functions The syntax to define a named function is:
(defn function_name docstring? [arguments] expressions) The docstring after the function name is optional The value of the function is the value of the last expression evaluated
The syntax of a function call is(function_name arguments)
Notice that the name of the function being called is the first thing inside the parentheses
The syntax of an anonymous function, or lambda, is (fn [arguments] expressions)
This syntax can be used in place of a function name Example: ((fn [lst] (first lst)) my-list) is equivalent to
(first my-list) Alternative syntax: #(expressions), where %1 (or just %), %2, %3, … stand for
parameters Since functions are first-class values, this syntax is convenient for creating a
function to be used as an argument to another function
7
Sequences Lists, vectors, maps, and sets are all “sequences,” and many of the same
operations apply
(first seq) returns the first element of seq If the seq is empty, the empty list, (), is returned
(rest seq) returns the seq without its first element If the seq is empty, the empty list, (), is returned
(cons value seq) returns a sequence of the same type with the value inserted as the new first element
Some sequences may be lazy; the elements of the sequence are not actually computed until they are used
This allows Clojure to have infinite sequences, for example, “all positive integers”
Fast and slow operations Sequence is an interface roughly analogous to Java’s Iterator:
It is a small interface (a couple of methods) that can be applied to a large range of data types Many (most?) of the common Clojure operations are defined on sequences For example, map and reduce work across almost all data types.
Clojure.org describes sequences as “logical lists” The primary sequence operations are first, rest and cons Everything else is built around these operations.
The set of cheap (constant time) operations are the same as for lists: Add/remove/access the first element or step to the next element
Operations like nth and last are expensive (O(n) time) since they must traverse the entire list.
count -- getting the length of a sequence -- is also expensive.
8
Sequence operations
Sequence operations can be used on almost any compound type Collections: Vector, list, map, set, struct Strings (as char sequence) Java arrays, collections, sets, maps, iterator, enumerable IO streams Producer functions: Many lazy seqs do not get their values from a backing data set,
but from a function that calculates the value when asked for.
Some collections can be used directly as sequences, others need to be converted
The rules are somewhat different for different kinds of collections, so the simplest general strategy is to
convert to sequence, using seq do the processing if you need a particular non-sequence type (like vector or array), convert the return value
back
9
10
Predicates
A predicate (in any computer language) is a function that returns either “true” or “false”
In Clojure, “false” is one of the atoms false or nil “true” is the atom true In addition, anything that isn’t false or nil is considered to be
true In particular, the empty list, (), is “true”
Hence, a predicate returns either false, nil, or any other value to mean “true”
Predicates often return “true” values other than true, especially if the returned value might be useful
11
Function calls and data
A function call is written as a list The first element is the name of the function Remaining elements are the arguments
Example: (println "Hello" "World") Calls function println with arguments "Hello" and "World" The function prints Hello World and returns nil
Data is written as atoms or lists Example: (println "Hello" "World") is a list of three
elements
12
Quoting
Is (f a b) a call to f, or is it just data? Lists and atoms must be quoted
Exceptions include nil, true, and nil, which do not need to be quoted x indicates the variable x, while (quote x) is just the atom x (quote (f a b)) is the list (f a b)
quote is not a function, but a special form The special form gets the unevaluated arguments, and is control of when or
whether to evaluate them Special forms, unlike functions, are not first-class values
'(f a b) is a shorthand way of doing the same thing There is just one single quote at the beginning It quotes one expression
13
Examples (defn my-first [x]
(first x)) user=> (my-first '(1 2 3))
1
defn my-second [lst] (first (rest lst)))
user=> (my-second '(a b c))b
(defn my-third "This is a doc comment" [lst] (first (rest (rest lst))) )
user=> (my-third "Whatever")\a
user=> (doc my-third)-------------------------user/my-third([lst]) This is a doc commentnil
14
Arithmetic + Returns the sum of its arguments; (+) returns 0 - Subtracts the rest of the numbers from the first number * Returns the product of its arguments;(*) returns 1. / Divides the rest of the numbers into the first number
If operands are integers, result is a ratio If only one number, returns its inverse
quot Returns the quot[ient] of integer division of the first number by the rest of the numbers
rem remainder of dividing the first number by the second mod Modulus of first and second number; truncates toward negative infinity inc Returns a number one greater than its argument dec Returns a number one less than its argument max Returns the largest of its arguments min Returns the smallest of its arguments
15
Numeric comparisons = Returns true if all arguments are equal
This is a true equality test, not an identity test Collections are compared in a type-independent manner; for example,
(= '(1 2 3) [1 2 3]) returns true == Returns true if numeric arguments all have the same value, otherwise
false not= Same as (not (= obj1 obj2)) < Returns true if numeric arguments are in monotonically increasing
order > Returns true if numeric arguments are in monotonically decreasing
order, otherwise false <= Returns true if numeric arguments are in monotonically non-
decreasing order, otherwise false >= Returns true if numeric arguments are in monotonically non-
increasing order, otherwise false
16
Conditional execution
if is a special form that takes exactly three arguments: A predicate An expression to evaluate if the predicate is true An expression to evaluate if the predicate is false
Syntax: (if predicate true-branch false-branch) Example:
(defn sum [lst] (if (= lst ()) 0 (+ (first lst) (sum (rest lst))) ) )
(defn average [lst] (/ (sum lst) (count lst)) )
17
The problem with if
if expressions nest rather awkwardly (defn collatz [n] (println n) (if (= n 1) 1 (if (= (rem n 2) 0) (collatz (/ n 2)) (collatz (inc (* 3 n))) ) ) )
In most cases, the more general cond is preferred
18
cond
cond implements the if...then...elseif...then...elseif...then... control structure
Like if, cond is a special form, not a function That is, the arguments to cond are not evaluated before cond
is called; rather, cond evaluates the arguments as it pleases
19
Syntax of the cond
The syntax of the cond special form is:(cond condition result condition result ...)
Example: (defn pos-neg-or-zero
"Determines whether n is positive, negative, or zero" [n] (cond (< n 0) "negative" (> n 0) "positive" :else "zero" ) )
The last condition, :else, is true, and reads better than the atom true
20
Rules for Recursion
Handle the base (“simplest”) cases first Recur only with a “simpler” case
“Simpler” = more like the base case Don’t alter global variables Don’t look down into the recursion
21
Example: member
As an example we define member, to test membership in a list (defn member [a lat] (cond (empty? lat) false (= a (first lat)) true :else (member a (rest lat)) ) )
user=> (member :b '(:a :b :c))true
22
Guidelines for recursive Clojure functions
Unless the function is trivial, use a cond Handle the base case(s) first
Avoid having more than one base case The base case is often testing for an empty list
Do something with the first and recur with the rest Use tail recursion wherever possible
(To be explained later)
23
Example: union (defn union [set1 set2]
(cond (empty? set1) set2 (member (first set1) set2) (union (rest set1) set2) :else (cons (first set1) (union (rest set1) set2)) ) )
This example uses the previously defined member function
It appears that, if function A calls function B, Clojure requires function B to be defined before defining function A
Of all the languages I have used, only C and its variants make this requirement
Unless I’m missing something, this is a real step backward for Lisp dialects
24
Tests The following can be used with any type of argument
nil? Returns true if its argument is nil, false otherwise. identical? Tests if its two arguments are the same object
The following will throw an exception if given a non-numeric argument zero? Returns true if its argument is zero, else false pos? Returns true if its argument is greater than zero neg? Returns true if its argument is less than zero even? Returns true if its argument is even odd? Returns true if its argument is odd
25
Type tests
coll? Returns true if x implements IPersistentCollection seq? Return true if x implements ISeq vector? Return true if x implements IPersistentVector list? Returns true if x implements IPersistentList map? Return true if x implements IPersistentMap set? Returns true if x implements IPersistentSet
26
Content Tests
(contains? map key) Returns true if key is present in the map
(distinct? args) Returns true if no two of the arguments are =
(empty? collection) Returns true if collection has no items - same as (not (seq coll)) Clojure programmers use the idiom (seq x) rather than (not (empty? x))
(every? predicate collection) Returns true if (predicate x) is true for every x in collection
(not-every? predicate collection) Returns false if (predicate x) is true for every x in collection
(not-any? predicate collection) Returns false if (predicate x) is true for any x in collection
(some predicate collection) Returns the first value in the collection for which the predicate is true
27
I/O *in* A java.io.Reader object representing standard input *out* A java.io.Writer object representing standard output *err* A java.io.Writer object representing standard error print, pr Prints the object(s) to the *out* output stream, separated by
spaces printf Prints formatted output, according to a format println Same as print followed by newline prn Same as pr followed by newline. Observes *flush-on-
newline* newline Writes a newline to the output stream *out* read-line Reads the next line from stream *in* slurp Reads the file into a string and returns it spit Opposite of slurp. Opens file with writer, writes content, then
closes it
Dealing with side effects
Functions compute values; but printing is a side effect The value returned by println is nil—not too useful You can put multiple expressions in a defn
The result of calling the function is the result of evaluating the last expression (defn five [] (println "Just five") 5 )(five)
Just five5
You can also use do; the result is the value of the last expression (do (println "one" "two") (+ 1 2) )
one two3
28
29
REPL commands (load-file filename) loads a file containing Clojure functions *1 bound in a repl thread to the most recent value printed *2 bound in a repl thread to the second most recent value printed *3 bound in a repl thread to the third most recent value printed *e bound in a repl thread to the most recent exception caught by the repl *print-dup* When set to logical true, objects will be printed in a way that
preserves their type when read in later *print-length* controls how many items of each collection the print *print-level* controls how many levels deep the printer will print *print-meta* If set to logical true, when printing an object, its
metadata will also be printed in a form that can be read back by the reader *print-readably* When set to logical false, strings and characters
will be printed with non-alphanumeric characters converted to the appropriate escape sequences
30
Dealing with the REPL
The last time I used Clojure’s REPL, it seemed to be very buggy I got a lot of errors along these lines:java.lang.Exception: Unable to resolve symbol: union in this context (NO_SOURCE_FILE:43)
(It’s a Java exception because Clojure is compiled to the JVM) Here are two workarounds that seem to solve most such problems:
Start a new Clojure shell, or Just copy the function definition and paste it directly into the Clojure shell
That was a year ago; the new version may be better You might also try out clooj, which is a very simple Clojure IDE
https://github.com/arthuredelstein/clooj
31
The End