clojure “lisp on the jvm”. 2 versions of lisp lisp is an old language with many variants lisp is...

40
Clojure “Lisp on the JVM”

Upload: gilbert-ward

Post on 28-Dec-2015

265 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Clojure

“Lisp on the JVM”

Page 2: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

2

Versions of LISP

Lisp is an old language with many variants LISP is an acronym for List Processing language

Invented by John McCarthy in 1958 (or so). Most modern versions are based on Common Lisp Scheme is one of the major variants Clojure is the latest in a long line of dialects

Page 3: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Why Clojure?

As CS students, you have some experience (at least) with procedural, object oriented and logic programming.

Most of you probably have little or no experience with functional programming.

Highlights include: Immutable “variables” (so no assignment statements as such) Lots of recursion Lists (and vectors) play a huge role Functions are first-class objects Macros are amazing and I hope we have time to use them

Page 4: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Zen of Clojure

Don't try to write Java (or Ada or C or . . .) code in Clojure.

Try to speak the language the way fluent speakers do. Learn to write idiomatic Clojure programs.

Page 5: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Clojure is symbiotic

Clojure is built on top of the Java Virtual Machine (JVM). There are also versions built on the .NET framework and a

Javascript-like language called ClojureScript (and a few others), but we will ignore all of these.

Very easy to create and manipulate Java objects, but we will mostly ignore this as well, except when we can't.

Page 6: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Clojure history

Designed by Rich Hickey, mostly on his own, at least at first.

Appeared in 2007. Latest version from 2014. There have been an incredible number of Lisp dialects

over the years and this is one of the relatively few to rise above the masses.

Page 7: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Okay, what about the parentheses?

Lists are used for both programs and data. Sometimes the phrase “code is data” is used to describe this.

This allows the language to manipulate its own structure and behavior.

The term homoiconicity is used to describe this concept and (believe it or not at this point) it is quite profound. From the Greek, homo meaning same, icon meaning

representation Among other things, this supports the ability to write

macros, which are code manipulate and create code.

Page 8: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Example

(defn r->lfix

([a op b] (op a b))

([a op1 b op2 c]

. . . show real code from pg 10-12 in Joy

Page 9: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Read-Eval-Print Loop (REPL)

texttext Read Eval PrintRead Eval Print

Read Eval Printtext ExpandCompile

Page 10: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

10

Lisp syntax vs. Clojure syntax

Page 11: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

11

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

Page 12: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

12

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}

Page 13: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

13

Functions The syntax to define a named function is:

(defn function_name [arguments] expressions) 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) Since functions are first-class values, this syntax is convenient for creating

a function to be used as an argument to another function

Page 14: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

14

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

Page 15: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

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 iterate over all elements

Operations like nth and last are expensive (O(n) time) since they must traverse the entire list.

Unlike lists, count - getting the length of a sequence - is also expensive.

15

Page 16: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

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 do the processing if you need a particular non-sequence type (like vector or array), convert the return value

back

16

Page 17: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

17

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

Page 18: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

18

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 Do you see a problem here?

Page 19: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

19

Quoting

Is (f a b) a call to f, or is it just data? All literal data must be quoted (atoms, too)

Exceptions include nil, true, and nil, which do not need to be quoted

x indicates the variable x, while (quote x) is just 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

Page 20: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

20

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

Page 21: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

21

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

Page 22: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

22

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

Page 23: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

23

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)) )

Page 24: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

24

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

Page 25: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

25

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

Page 26: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

26

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

Page 27: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Recursion with “recur”

Specifically for use with tail recursion (defn print-down-from [x]

(when (pos? x)

(println x)

(recur (dec x)))) (defn sum-down-from [sum x]

(if (pos? x)

(recur (+ sum x) (dec x))

sum))

Page 28: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Loop

Sometimes you don't want to loop all the way to the beginning of the function.

(defn sum-down-from [initial-x]

(loop [sum 0, x initial-x]

(if (pos? x)

(recur (+ sum x) (dec x))

sum)))

Page 29: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

Tail recursion

Recur can only appear in the tail position of a function or loop.

A form is in a tail position of an expression when its value may be the return value of the entire expression.

(defn absolute-value [x]

(if (pos? x)

x ;; this is tail

(- x))) ;; x is not tail

Page 30: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

30

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

Page 31: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

31

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

Page 32: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

32

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

Page 33: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

33

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

Page 34: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

34

Tests nil? Returns true if x is nil, false otherwise. identical? Tests if 2 arguments are the same object zero? Returns true if num is zero, else false pos? Returns true if num is greater than neg? Returns true if num is less than zero, else false even? Returns true if n is even, throws an exception if n is not an integer odd? Returns true if n is odd, throws an exception if n is not an integer

Page 35: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

35

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

Page 36: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

36

Content Tests

contains? Returns true if key is present in the given collection, else false distinct? Returns true if no two of the arguments are = empty? Returns true if coll has no items - same as (not (seq coll))

Use the idiom (seq x) rather than (not (empty? x)) every? Returns true if (pred x) is logical true for every x in collection, else false not-every? Returns false if (pred x) is logical true for every x in collection,

else true some Returns the first logical true value of (pred x) for any x in collection not-any? Returns false if (pred x) is logical true for any x in collection,

else true

Page 37: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

37

I/O *in* A java.io.Reader object representing standard input for read operations *out* A java.io.Writer object representing standard output for print operations *err* A java.io.Writer object representing standard error for print operations print Prints the object(s) to the output stream that is the current value of *out* printf Prints formatted output, as per format println Same as print followed by (newline) pr Prints the object(s) to the output stream that is the current value of *out* prn Same as pr followed by (newline). Observes *flush-on-newline* newline Writes a newline to the output stream that is the current value of *out* read-line Reads the next line from stream that is the current value of *in* slurp Reads the file into a string and returns it spit Opposite of slurp. Opens file with writer, writes content, then closes it

Page 38: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

38

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

Page 39: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

39

Dealing with the REPL

Clojure’s REPL (Read-Eval-Print Loop) seems to be very buggy I get 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

Page 40: Clojure “Lisp on the JVM”. 2 Versions of LISP Lisp is an old language with many variants LISP is an acronym for List Processing language Invented by John

40

The End