230 likes | 250 Views
Discover the evolution of functional languages like Lisp, their symbolic computing roots, symbolic forms, and expressions in AI research. Learn about recursion, functional forms, data structures, and Lisp evaluation process.
E N D
Functional Languages • Early in AI research, there was a need for symbolic computing • handling symbols instead of numbers or strings • parsing input • recursion • A functional approach was taken • programs would comprise functions and function calls, utilizing recursion as much as possible • rather than a series of imperative statements with local variables, controlled through loops • The earliest functional language was not Lisp, but Lisp was the successful language to come out of this research • Some details on Lisp • Lisp began as a purely functional language although it picked up many imperative features (loops, go to statements, local vars, etc) from other languages over time • Lisp is interpreted (although compilers later became available) leading to the ability to develop programs in a piecemeal fashion • eventually, Lisp compilers for better performance • Lisp initially was dynamically scoped but later Lisp was statically scoped • Lisp uses only implicit heap dynamic variables
Functional Forms • A function maps from a domain set to a range set • the range of these two sets is determined by the function • A functional form is a higher-order function, that is, a function that applies to functions • this can either be a function that accepts a function as a parameter, or a function that returns a function • these are important in Lisp as they allow functions to generate new functions (that is, code that can produce code) • Construction • a functional form in which the result of one function is applied to a second function (nested functions) • example: f(x) = x + 2, g(x) = 3 * x, h(x) = f o g = f(g(x)) = 3*(x+2) • Apply to all • a functional form in which the function is applied to a list of parameters, returning a list of values • example: (using f from above) apply(f(2, 5, 12)) = (4, 10, 14)
LISP • A typeless language – we do not declare variables, and variables are merely pointers that can point to any type of datum • In early Lisp, there were no local variables, only parameters • Data Structures are either atoms and lists • atom – a literal value (char, string, number) usually denoted as ’atom (as in ’a or ’buckeye or ’red) • list – nil or ( x ) where x is an atom, a series of atoms, or a sub list, or any combination of these • example of a list (A (B C D) E F) • In Lisp, functions are stored in lists and written in prefix notation • form of a Lisp function is: (function_name param1 param2 …) • examples: • (+ 5 7) returns 12 • (equal? a b) returns T (true) if a = b, nil (false) otherwise • (/ (+ a b) (- c d)) returns (a + b) / (c – d) • note the nested parens, this will take some getting used to! • (cube x) returns x3 • Functions can also be written as Lambda expressions • a Lambda expression is an unnamed function in which the parameter(s) is(are) specified after the function • e.g. (lambda (x) (* x x)) (5) 25
( A ( D E F ) B C ) A B C Each list node consists of a data structure (as shown to the right) consisting of two pointers, the CAR points to the item being stored in that node, the CDR points to the next node D E F The terms CAR and CDR are historical, named after registers in an early machine that ran Lisp CDR CAR Representing Lists The CDR of a node can be equal to nil (equivalent to null or NULL in Java and C) C’s & F’s CDRs are nil
Lisp and the Eval Function • Lisp’s primary function is EVAL which evaluates a list or atom at run-time • functions are special cases of lists: they are lists whose car is a function name and whose cdr is a list of parameters • eval will evaluate the function name on the parameters • that is, (evallis) does (eval (car lis) (cdrlis)) • this makes Lisp an interpreted language – the eval function is the interpreted • original Lisp was supposed to be similar to FORTRAN in its appearance, but this was not practical • lists are known as s-expressions (symbolic expressions) and can represent either data or functions • note that evaluating a list which is not a function will return an error • for instance if lis1 is (a b c d) then (eval lis1) would attempt to evaluate a on (b c d) but a is an atom, not a function! • to define a function, we use the following notation: • (function_name (LAMBDA (arg1 … argn) expression)) • where function_name is the name of the function to be called by other functions and where expression consists of lists of Lisp function calls
Example Math Functions in Lisp (quote a) or ’a means to take something literally, do not evaluate it A without the ’ would be interpreted as a variable, but if A has not already been assigned a value, we get • 42 returns 42 • ’42 returns 42 • A returns an error but ’A returns A • (* 3 7) returns 21 • (+ 5 7 8) returns 20 • (- 5 6) returns -1 • (- 15 7 2) returns 6 • (- 24 (* 4 3)) returns 12 • (> 5 6) returns nil (false) • (= 5 (+ 2 3)) returns t (true) • (* (+ 5 4) (- 8 (/ 6 2)) returns 45 an error for trying to access it We will use quote to define atoms and initialize lists – without using a quote, the list is evaluated as a function call where the first item in the list is a function name and the remainder are parameters, but this would yield an error because the first item would not be a function name but a datum (probably an atom)
Variables vs. Symbols and Lists • Variables point to data items which are either symbols (similar in ways to strings) or lists • a variable is denoted by its name (e.g., A) • An atom or list is denoted using a quote mark as in ’A or ’(A B C) • To create a symbol, use the function QUOTE as in (QUOTE A) which returns ’A or use the shorthand ’A • note that ‘ has a different meaning in Lisp to ’ as the ‘ is used in macro definitions, something we will briefly discuss later • Notice that ’(+ 3 5) does not return 8, it returns the literal list (+ 3 5) because ’ says “do not evaluate this” • we could evaluate this list by doing (eval ’(+ 3 5)) which would return 8 • so the ’ will allow us to construct a list whether the list stores data or code, and the eval function will allow us to execute a list that is code
Scheme • Lisp was a pioneering programming language in AI and functional programming, but it had many problems • the lack of variables led to the programmer having to be tricky in how to use recursion • dynamic scoping was also a problem • Scheme was one of many versions of Lisp that added functionality to the language • Scheme emerged from MIT in the mid 70’s • characterized by a small subset of Lisp features, static scoping and classes • Scheme continues to be taught today as an example of a functional language although Scheme itself has not been used much in AI (AI tends to use Common Lisp today if it uses any version of Lisp at all) • We concentrate our examination on Lisp by looking at Scheme and later Common Lisp rather than Lisp itself because Lisp is awkward and difficult
Some Functions in Scheme • CAR – content of address register (returns first item of a list) • CDR – content of decrement register (returns remainder of list) • EQ? – equal (for atoms or lists, returns T or nil) • MEMBER – membership (is a given item in this list?) • ATOM? – is the datum an atom? (T or nil) • LIST? – is the datum a list? (T or nil) • NULL? – is the datum a nil pointer or something? (T or nil) • EVEN?, ODD?, ZERO? – self explanatory (T or nil) • =, <>, <, >, <=, >= – self explanatory (T or nil) • CONS – a list constructor (we’ll go into more detail later) • LIST – an alternative list constructor (returns a list) • IF statement: (IF (condition) (then clause) (else clause) ) • returns whatever the then clause or else clause returns • COND – conditional statement used like a nested if-else structure (we’ll see more examples of this momentarily)
Assignments in Scheme • Scheme has 3 different ways of assigning values • set!, define, let • (set! a b) is essentially a = b, although in this case, it is a pointer assignment where a points to b • if b is a literal, then a points to a memory location storing the value of b, otherwise a points at the same memory location as what b points to • (set! a ’(a b c)) creates the list of a, b and c and has the variable a point at the list • (set! x (+ 6 (- 8 (* 2 1)))) computes the value (6 + (8 – (2 * 1))) or 12, stores it in memory and has x point at that memory location • (set! x ’(+ 6 5)) points x at the list (+ 6 5) but not 11 • if we do then do (eval x), we get 11 • (define a b) is used to bind the name a to the item b, this can be used to bind a constant to its value, or a function name to its code (we do not use define for variables because the value is not permitted to change once assigned) • (let ((a 0) b) –creates local variables for a block enclosed in ( ), in this case, a is assigned 0, and b has no initial value
Types of Functions • All functions return a value • the various mathematical functions return atoms or numerical values • predicate functions return T or NIL depending on the outcome of the condition • these include EQ?, ATOM?, NULL?, =, <, >, <=, >=, <>, EVEN?, ODD?, ZERO? • LIST and CONS are list constructor functions • they create and return lists • COND and IF evaluate one or more conditions to select a function to evaluate and return the value returned by that function • SET! is a function with a side effect, it redirects a pointer to point at the second parameter and then returns the value of the second parameter • note that if the second parameter is a literal value (atom or list), there is another side effect, first storage is allocated to store the literal value
Form is (COND (test1 result1) (test2 result2) …) where tests are conditionals and results are functions The first test to return true executes the associated function The IF statement has two forms (IF (condition) (function1)) – if the condition returns T, the function returns the value returned by function1 (IF (condition) (function1) (function2)) – if the condition returns T, the function returns the value returned by function1 otherwise it returns the value returned by function2 Conditional Examples (cond ((eq? x 0) (set! y 0)) ((> x 0) (set! y 1)) (t (set! y –1))) (cond ((<= hours 40) (* hours wages)) (t (+ (* 40 wages) (* (* (- hours 40) wages) 1.5)))) (if (<= hours 40) (* hours wages) (+ (* 40 wages) (* (* (- hours 40) wages) 1.5))) We might use this as (set! pay (if …))
CONS – list constructor • CONS has two parameters and returns the list constructed by taking the first parameter and making it the CAR of a list and attaching to it the second parameter as the CDR • Since the CDR must be a list, this only works if the second parameter was already a list • Examples: • (CONS ’A ’( ) ) returns (A) • (CONS ’A ’(B C)) returns (A B C) • (CONS ’(A) ’(B)) returns ((A) B) • (CONS ’(A B) ’(C D)) returns ((A B) C D) • (CONS ’A ’((B C))) returns (A (B C)) A C D A B
Defining Functions in Scheme • Form: (DEFINE (funct_name params) body) • (DEFINE (square num) (* num num)) • so we can now use square as (square x) which returns x2 • (DEFINE (second alist) (car (cdr alist))) • notice that for the parameter, I used alist, this was merely to avoid confusion since list is defined as a function • if we do (second ’(a b c d)), it returns ’b • (DEFINE (factorial n) (cond ((eq? n 0) 1) (T (* n (factorial (- n 1)))))) • (DEFINE (fib n) (if (> n 2) (+ (fib (- n 1)) (fib (- n 2))) 1)) • (DEFINE (foo alist) (cond ((null? alist) 0) (T (+ (foo (cdr alist)) 1)))) If n = 0, return 1, otherwise return n * factorial (n-1) What does this function do?
More Scheme Functions (DEFINE (factorial n) (IF (= n 0) 1 (* n (factorial (- n 1))))) (DEFINE (member atm lis) (COND ((NULL? lis) NIL) ((EQ? atm (CAR lis)) #T) (ELSE (member atm (CDR lis))))) Member will return T if the atm is found in the lis (list) and nil if it is not found (member ’d ’(a b c d e f)) returns T (member ’d ’(a b e f j k)) returns nil Member in Lisp and Common Lisp will return the list starting at atm and going to the end of the list, how can we modify Scheme’s member to do this? (DEFINE (append lis1 lis2) (COND ((NULL? lis1) lis2) (ELSE (CONS (CAR lis1) (append (CDR lis1) lis2))))) If we call (append ’(A B) ’(C D E)) we get back (A B C D E) If we call (append ’((A B) C) ’(D (E F))) we get back ((A B) C D (E F))
Two Similar Scheme Functions (DEFINE (equalsimp lis1 lis2) (COND ((NULL? lis1) (NULL? lis2)) ((NULL? lis2) NIL) ((EQ? (CAR lis1) (Car lis2)) (equalsimp (CDR lis1) (CDR lis2))) (ELSE NIL))) (DEFINE (equal lis1 lis2) (COND ((ATOM? lis1) (EQ? lis1 lis2)) ((ATOM? lis2) NIL) ((equal (CAR lis1) (Car lis2)) (equal (CDR lis1) (CDR lis2))) (ELSE NIL)))
A function that uses local variables to compute quadratic roots (DEFINE (quadratic_roots a b c) (LET ((root_part_over_2a (/ (SQRT (- (* b b) (*4 a c))) (* 2 a))) (minus_b_over_2a (/ (- 0 b) (* 2 a)))) (DISPLAY (+ minus_b_over_2a root_part_over_2a)) (NEWLINE) (DISPLAY (- minus_b_over_2a root_part_over_2a)))) More Examples Two ways to add a list of numbers: (DEFINE (adder lis) (COND ((NULL? lis) 0) (ELSE (+ (CAR lis) (adder (CDR lis)))))) (DEFINE (create-adder lis) (COND ((NULL? Lis) 0) (ELSE (EVAL (CONS ’+ lis))))) What does this second one do? Example of an apply-to-all function (DEFINE (mapcar fun lis) (COND ((NULL? Lis) ( )) (ELSE (CONS (fun (CAR lis)) (mapcar fun (CDR lis)))))) (mapcar (LAMBDA (X) (* x (+ x 1))) ’(1 5 2 9)) returns (2 30 6 90)
Common Lisp • First developed in 1984 • Incorporates features of many earlier Lisps including Scheme • Around 1988, CL was combined with CLOS (the Common Lisp Object System) to be a hybrid OO+functional language (in the same way that C++ is a combination of C and Smalltalk) • Common Lisp is similar to C++ in size and complexity • Common Lisp uses static scoping as its default but allows for dynamic scoping • Common Lisp includes • a full range of built-in data types including arrays, records, complex #’s, strings, classes • powerful I/O operations including streams and formatted I/O • imperative features including a wide variety of control structures • exception handling (several different forms)
Some Common Lisp Functions • PROG • a local block where global variables are unaffected by any statements within the block • progn is the same except that the block automatically returns the value of the last function executed in the local block • New control statements • DOTIMES – counter-controlled loop • DOLIST – iterator loop, executes loop body once for each list item • DO – general purpose loop, can be counter-controlled or logical, much like C’s for-loop • Setf for assignment • variables declared in let statements initialized to NIL until assigned a value • DEFUN – defining functions • form: (DEFUN functionname (params) body) • body is any single or group of statements, use (let ( ) body) to have local variables and (progn body) for a block (for instance as the if or else clause of an if-else statement) • GO – a goto statement (we will see this next) • Variations of CAR and CDR: CADR, CDDR, CAAR, CDAR, etc are all available
Common Lisp Functions Two member functions Iterative version: (DEFUN imember (atm lst) (PROG ( ) loop_1 (COND ((NULL lst) (return nil)) ((EQUAL atm (CAR lst)) (RETURN T))) (SETQ lst (CDR lst)) (GO loop_1))) Recursive version: (DEFUN rmember (atm lst) (COND ((NULL lst) NIL) ((EQUAL atm (CAR lst)) T) (T (rmember atm (CDR lst))))) Two length functions Iterative version: (DEFUN ilength (lst) (PROG (sum) (SETQ sum 0) again (COND ((NULL lst) (RETURN sum))) (SETQ sum (+ sum 1)) (SETQ lst (CDR lst)) (GO again))) Recursive version: (DEFUN rlength (lst) (COND ((NULL lst) 0) (T (+ 1 (rlength (CDR lst))))))
More Recursive Demonstrations • The main idea behind Lisp was to use recursion and no local variables • while both iteration and local variables (through the let function are available, we are generally discouraged from using either as seen in these examples (defun remove-first (a lis) (cond ((null lis) '( )) ((equal (car lis) a) (cdr lis)) (t (cons (car lis) (remove-first a (cdr lis)))))) (defun member-at-all (a lis) (cond ((null lis) nil) ((listp (car lis)) (or (member-at-all a (car lis)) (member-at-all a (cdr lis)))) ((equal (car lis) a) t) (t (member-at-all a (cdr lis))))) Remove the first instance of the item a from the list lis (defun all-the-same (lis) (cond ((null lis) t) ((or (atom lis) (= (length lis) 1)) t) ((equal (car lis) (cadr lis)) (all-the-same (cdr lis))) (t nil))) The member function looks for a in lis, but what if a is burried inside of further lists? member-at-all sees if a is a member at any level of lis and returns t or nil Determine if the items in lis are all the same, such as ’(a a a) versus ’(a b a)
Common Lisp Data Structures • struct – similar to C/C++ • (defstruct car year make model mileage) • (setf mycar (make-car : year 2005 : model ’camry)) • make-car is automatically generated when use defstruct to define car • defstruct also creates member accessing functions for each field as car-member • (car-year mycar) returns 2005 • (car-mileage mycar) returns nil since that member has no value yet • arrays – use make-array to generate an array • (setf anarray (make-array ’(10 5)) – creates a 10x5 array and points anarray at it • access the array using (aref arrayname arrayindex(es)) • as in (setf (aref anarray 3 2) 12) which sets anarray[3][2] = 12 • indices start at 0 as in C/Java • arrays can be resized/reshaped with the old values retained • strings – treated as arrays of chars • can be assigned using make-array or directly as in (setf name “Frank Zappa”) • accessed through aref and length • classes – added to the language in the late 80s • define a class through defclass and a method through defmethod, and an object (instance) through make-instance • common lisp classes are unlike other OOPLs because methods are not necessarily tied to classes, but can be tied to specific objects, and there is no information hiding mechanisms • like C++, common lisp classes can have multiple parents
Functional languages vs. imperative languages: variables/memory usage less visible making programming easier at least in some situations simpler syntactic structures to deal with (everything is a list) concurrency easier to design and implement interpreted nature makes large systems easier to build exploits recursion as much as possible, more so than imperative languages Uses of functional languages: mostly used in AI research Natural Language Understanding (easy parsing partially due to recursive nature) Expert Systems (easy rule format) Knowledge Representation (symbolic capabilities) Machine Learning (dynamic storage) used to teach functional programming used to implement EMACS, MACSYMA and some operating systems Conclusion