DEV Community

Ian Johnson
Ian Johnson

Posted on

A List of Lessons from Lisp for 2018

One of my resolutions for 2017 was to learn Lisp. I had recently learned Haskell and Elixir. Lisp was appealing because it was another type of functional language.

Learning Lisp was so helpful for me for the rest of the year, that I decided I wanted to share that experience with others so that maybe they will add Lisp to their resolution list for 2018.

The value of learning Lisp is in the ideas. I don't write any Lisp code. But I use the ideas from Lisp in my code all the time. So, for the new year, I have compiled a list of lessons that I have taken from learning Lisp. I hope this inspires someone else to learn Lisp in 2018!

Lesson 1: Names Matter

Labels carry meaning. Labels are powerful. When you affix a label to something, you gain power over it. That is, you gain the power to talk about it. This is the idea of abstraction. When we create a class, we are creating an abstraction to talk about some piece of the system. But this is not where abstraction stops.

When you create a function, you are also creating an abstraction. In Java, classes are the main method for abstracting. In Lisp, the main method for abstracting is by using functions. This is one of the markers of a functional language.

Lesson 2: Functions Are Objects Too

In many programming languages, functions are first-class citizens. That means that functions are a type. They can be the input of a function and the can be the output of a function. Bonus points if your language supports anonymous functions, which many do at this point.

Lisp has functions that are first-class citizens. Lisp has the original anonymous function, called a lambda. The lambda is actually the same thing as a closure, which is an anonymous function that can capture it's environment.

This becomes particularly powerful when you use higher-order functions, like map, filter or fold. Lambdas are so powerful that the entire Lisp interpreter can be implemented using nothing but lambdas. Even the data structures!

Lesson 3: Recursion Doesn't Have To Be Scary

Recursion is scary for many developers. There are several reasons why this could be the case. There could be a fear of creating an infinite loop. Maybe you don't like defining something in terms of itself. But there's nothing to be afraid of and you'll find that defining things in terms of themselves sounds natural, so long as certain preconditions are met.

Recursion consists of four parts: a base case, a recursive case, a condition and a reduction of the problem. Let's write some pseudocode for this:

function factorial(n)
  if n is less than or equal to 0 # condition
    1 # base case
  otherwise
    n * factorial(n - 1) # recursive case, reduction of the problem
Enter fullscreen mode Exit fullscreen mode

As a matter of fact, the Lisp code is not that different:

(define (factorial n)
  (if (<= n 0)
      1
      (* n (factorial (- n 1)))))
Enter fullscreen mode Exit fullscreen mode

The condition determines whether our function ever stops. A condition that is always false will become an infinite loop. Here, our condition is guaranteed to be true at some point as long as our n is a positive integer. The base case is what happens if the condition is true. Our base case is 1, which is the answer to factorial(0). The recursive case is what happens if the condition is false. The recursive case should involve a reduction of the problem in terms of itself. Here, factorial(n) becomes the subproblem n * factorial(n - 1). The reduction here is very important. If we didn't subtract one from n, then our condition would never be true. This, again, would cause an infinite loop.

So, to keep your recursive function from running forever you need a condition that is guaranteed to become true based on the initial values of the input and the method of reduction.

You could write a length function that reduces a string by each character until it becomes empty. You could write a sum function that reduces an array by each entry until it becomes empty. These are using the same recursive mechanism, just with different methods of reduction.

Lesson 4: Keep Data Immutable

Keeping data immutable allows you to use that code in parallel with much fewer worries than if it relied on mutating values. What if the value changes before the loop in the other thread reads it? These problems go away with immutability. But, naturally, the question that follows is: "If we can't change values, then how do we change values?"

The answer is to construct a new value using the old value, rather than changing the old value. This is why map returns a new list, not a modified one.

Lesson 5: Keep Scope Limited

Global variables are discouraged in Lisp. Scope is limited by a let function. This also allows us a way to mutate values within a certain context. Keeping data immutable in general and mutating within scope when necessary will help to keep your data consistent.

Lesson 6: Code In The Domain

In many languages, you code using the constructs of the language directly. But in Lisp, we define a Domain-Specific Language. Then, we write our program in language we defined. This makes our code cleaner and more understandable. It makes it less likely for bugs to enter our code and easier to fix them should they arise.

Conclusion

I hope that you consider learning Lisp in 2018. It's a very worthy language to learn. You probably will not use it to build anything directly, but you will use the ideas that you learn from it to build better programs. Lisp challenges you to think in a new way and expand your horizons. No matter what language you use to code, the most important skill that you have is problem solving. Why not add more tools to your toolbelt?

Top comments (2)

Collapse
 
twof profile image
Alex Reilly

Do you recommend any specific dialect of Lisp?

Collapse
 
lambdude profile image
Ian Johnson

Last year I learned Racket, Scheme and Common Lisp. I didn't like Common Lisp much. I really like the minimalism of Scheme. I started with Scheme because I was following along with SICP. Racket is a descendant of Scheme that has a really nice ecosystem and a pretty good tutorial. Actually, I wrote an implementation of bf in Racket. Racket is my favorite dialect. You may also want to try Clojure. That one is pretty popular.