Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lisp-family: how to escape object-oriented java-like thinking? [closed]

Backstory: I've made a lot of large and relatively complex projects in Java, have a lot of experience in embedded C programming. I've got acquainted with scheme and CL syntax and wrote some simple programms with racket.

Question: I've planned a rather big project and want to do it in racket. I've heard a lot of "if you "get" lisp, you will become a better programmer", etc. But every time I try to plan or write a program I still "decompose" the task with familiar stateful objects with interfaces.
Are there "design patterns" for lisp? How to "get" lisp-family "mojo"? How to escape object-oriented constraint on your thinking? How to apply functional programming ideas boosted by powerful macro-facitilties? I tried studying source code of big projects on github (Light Table, for instance) and got more confused, rather than enlightened.
EDIT1 (less ambigious questions): is there a good literatue on the topic, that you can recommend or are there good open source projects written in cl/scheme/clojure that are of high quality and can serve as a good example?

like image 926
artemonster Avatar asked Dec 20 '15 17:12

artemonster


2 Answers

A number of "paradigms" have come into fashion over the years: structured programming, object oriented, functional, etc. More will come.

Even after a paradigm falls out of fashion, it can still be good at solving the particular problems that first made it popular.

So for example using OOP for a GUI is still natural. (Most GUI frameworks have a bunch of states modified by messages/events.)


Racket is multi-paradigm. It has a class system. I rarely use it, but it's available when an OO approach makes sense for the problem. Common Lisp has multimethods and CLOS. Clojure has multimethods and Java class interop.

And anyway, basic stateful OOP ~= mutating a variable in a closure:

#lang racket

;; My First Little Object
(define obj
  (let ([val #f])
    (match-lambda*
      [(list)         val]
      [(list 'double) (set! val (* 2 val))]
      [(list v)       (set! val v)])))

obj           ;#<procedure:obj>
(obj)         ;#f
(obj 42)
(obj)         ;42
(obj 'double)
(obj)         ;84

Is this a great object system? No. But it helps you see that the essence of OOP is encapsulating state with functions that modify it. And you can do this in Lisp, easily.


What I'm getting at: I don't think using Lisp is about being "anti-OOP" or "pro-functional". Instead, it's a great way to play with (and use in production) the basic building blocks of programming. You can explore different paradigms. You can experiment with ideas like "code is data and vice versa".

I don't see Lisp as some sort of spiritual experience. At most, it's like Zen, and satori is the realization that all of these paradigms are just different sides of the same coin. They're all wonderful, and they all suck. The paradigm pointing at the solution, is not the solution. Blah blah blah. :)


My practical advice is, it sounds like you want to round out your experience with functional programming. If you must do this the first time on a big project, that's challenging. But in that case, try to break your program into pieces that "maintain state" vs. "calculate things". The latter are where you can try to focus on "being more functional". Look for opportunities to write pure functions. Chain them together. Learn how to use higher-order functions. And finally, connect them to the rest of your application -- which can continue to be stateful and OOP and imperative. That's OK, for now, and maybe forever.

like image 84
Greg Hendershott Avatar answered Oct 15 '22 04:10

Greg Hendershott


A way to compare programming in OO vs Lisp (and "functional" programming in general) is to look at what each "paradigm" enables for the programmer.

One viewpoint in this line of reasoning, which looks at representations of data, is that the OO style makes it easier to extend data representations, but makes it more difficult to add operations on data. In contrast, the functional style makes it easier to add operations but harder to add new data representations.

Concretely, if there is a Printer interface, with OO, it's very easy to add a new HPPrinter class that implements the interface, but if you want to add a new method to an existing interface, you must edit every existing class that implements the interface, which is more difficult and may be impossible if the class definitions are hidden in a library.

In contrast, with the functional style, functions (instead of classes) are the unit of code, so one can easily add a new operation (just write a function). However, each function is responsible for dispatching according to the kind of input, so adding a new data representation requires editing all existing functions that operate on that kind of data.

Determining which style is more appropriate for your domain depends on whether you are more likely to add representations or operations.

This is a high-level generalization of course, and each style has developed solutions to cope with the tradeoffs mentioned (eg mixins for OO), but I think it still holds to a large degree.

Here is a well-known academic paper that captured the idea 25 years ago.

Here are some notes from a recent course (I taught) describing the same philosophy.

(Note that the course follows the How to Design Programs curriculum, which initially emphasizes the functional approach, but later transitions to the OO style.)

edit: Of course this only answers part of your question and does not address the (more or less orthogonal) topic of macros. For that I refer to Greg Hendershott's excellent tutorial.

like image 30
stchang Avatar answered Oct 15 '22 04:10

stchang