Paul Graham style LISP/the LISP Way or Avoiding the CLOS

Discussion of Common Lisp
Post Reply
nanthil
Posts: 1
Joined: Thu Aug 02, 2018 9:56 am

Paul Graham style LISP/the LISP Way or Avoiding the CLOS

Post by nanthil » Thu Aug 02, 2018 11:01 am

I have a question pertaining to writing code in a native LISP style, LISP design patterns in LISP, or figuring out the LISP way to accomplish some things that I already understand well in a dozen "common" programming languages. Skip to the TL;DR if you wish to avoid the setup and context for the question.

viewtopic.php?f=2&t=757

In this thread, the post by lispamour » Wed Jul 07, lispamour mentions an excerpt from an article or book of Paul Graham. Having read a number of Paul Graham articles (and part of my inspiration for trying to tack LISP... again) I have a few questions about how to accomplish some common coding patterns in a more LISPY way.

The excerpt from Paul Graham I'm referring to specifically is the following:
In practical terms, object-oriented programming means organizing a program in terms of methods, classes, instances, and inheritance. Why would you want to organize programs this way? One of the claims of the object-oriented approach is that it makes programs easier to change...If the program was written carefully to begin with we can make all these types of modifications without even looking at the rest of the code. [Emphasis mine]
Further, Listening to "Uncle Bob" for any amount of time, it becomes clear that OO is not really about "polymorphism, encapsulation, abstraction" .
TL;DR

Getting to the question at hand:

I have been juggling a really simply program in lisp:A loop that sees a player and an enemy battling to the death.

There are character definitions, the player def prompts the player for a name

Code: Select all

(defun make-ent (hp atk name)
   (list :name name :hp hp :atk atk))
(defun make-player (hp atk)
    (make-ent hp atk (prompt-read "What is your name")))
A game loop where in enemy and player attack to the death

Code: Select all

(defun game ()
    (setf player (make-player 10 10))
    (setf enemy (make-ent 10 10 "bad-guy")
    (loop
        do
            (attack enemy player)
            (attack player enemy)
        while (and (is-alive player) (is-alive enemy))))
Some Actions defined

Code: Select all

(defun attack (attacker defender)
    (take-damage defender (getf attacker :atk)))
So Here's my quandry. What I would do next if I were in a language like C++ would be to extract the entity into a class, and create some subclasses for entity.

Entity

Attack(Entity* Target)

IsAlive()

TakeDamage()

etc.

Player<Entity>

overrides entity functions and adds own properties

vector<Items> Inventory

playerRole CharacterClass

GetPlayerInput(vector<option> Options)

Enemy<Entity>

Goblin

overrides entity functions and adds own properties...

Dragon

overrides entity functions and adds own properties...

etc...

Perhaps the take-damage method for the Goblin and Damage are different, as the Goblin might have a cowardice check and flee, whereas the dragon will become enraged. And clearly the Player->Attack() method will be different than the Dragon->Attack() method. So polymorphic is in order, either overriding abstract methods, or simply implementing from a blank Interface.

Herein lies the question. If Paul Graham dislikes the CLOS and thinks object oriented design of code is less effective than "careful design", then how would one achieve this kind of polymorphic behavior without using defgeneric or something else from the CLOS? How would you define an attack method which performs a kind of dispatch mechanism to call the correct form of the attack method without some kind of giant cond expression?

Code: Select all

(cond
    ((eq attacker-type Player) (player-attack target))
    ((eq attacker-type Dragon) (dragon-attack target))
    ;;; etc
Please, put on your Paul Graham hat, and try to answer my question from his position, as I'm genuinely curious, and not dogmatic about code paradigms. I'm sincerely interested in learning how would one achieve this kind of polymorphic behavior in a natural LISP style without using the CLOS?

If you'd like more information about the code that I'm working on, simple game, can be run with CLISP.
https://github.com/carkat/game-lisp/blo ... /game.lisp


Originally asked by me on r/learnlisp
https://www.reddit.com/r/learnlisp/comm ... _the_clos/

David Mullen
Posts: 78
Joined: Mon Dec 01, 2014 12:29 pm
Contact:

Re: Paul Graham style LISP/the LISP Way or Avoiding the CLOS

Post by David Mullen » Mon Aug 13, 2018 12:31 pm

I seem to've misplaced my Paul Graham hat, but I wanted to point out that the "dispatch mechanism" of CLOS is, on a conceptual level, nothing but a "giant cond expression," just with a set of optimizations (say, lookup tables) that make it go faster than the equivalent COND. The other difference is that there can be multiple "applicable methods" for the given arguments, and they get combined into an "effective method." It's a pretty convenient system, although you don't have to use it. I don't like the MOP, for some reason, just CLOS. But you can always roll your own dispatch tables, if that's simpler or faster for whatever you have in mind.

nuntius
Posts: 538
Joined: Sat Aug 09, 2008 10:44 am
Location: Newton, MA

Re: Paul Graham style LISP/the LISP Way or Avoiding the CLOS

Post by nuntius » Wed Aug 15, 2018 8:39 pm

It sounds to me like you are asking how to do type-based function dispatch in Lisp.

My recommendation is simple.
If your goal is to write applications, then start with CLOS (or the native object system of your Scheme) and only switch if you hit some limit, at which point you will have identified specialized requirements.
If your goal is to learn design patterns, then read SICP, AMOP, and possibly GoF DP.

Common Lisp is a multi-paradigm language, not a "pure" language like Scheme or Haskell.
Chances are you can directly translate the approach used in any other language.

Personally, I am generally happy with the DEFGENERIC approach provided by CLOS.
It is usually fast enough and built-in multiple dispatch can be addictive.


SICP sections 2.4 and 2.5 discuss a number of implementation options.

http://mitpress.mit.edu/sites/default/f ... _toc_start


Whether you like CLOS or not, "The Art of the Metaobject Protocol" (AMOP) is a very good read, but it might be harder if you aren't comfortable with Lisp.

The Design Patterns book (gang of four) documents many ways to roll manual dispatch into your program.

https://en.wikipedia.org/wiki/Design_Patterns#Criticism

After learning the patterns, look at how a few popular languages do dispatch.
C++ uses vtables, Clojure and Python uses dictionaries, etc.

C++ uses "vtables" for dispatch. Basically every object has a pointer to a vtable, the vtable has a slot for every "virtual" function, and each slot contains a function pointer. At runtime, the function is called by jumping to the vtable entry. The same easily works with Lisp.

Post Reply