Functional programming the LISP way
Lately, there’s been a lot of talk about functional programming. The newer generation languages advertise themselves as multi-paradigm ( as opposed to java’s object oriented style). Java is doing catching up by bringing in Project Lambda in JDK 8, and Predicates has already started becoming popular (here’s a good starter by Arun Vydianathan).
This post is an introduction to a very potent and heady LISP cocktail of Higher Order Functions, Lamdas and Macros.
Higher Order Functions: Functions that take other function as its argument. A function that filters a list can be converted into higher order function by providing the actual filtering function as an argument.
Lambda Expression: Dynamically creates functions and attaches a context to them. The filtering function can be created at runtime as a lambda and its bounds set at runtime.
Macro: Creates code at runtime by substituting the macro with corresponding expression. While Lambdas let you replace the bound conditions, macros let you decide the structure of your filter function.
Higher Order Functions
You could print all students belonging to “ELX” department from a list like this:
(defun print-students (students-lst) (loop for student in students-lst do ( if (equal "ELX" (getf student :department)) (print student))))
Now if you want to print the students who had received ‘A’ grade, you are at a loss. You cannot do that without writing a new function by duplicating the looping logic.
Enter Higher Order Functions. Higher Order Functions are functions which take another function as its argument. The example above can be modified into a generic higher order function if we could abstract the evaluation logic into another function.
Consider the following re-write
(defun print-students (students-lst, eval-student) (loop for student in students-lst do ( if (funcall eval-student student) (print student)))) (defun eval-elx-department (student) ( if (equal "ELX" (getf student :department)) t)) (defun eval-a-grade (student) ( if (equal "A" (getf student :grade)) t)) (print-students *students-lst* #'eval-a-grade)
Note: LISP built-in functions REMOVE-IF and REMOVE-IF-NOT takes a list and a evaluation function, applies the evaluation function to every element in the list and returns a new list which contains/doesn’t contain the element. The idea of predicates is very similar to this.
If we could parameterize the grade from the eval-a-grade function, we would have a generic eval-grade and that would have been just great!
Unfortunately, the print-students function expects an evaluation function that takes in a single argument: student.
Using Lambda expressions, it is possible to custom make a function with a context. Lambda expressions are used to create anonymous functions with a scope attached to them. The variables used within the lambda expressions are evaluated in the context they are run in.
(defun eval-grade (grade) (lambda (student) ( if (equal grade (getf student :grade)) t))) (print-students *students-lst* (eval-grade "A")) (print-students *students-lst* (eval-grade "B"))
LISP macro is a very powerful beast that can be used to generate code dynamically. Instead of having multiple evaluation functions, it is possible to write a LISP macro to dynamically generate a lisp function based on the inputs you pass to it.
(defmacro eval-student (&key dept grade) `(lambda(student) (and ,(if dept `(equal ,dept (getf student :department))) ,(if grade `(equal ,grade (getf student :age)))))) (print-students *students-lst* (eval-student :dept "ELX" :grade "A"))
The eval-student macro expands to
LAMBDA (STUDENT) (AND (EQUAL "ELX" (GETF PERSON :DEPT)) (EQUAL "A" (GETF PERSON :GRADE))))
Note: Though very powerful, it is easy to cut yourself using macros. Use discretion when using them.
Functional programming is a great way to modularize and reduce the size of your code. More than that, it is fun! Happy coding.