For instance, I am building a web application. Normally, this would
consist (on the server) of a web layer to process requests, a business
layer containing the problem domain rules, and a data access layer for
persistence. Last night I began to define/implement the data access
layer when I began to wonder if I wasn't again attempting to apply
improper design principles to my application.
Any help or references to information would be greatly appreciated.
Thanks,
Gene
> Any help or references to information would be greatly appreciated.
<http://norvig.com/design-patterns/>
"Design Patterns in Dylan or Lisp
16 of 23 patterns are either invisible or simpler due to:
*First-class types (6): Abstract-Factory, Flyweight, Factory-Method,
State, Proxy, Chain-of-Responsibility
*First-class functions (4): Command, Strategy, Template-Method, Visitor
*Macros (2): Interpreter, Iterator
*Method Combination (2): Mediator, Observer
*Multimethods (1): Builder
*Modules (1): Facade"
Sometimes. It depends on your coding style. The division into UI, OS, Data
access and Problem Domain layers is pretty generic regardless of language.
The three first tend to be implementation specific and volatile so it
makes sense to encapsulate the dependencies. There are a number of
libraries that can help you here. For my last web app I used CFFI,
Hunchentoot, CL-WHO, CXML, CL-SQL.
As usual the choice of library will affect the overall design and style.
For instance there are a few libraries that help implement persistence
like CL-STORE or XMLLisp. Check out http://www.cliki.net/index for what is
available.
As for design patterns http://norvig.com/design-patterns/ has a critical
overview that might help put things into perspective.
--------------
John Thingstad
> This may be a nonsensical question, but I was wondering if it is
> idiomatic to apply common design patterns to lisp applications.
> Normally, I wouldn't ask such a question, but I've perceived such a
> difference in development philosophy between the Java/C++ world and
> the Lisp world that I am questioning pretty much everything I "know."
>
> For instance, I am building a web application. Normally, this would
> consist (on the server) of a web layer to process requests, a business
> layer containing the problem domain rules, and a data access layer for
> persistence. Last night I began to define/implement the data access
> layer when I began to wonder if I wasn't again attempting to apply
> improper design principles to my application.
That's not a 'design pattern'. That's architecture. Layered
architectures, component architectures, ... (and other
architecture patterns) make a lot of sense in Lisp, too.
Some of the 'design patterns' (GoF) are relatively simple
in Lisp. Probably there are design patterns that
are more useful in Lisp.
>
> Any help or references to information would be greatly appreciated.
>
> Thanks,
> Gene
that depends on what you call "common design patterns".
(most of) object-oriented stuff like singletons does not make much sense
in CL.
v> For instance, I am building a web application. Normally, this would
v> consist (on the server) of a web layer to process requests, a business
v> layer containing the problem domain rules, and a data access layer for
v> persistence. Last night I began to define/implement the data access
v> layer when I began to wonder if I wasn't again attempting to apply
v> improper design principles to my application.
OTOH general application architecture like this makes sense. there
is no particular prefered style, so you can just arrange your application
same way as you did that with other languages. but keep in mind
that lisp's flexibility often allows to simplify different aspects.
for example, in a CRUD application you can automatically generate
view templates from your model class definitions, rather than using
separate templates.
v> Any help or references to information would be greatly appreciated.
you might want to look at existing web frameworks, to name some:
cl-terrace, weblocks, core server.
you can find longer list of web-related Lisp stuff here:
http://www.cl-user.net/asp/search?search=web
Concise, funny & profound (meaning: exactly my sentiments!).
An epigram worthy of fortune(1).
class StrategyInterface
{
public:
virtual void rant();
};
class ConcreteStrategyA: public StrategyInterface
{
public:
virtual void rant()
{
cout << "The human race will decree from time to time: \"There
is something at which it is forbidden to laugh.\" -- Nietzsche on C++"
<< endl;
}
};
There are various ways to do this in Lisp. Maybe you wouldn't even
bother using classes:
(defgeneric rant (which))
(defmethod rant ((which (eql 'a))) (print "Rant A goes here"))
That's just one way. As you can see, the pattern doesn't look quite so
patternesque in Lisp. It kind of fades into the background, but you
could say it's there. Lisp is weird but it's not _that_ weird.
Secondly, I think you should question everything you know, as long as
it doesn't interfere with your sleep.
Actually, I was trying to ask a general question and provide some
context within which it made sense.
"This is how I would normally construct my application at a macro
level (architecture). Before I spend a lot of time spinning my wheels
attempting to apply concepts at a micro level (design patterns), is
there something I should know as how developing in Lisp might affect
these areas?"
To those who provided actual assistance, thank you.
Unfortunately, it is!
That was not meant as a criticism of you or your questions. I commented
because what Joswig said exactly captured what I feel is important in
software design. Architecture is discovering structure and not just at
a macro level. If the structure "feels right", relevant idioms will
suggest themselves. At least that has been my experience. I did use
the Design Patterns book when I was doing lots of C++ work and found it
somewhat useful (but it also got in the way. If the structure changed
due to changing requirements, different patterns may make sense but
people had difficulty letting go of the patterns they liked).
I don't really think in those object-oriented design patterns
of the GoF book. I've seen Java developers doing it.
I don't.
See for example this Python code from
http://en.wikipedia.org/wiki/Factory_method_pattern:
class Pizza:
HAM_MUSHROOM_PIZZA_TYPE = 0
DELUXE_PIZZA_TYPE = 1
SEAFOOD_PIZZA_TYPE= 2
def __init__(self):
self.__price = None
def getPrice(self):
return self.__price
class HamAndMushroomPizza(Pizza):
def __init__(self):
self.__price = 8.50
class DeluxePizza(Pizza):
def __init__(self):
self.__price = 10.50
class SeafoodPizza(Pizza):
def __init__(self):
self.__price = 11.50
class PizzaFactory:
def createPizza(self, pizzaType):
if pizzaType == Pizza.HAM_MUSHROOM_PIZZA_TYPE:
return HamAndMushroomPizza()
elif pizzaType == Pizza.DELUXE_PIZZA_TYPE:
return DeluxePizza()
elif pizzaype == Pizza.SEAFOOD_PIZZA_TYPE:
return SeafoodPizza()
else:
return DeluxePizza()
pizzaPrice = PizzaFactory().createPizza(Pizza.HAM_MUSHROOM_PIZZA_TYPE).getPrice()
That's so ugly. Amazingly ugly. Probably there are
better ways to do it in Python...
Imagine (for a moment) you would write that in Lisp:
(defclass pizza ()
((price :type integer :accessor pizza-price)))
(defclass ham-and-mushroom-pizza (pizza)
((price :initform 8.50)))
(defclass deluxe-pizza (pizza)
((price :initform 10.50)))
(defclass seafood-pizza (pizza)
((price :initform 11.50)))
(defmethod make-pizza ((type (eql 'gimme-some-ham-and-mushroom-pizza)))
(make-instance 'ham-and-mushroom-pizza))
(defmethod make-pizza ((type (eql 'gimme-some-deluxe-pizza)))
(make-instance 'deluxe-pizza))
(defmethod make-pizza ((type (eql 'gimme-some-seafood-pizza)))
(make-instance 'seafood-pizza))
(defun give-me-some-pizza (what)
(make-pizza what))
(pizza-price (give-me-some-pizza 'give-me-some-ham-and-mushroom-pizza))
Arrrrrrrrrrrrrrrrrrrrrrrrggghhh!!!!!
Then you think let's get rid of the methods:
(defclass pizza ()
((price :type integer :accessor pizza-price)))
(defclass ham-and-mushroom-pizza (pizza)
((price :initform 8.50)))
(defclass deluxe-pizza (pizza)
((price :initform 10.50)))
(defclass seafood-pizza (pizza)
((price :initform 11.50)))
(defparameter *pizza-assoc-list*
'((give-me-some-ham-and-mushroom-pizza ham-and-mushroom-pizza)
(give-me-some-deluxe-pizza deluxe-pizza)
(give-me-some-seafood-pizza seafood-pizza)))
(defun give-me-some-pizza (what)
(make-instance (second (assoc what *pizza-assoc-list*))))
But then you think WTF, WTF, WTF:
(defclass pizza ()
((price :type integer :accessor pizza-price)))
(defmacro defpizza (name price)
`(defclass ,name (pizza)
((price :initform ,price))))
(defpizza ham-and-mushroom-pizza 8.50)
(defpizza deluxe-pizza 10.50)
(defpizza sea-food-pizza 11.50)
(defun make-pizza (class)
(make-instance class))
(pizza-price (make-pizza 'ham-mushroom-pizza))
Pass the class name directly. If you want to do something
just program it. Forget the factory pattern. Just write
it like you would do it in Lisp. Why would you number
your classes?
If you would really need numbers for classes, then you would just lookup
the class from a table.
(defclass pizza ()
((price :type integer :accessor pizza-price)))
(defparameter *number-to-pizza-table* (make-hash-table))
(defmacro defpizza (name &key price number)
`(progn
(defclass ,name (pizza)
((price :initform ,price)))
(setf (gethash ,number *number-to-pizza-table*) ',name)))
(defpizza ham-and-mushroom-pizza :price 8.50 :number 0)
(defpizza deluxe-pizza :price 10.50 :number 1)
(defpizza sea-food-pizza :price 11.50 :number 2)
(defun make-pizza (number)
(make-instance (gethash number *number-to-pizza-table*)))
(pizza-price (make-pizza 2))
Suddenly the code gets a whole lot more descriptive.
That's why people say Lisp code can be extremely readable.
All the special pizza stuff can be changed in one
macro. All the domain definitions then are done
using the macros. These domain definitions then are dense.
How would you name a pattern from where you would derive
such a solution??? The FACTORY-PRIMITIVE-DSL-AND-TABLE-PATTERN?
The important thing is not the technical solution
how to create the the classes and then the objects.
It is much more important to get the linguistic interface
for the programmer right. That's what a good Lisp
programmer looks for: good surface code. The implementation
is secondary - it will be generated anyway. Then you
also can forget about refactoring. Change the code
generators. Done. Lisp is not about dead code that stands
in your way. Lisp is about creating the right surface code.
That's why Lisp books like PAIP are like code poetry.
What you NEED to learn is to live with the flexibility of Lisp
and acquire some taste about what code looks good,
is easy to read, easy to extend, easy to look at in
the debugger and so on...
Random thoughts:
I liked the descriptions on the c2 patterns repository wiki of the
"Big Ball of Mud". Due to Lisp's malleability, someone called it the
mudball of strength. (I also like the metaphor of MacGyver-oriented
programming: keeping your plans loose, picking up resources along the
way...)
You can get a lot of mileage from data-directed programming. Since
Lisp sexps are the healthy person's XML, you have the advantage of
mixing data and code. Suppose you just started writing a game where
certain actions take a bit of time. You might write it like this:
(defparameter *time-taken*
'(:work 1
:play 1
:eat 1
:learn 1
:rest 1
:move 1/2
:buy 1/4))
A simple plist. And you can use those keys (like :work) with OOP
functions:
(defgeneric action (person op &key &allow-other-keys)
(:method (person (op (eql :work)) &key hours &allow-other-keys)
...))
Will this initial code look the same later? Dunno. Doesn't matter. The
cost of changing Lisp code is lower than some other languages like
Java. When you start seeing the architecture straining, you consider
what forces are straining it, and change things appropriately. Maybe
just get something small working first. And build on it. When it gets
unwieldy, mold it. (Or take a shower or do something new, and let your
subconscious chew on it.) As it grows, so does your understanding of
it.
> For instance, I am building a web application. Normally, this would
> consist (on the server) of a web layer to process requests, a business
> layer containing the problem domain rules, and a data access layer for
> persistence. Last night I began to define/implement the data access
> layer when I began to wonder if I wasn't again attempting to apply
> improper design principles to my application.
That sounds right to me. I personally go for a very simple style. So
for example, I have a persistence layer that has 3 or 4 simple
operations:
add
drop
see
These are generic functions that take objects I need to store in the
database. (They can also can take the objects' UUIDs, when
appropriate. I use UUIDs because these objects are zipping across
different computers, and so they need some decent standard of object
identity. Big random numbers would've worked just as fine.) When the
objects go into the Elephant object database, they're indexed for
performance, based on the objects' types.
What code uses this? When I send a command to the webserver, it's a
serialized sexp which looks something like:
(:send :all-engines)
(:remember #<pretend this is an object you want to store>)
At least, that's how I approach things. Hope this is at all useful to
you...
Tayssir
And I should say, you can find examples within Lisp. (I mean plain
boring Lisp; everything you write builds up your personal Lisp image
with more power and personality.) Note defclass's syntax. That's
pretty data-directed.
(defclass institution ()
((name :accessor name :initarg :name :initform
"Default")
(location :accessor location :initarg :location :initform :secret)
(slogans :accessor slogans :initarg :slogans :initform '())))
It's verbose to type (and verbose in the brain, when you're thinking
up what attributes you want to make slots of), but my editor fills
much of that detail in from this:
(defclass institution ()
(name location slogans))
I don't think that first defclass that verbose to read though, so I
don't make a Lisp macro for it.
Tayssir
>
> (defparameter *time-taken*
> '(:work 1
> :play 1
> :eat 1
> :learn 1
> :rest 1
> :move 1/2
> :buy 1/4))
>
> A simple plist. And you can use those keys (like :work) with OOP
> functions:
>
> (defgeneric action (person op &key &allow-other-keys)
> (:method (person (op (eql :work)) &key hours &allow-other-keys)
> ...))
Cough! This method checks (eq op :work) which can easily be determined at
compile time but is probably not what you want.
(:method (person (op (eql #.(getf :person *time-taken)))
&allow-other-keys) ...))
should work and can be resolved at read time. That is if the compiler has
evaluated *time-taken* first. So wrap it in a (eval-when
(:compile-toplevel :load :execute)...) or move it to the
"compile-this-one-first.lisp" file to be sure.
This, by the way, is a common GOTCHA in Lisp. I mean it worked when you
developed it.. You compiled incrementally *time-taken* so the 'person'
method worked fine. Next day when you load it it has "forgotten"
*time-taken*. (It isn't seen by the reader at read time, because the
compiler hasn't seen it yet.)
--------------
John Thingstad
I think I know the "gotcha" you're talking about, but it doesn't even
come close to applying in this case. I'm doing nothing funky at
compile-time, like referring to *time-taken* in my function's argument
list.
Tayssir
This post is so good it needs to be put someplace lots of people will
see it. I'm going to forward it to a colleague (whom I've been trying
to get to learn some Lisp) right now.
-- Scott
> "Design Patterns in Dylan or Lisp
>
> 16 of 23 patterns are either invisible or simpler due to:
I remember when I read the original "Design Patterns" book, thinking
"wow, non-Lisp languages sure make you jump through all sorts of hoops
to do things we take for granted." That's why they need to give names
to all the patterns, because you can't "just do it."
--
Barry Margolin, bar...@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
*** PLEASE don't copy me on replies, I'll read them in the group ***
> In article
> <b4441692-2db8-46b2...@l42g2000hsc.googlegroups.com>,
> vttoonses <vtto...@gmail.com> wrote:
>
>> On Sep 26, 1:21 pm, Bakul Shah <bakul+use...@bitblocks.com> wrote:
>> > Rainer Joswig wrote:
>> > > That's not a 'design pattern'. That's architecture.
>> >
>> > Concise, funny & profound (meaning: exactly my sentiments!).
>> > An epigram worthy of fortune(1).
>>
>> Actually, I was trying to ask a general question and provide some
>> context within which it made sense.
>>
>> "This is how I would normally construct my application at a macro
>> level (architecture). Before I spend a lot of time spinning my wheels
>> attempting to apply concepts at a micro level (design patterns), is
>> there something I should know as how developing in Lisp might affect
>> these areas?"
>
> I don't really think in those object-oriented design patterns
> of the GoF book. I've seen Java developers doing it.
> I don't.
>
> See for example this Python code from
> http://en.wikipedia.org/wiki/Factory_method_pattern:
> [...]
> class PizzaFactory:
> def createPizza(self, pizzaType):
> if pizzaType == Pizza.HAM_MUSHROOM_PIZZA_TYPE:
> return HamAndMushroomPizza()
> elif pizzaType == Pizza.DELUXE_PIZZA_TYPE:
> return DeluxePizza()
> elif pizzaype == Pizza.SEAFOOD_PIZZA_TYPE:
> return SeafoodPizza()
> else:
> return DeluxePizza()
This code (from the wikipedia page) doesn't implement the pattern
correctly. (And this is not OO programming, but mere procedural
programming).
PizzaFactory should be an abstract class, with an abstract method
createPizza(self), and there should be subclasses
HamMushrootPizzaFactory, DeluxePizzaFactory, SeefoodPizzaFactory, etc
implementing the createPizza method.
In lisp, a correct implementation of this design pattern would be:
> (defclass pizza ()
> ((price :type integer :accessor pizza-price)))
>
> (defclass ham-and-mushroom-pizza (pizza)
> ((price :initform 8.50)))
>
> (defclass deluxe-pizza (pizza)
> ((price :initform 10.50)))
>
> (defclass seafood-pizza (pizza)
> ((price :initform 11.50)))
(defclass factory () ()
(defgeneric new-instance (factory &rest initargs &key &allow-other-keys))
(defclass ham-and-mushroom-pizza-factory () ())
(defmethod new-instance ((self ham-and-mushroom-pizza-factory)
&rest initargs &key &allow-other-keys)
(apply (function make-instance) 'ham-and-mushroom-pizza initargs))
(defclass deluxe-pizza-factory () ())
(defmethod new-instance ((self deluxe-pizza-factory)
&rest initargs &key &allow-other-keys)
(apply (function make-instance) 'deluxe-pizza initargs))
(defclass seafood-pizza-factory () ())
(defmethod new-instance ((self seafood-pizza-factory)
&rest initargs &key &allow-other-keys)
(apply (function make-instance) 'see-food-pizza initargs))
So now you can pass a factory object to your code, so the exact
subclass of pizza created is parameterized:
(defun pizzaiolo (factory n)
(loop repeat n collect (new-instance factory)))
(pizzaiolo (make-instance 'seafood-pizza-factory) 4)
In Lisp, you would encapsulate this design pattern in a macro:
(defmacro define-factory (factory-class instance-class)
`(progn
(defclass ,factory-class () ())
(defmethod new-instance ((self ,factory-class)
&rest initargs &key &allow-other-keys)
(apply (function make-instance) ',instance-class initargs))
',factory-class))
> But then you think WTF, WTF, WTF:
Indeed.
> (defclass pizza ()
> ((price :type integer :accessor pizza-price)))
>
> (defmacro defpizza (name price)
> `(defclass ,name (pizza)
> ((price :initform ,price))))
>
> (defpizza ham-and-mushroom-pizza 8.50)
> (defpizza deluxe-pizza 10.50)
> (defpizza sea-food-pizza 11.50)
>
> (defun make-pizza (class)
> (make-instance class))
MAKE-INSTANCE is our generic factory method.
In most languages given as example on the wikipedia page, as well as
in lisp, the Factory Design Pattern is useless, since the language
primitive to create objects can be parameterized directly by the
class. Of the examples given, only C# needs it.
Ruby:
irb(main):047:0> (factory = DeluxePizza)
DeluxePizza
irb(main):048:0> (factory . new)
#<DeluxePizza:0xb7cff58c>
Python:
>>> factory=DeluxePizza
>>> factory()
<__main__.DeluxePizza instance at 0xb7cb4acc>
Javascript:
js> factory = DeluxePizza
function DeluxePizza() {
var price = 10.5;
this.getPrice = function () {return price;};
}
js> (new factory)
[object Object]
js> (new factory) . getPrice()
10.5
Moreover, in these three languages, like in CLOS, the metaclasses can
also be enhanced, so the Factory design pattern is not needed even
when you need to augment it with additionnal features.
Nonetheless, it may be interesting to express the pattern in these
languages, just to communicate it, to explain it to programmers, not
to be used in actual programs. Or, some factories could need
additionnal parameters, that we would implement as slots to the
factory class. (But in Lisp we also have the choice of using
closures).
Design patterns are needed only to palliate missing features in
programming languages. When you program in C# or C++, classes are not
first class objects, so you must implement factory classes to
parameterize the created class. In effect, you are implementing the
metaclass (factory = metaclass).
> Suddenly the code gets a whole lot more descriptive.
> That's why people say Lisp code can be extremely readable.
> All the special pizza stuff can be changed in one
> macro. All the domain definitions then are done
> using the macros. These domain definitions then are dense.
>
> How would you name a pattern from where you would derive
> such a solution??? The FACTORY-PRIMITIVE-DSL-AND-TABLE-PATTERN?
Exactly.
In the case of the Factory Design Pattern, in Lisp we just use
MAKE-INSTANCE, because we already have that in Lisp.
For other design patterns however, Lisp may not have already the
needed feature. But the difference with other programming languages,
is that in Lisp we have an operator to define design patterns directly
in the language: DEFMACRO.
,-------------------------------------------------------,
| |
| |
| MACRO === DESIGN PATTERN |
| |
| |
`-------------------------------------------------------'
For another example, see my implementation of the Memento Design
Pattern here:
http://groups.google.com/group/comp.programming/msg/9e7b8aaec1794126
And in the other dirrection, you can document any lisp macro into a
design pattern for programming languages who, like lisp, lack the
feature (originaly).
For example, in languages where there is no pre-increment operator,
such as Lisp, we could document the INCF macro as the following design
pattern:
------------------------------------------------------------------------
PRE-INCREMENT DESIGN PATTERN
============================
Intent
------
The essence of the PRE-INCREMENT design pattern is to
increment the numerical value of a place.
Also Known As
-------------
INCF
Motivation
----------
In a language without an increment operator, incrementing a
variable is done with a statement such as:
i = i + 1
A place is any memory location that can be accessed by an
expression, for example: a[f(x,y)]. When this expression has
side effects, the above statement cannot be used as a pattern to
increment the place:
a[f(x,y)] = a[f(x,y)] + 1
is wrong because the side effects of f(x,y) are applied twice.
Applicability
-------------
Any time a place must be incremented by some increment, when the
programming language doesn't provide an increment operator, and
when accessing this place involves some side effect.
Structure
---------
None. (Not an OO pattern).
Participants
------------
None. (Not an OO pattern).
Collaboration
-------------
None. (Not an OO pattern).
Consequences
------------
We can increment a place with the proper side effects. We may
need some temporary variables to access the place. In languages
making a distinction between expression and statement, or where
it's not possible to declare variables locally, this makes
expanding the pattern more awkward, but in presence of side
effects, there's no choice.
Implementation
--------------
When the language has functions parameters passed by reference, it
is possible to encapsulate the incrementing part per-se:
procedure incf(var place:integer;increment:integer);
begin
place := place + increment;
end;
incf(i,1);
incf(a[f(x,y)],42);
In this case, the temporary variables are the parameters and the
internal pointer used for the reference.
With a language hasn't reference parameters, the pattern must be
expanded in place. First we evaluate the subexpressions of the
access expression, storing them in temporary variables, then we
can use these temporary variables to access the place several
times without side effects, in the incrementing statement:
index=f(x,y);
a[index] := a[index] + increment;
Sample code
-----------
See the implementation.
Known Uses
----------
Have a look at any program.
Related Patterns
----------------
None.
------------------------------------------------------------------------
Now, of course, in Lisp, we just implement the design pattern INCF as
a macro, such as:
(defmacro incf (place &optional (delta 1) &environment env)
(multiple-value-bind (temps subforms stores setterform getterform)
(get-setf-expansion place env)
(let ((let-list (mapcar (function list) temps subforms)))
(if (and (constantp delta))
(let ((function-application (list* '+ getterform delta nil)))
`(let* ,(nconc let-list
(list (list (car stores)
function-application)))
,setterform))
(let* ((argvars
(mapcar (lambda (var) (declare (ignore var)) (gensym))
(list* delta nil)))
(function-application (list* '+ getterform argvars)))
`(let* ,(nconc let-list
(mapcar (function list) argvars (list* delta nil))
(list (list (car stores) function-application)))
,setterform))))))
using the factorized design pattern to access places without
duplicated side effect GET-SETF-EXPANSION, and now every where we need
to apply this design pattern, instead of expanding it by hand, we just
name it:
(INCF (aref a (f x y)))
(INCF (gethash key h) 42)
It would be as ludicrous to try to implement this design pattern in
C++ where there is this ++(place) pre-increment operator, as it is to
try to implement the Factory design pattern in languages having
metaclasses as first class objects.
> The important thing is not the technical solution
> how to create the the classes and then the objects.
> It is much more important to get the linguistic interface
> for the programmer right. That's what a good Lisp
> programmer looks for: good surface code. The implementation
> is secondary - it will be generated anyway. Then you
> also can forget about refactoring. Change the code
> generators. Done. Lisp is not about dead code that stands
> in your way. Lisp is about creating the right surface code.
> That's why Lisp books like PAIP are like code poetry.
>
> What you NEED to learn is to live with the flexibility of Lisp
> and acquire some taste about what code looks good,
> is easy to read, easy to extend, easy to look at in
> the debugger and so on...
>
> --
> http://lispm.dyndns.org/
--
__Pascal Bourguignon__ http://www.informatimago.com/
> I remember when I read the original "Design Patterns" book, thinking
> "wow, non-Lisp languages sure make you jump through all sorts of hoops
> to do things we take for granted." That's why they need to give names
> to all the patterns, because you can't "just do it."
Yeah, I remember having the same reaction - kind of sad when a language
is so broken that there is a list as long as your arm of collected
workarounds that are glorified as "design patterns."
I also had a similar reaction when I first saw the C++ STL. While
there's some elegance to the way they abstracted things in a fully
orthogonal way, you can hardly see it through all the C++ template
garbage. Those are the same people who ridiculed Lisp for years because
of Lots of Irritating Silly Parentheses, but their <...> stuff is just
as bad; worse, in fact, because the stuff between the angle brackets is
too convoluted.
Give me DOLIST and LOOP any day.
> In the case of the Factory Design Pattern, in Lisp we just use
> MAKE-INSTANCE, because we already have that in Lisp.
Not necessarily.
Often, the purpose of a factory class is to map from objects to classes.
For instance, the pizza factory may be used by an application that asks
the customer what toppings he wants. It needs to go from the string
"mushrooms" to the class MUSHROOM-PIZZA.
However, in many cases you're right. In most OO languages, classes and
class names are not first-class objects, so you need a factory class to
figure out the class to instantiate dynamically. In CL, you can store
class names in variables and data structures and then use MAKE-INSTANCE
as the factory. If you need to go from user input, the mapping from
"mushrooms" to MUSHROOM-PIZZA would be part of the parser, not the job
of the factory. And a GUI with check-boxes for the toppings would just
associate the boxes with the classes directly in the toolkit.
> In article <871vz6u...@hubble.informatimago.com>,
> p...@informatimago.com (Pascal J. Bourguignon) wrote:
>
>> In the case of the Factory Design Pattern, in Lisp we just use
>> MAKE-INSTANCE, because we already have that in Lisp.
>
> Not necessarily.
>
> Often, the purpose of a factory class is to map from objects to classes.
> For instance, the pizza factory may be used by an application that asks
> the customer what toppings he wants. It needs to go from the string
> "mushrooms" to the class MUSHROOM-PIZZA.
>
> However, in many cases you're right. In most OO languages, classes and
> class names are not first-class objects, so you need a factory class to
> figure out the class to instantiate dynamically. In CL, you can store
> class names in variables and data structures and then use MAKE-INSTANCE
> as the factory. If you need to go from user input, the mapping from
> "mushrooms" to MUSHROOM-PIZZA would be part of the parser, not the job
> of the factory. And a GUI with check-boxes for the toppings would just
> associate the boxes with the classes directly in the toolkit.
Right, this mapping should not be confounded with the factory pattern.
This pattern is useful also independently of user input.
For example, when you have covariant class hierarchies:
(defclass b () ())
(defclass c () ((b :initarg :b :accessor b)))
(defclass b1 (b) ())
(defclass c1 (c) ((b :initarg :b :accessor b
:documentation "Must be a b1, not just a b")))
(defclass b2 (b) ())
(defclass c2 (c) ((b :initarg :b :accessor b
:documentation "Must be a b2, not just a b")))
Then you can add a factory method:
(defgeneric make-b-of-right-subclass (c)
(:method ((self c1)) (make-instance 'b1))
(:method ((self c2)) (make-instance 'b2)))
(let ((c (make-instance (if (zerop (random 2)) 'c1 'c2))))
(setf (b c) (make-b-of-right-subclass c)))
But of course, again, in Lisp, we don't need the factory method, we
can just deal with the class name:
(defgeneric matching-b-subclass (c)
(:method ((self c1)) 'b1))
(:method ((self c2)) 'b2)))
(let ((c (make-instance (if (zerop (random 2)) 'c1 'c2))))
(setf (b c) (make-instance (matching-b-subclass c))))
and matching-b-subclass can be implemented in any way, a generic
function as above, or searching an a-list or hash-table, or even:
(defun matching-b-subclass (instance)
(intern (format nil "B~A" (subseq (symbol-name (class-of instance)) 1))))
--
__Pascal Bourguignon__ http://www.informatimago.com/
In a World without Walls and Fences,
who needs Windows and Gates?
I said something very similar a few weeks ago. I'll expand on it later below.
> ,-------------------------------------------------------,
> | |
> | |
> | MACRO === DESIGN PATTERN |
> | |
> | |
> `-------------------------------------------------------'
Actually that's not true. In a sense macros and design patterns are
antagonistic ways to accomplish the same functionality. A macro is
a way to get *rid*of* a design pattern, replacing it instead with a
specialized language/shorthand. What the macro does is to
encapsulate the constant parts of the design pattern and provide
hooks for specifying the variable parts. The expansion of an
instance of the macro is an instance of the design pattern.
Thus a macro provides a (hidden) *implementation* of a design pattern.
OK, here's my promised expansion of my idea:
- When you have never before achieved the desired functionality,
you do R&D (Research and Development). You think of ways the
desired functionality might be implemented, using tools you have
available, and consider each long enough to either reject it (it
suffers a serious bug that prevents correct functionality) or
tentatively accept it for serious work. As soon as you have one
or more tentatively accepted ideas, you try to implement
it/them. Some ideas that seemed worthwhile turn out to have bugs
you didn't anticipate at first, and you have to either
drastically modify their concepts or abandon their concepts
totally. If you're fortunate, you eventually find a design that
actually works via your implementation. At that point you have a
first prototype of the desired functionality, and a successful
conclusion to the R&D task.
- Later when you need similar functionality, you re-use that
already-solved problem, adapting it to the new specific need. At
this point you are developing a design pattern. There are two
fundamental ways to accomplish this code-concept re-use:
- Find where you did the prototype, copy that code, paste into
your new source file, and use it as a "template" i.e. a
starting point for a minor edit to change some parts but
leave the overall code as it was the first time around.
- If the code is pretty simple, you might have it memorized, so
you can manually re-key from memory, making minor changes as
you type.
- After several such adaptions, as you feel you mostly understand
how to adapt this solved problem concept to many new specific
needs, you now have established an actual design pattern. If
you're in the mood to publish your design pattern, and if you
can write up the concept in a way that others can understand,
you might actually do so.
- Later, if you feel you fully understand from repeated use which
parts of the design pattern are fixed and which parts change
from application to application, i.e. which parts of the
template you have been leaving the same across all applications
and which parts you have been changing for each new application,
and if you are fortunate enough to be using a programming
language such as Lisp which supports parse-tree transformations
between READ and EVAL, then you can now define such a
transformation, what in Lisp is called a "macro" (a
semi-misnomer).
- From now on, you no longer need to bother with manually editing
from a template or re-keying all that code again and again.
Instead you can just type the shorthand syntax to invoke the
"macro". In a sense you've implemented a DSL (Domain Specific
Language), or perhaps more properly DPSL (Design Pattern
Specific Language), and can henceforth express such
functionality using the DSL/DPSL shorthand instead of the more
verbose design pattern. Of course "under the hood" the original
design pattern is still present in the expansion of the macro
which exists for a brief moment between READ-TRANSFORM and
EVAL-PRINT. But in a sense this is no different from the much
more primitive design patterns you needed to use when coding in
assembly language, such as the WHILE LOOP design pattern:
LOAD <startValue>
LP <testDone>
JUMPNZ EX
<mainBody>
<updateValue>
JUMP LP
EX ...(whatever follows)
which can now be expressed in C by just this kind of syntax:
{<type> tmp = <startValue>;
while (<testDone>) {<mainBody> <updateValue>}}
or equivalently:
{<type> tmp;
for (tmp = <startValue>, <testDone>, <updateValue>) <mainBody>}
(Caveat: Extra {braces} or somesuch needed around any <token> that
represents more than one line of code.)
Even more primitively, the assembly-language design pattern:
LOAD <value>
STORE <variable>
can be replaced by this kind of syntax:
<variable> = <value>
So the point is that Lisp allows any user to convert what had been
a design pattern into a brand-new DSL/DPSL and then use it from
then on.
> In a language without an increment operator, incrementing a
> variable is done with a statement such as:
> i = i + 1
> A place is any memory location that can be accessed by an
> expression, for example: a[f(x,y)]. When this expression has
> side effects, the above statement cannot be used as a pattern to
> increment the place:
> a[f(x,y)] = a[f(x,y)] + 1
> is wrong because the side effects of f(x,y) are applied twice.
I was going to ask what you proposed to do to fix that, but I see later:
> Implementation
> --------------
> When the language has functions parameters passed by reference, it
> is possible to encapsulate the incrementing part per-se:
> procedure incf(var place:integer;increment:integer);
> begin
> place := place + increment;
> end;
> incf(i,1);
> incf(a[f(x,y)],42);
> In this case, the temporary variables are the parameters and the
> internal pointer used for the reference.
> With a language hasn't reference parameters, the pattern must be
> expanded in place. First we evaluate the subexpressions of the
> access expression, storing them in temporary variables, then we
> can use these temporary variables to access the place several
> times without side effects, in the incrementing statement:
> index=f(x,y);
> a[index] := a[index] + increment;
Yes, nice example of something that is conceptually trivial but
takes some thought to implmement correctly, *differently* in
different programming languages that have different capabilities.
Note that with reference paramters, the design pattern has been
almost totally replaced by the function-with-reference-parameters,
which is just as compact (terse) as Lisp's (INCF <place> &optional <howMuch>).
And of course note that because of the need to avoid doing side
effects more than once from a single place form, the defmacro is
rather complicated. But that mess needs to be done right just once.
(Maybe this is a case where the task of converting a design pattern
to a DSL/DPSL by defining a "macro" is beyond the capability of
J.Random novice Lisp programmer and should be left for an expert
to do *really* correctly.)
> Known Uses
> ----------
> Have a look at any program.
**Crimany** (as my father used to exclaim)
That's a complete cop-out, in fact it's a lie.
Most any random program won't increment any complicated places in
the first place. Some that do, do it wrong, and the programmer is
too dumb to notice it's wrong. Looking at programs that don't do
the complicated case is worthless, and looking at programs where it
was done wrong is worse than useless.
Your penance for insulting the intelligence of your reader is to
find five (5) programs that implement this design pattern in
totally different ways. Each must handle a "difficult" case where
the <place> form does a side effect and it's really important *not*
to do the side effect more than once. If you have written software
in five grossly different languages, then perhaps you can remember
where you needed to implement this design pattern in each language,
and post complete code for each program plus a README note that
tells where specifically to find this design pattern at least once
in each of the programs. Otherwise you may have a difficult time
doing your penance, where you have to guess which software written
by other people can be expected to need this design pattern.
You got me, I'll have to find cases :-)
--
__Pascal Bourguignon__ http://www.informatimago.com/
In deep sleep hear sound,
Cat vomit hairball somewhere.
Will find in morning.
http://c2.com/cgi/wiki?SimplyUnderstoodCode
--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
BTW, one large architectural difference is in error-handling.
("Exceptional situations," whatever.) In Lisp, it's not a big deal to
throw an error. Or signal a condition.
While reading PHP code, I saw they didn't use exceptions for error-
handling. Even though that's the main point of exceptions. Instead,
they just passed an error array around, which accumulated the errors
until it was time to show them to the user. This interlarded error-
handilng with more polite code.
Why did they do this? Because exceptions bust out of the computation,
up to the "catch" statement. That means it's not convenient to (say)
let a handler silently note the problems. In lisp, you can simply
throw a signal:
(signal 'input-error :offending-datum name)
and some handler up the stack can do what it wants, with its forest-
level view of things. This sets up a nice communication channel
between the signaller and the handler.
So the handler could be the one quietly remembering the errors,
whenever a signaller reports them, until it's time to show them to the
user.
Tayssir
> On Sep 26, 9:51 pm, Tayssir John Gabbour <tayssir.j...@googlemail.com>
> wrote:
>> Will this initial code look the same later? Dunno. Doesn't matter. The
>> cost of changing Lisp code is lower than some other languages like
>> Java.
>
> BTW, one large architectural difference is in error-handling.
> ("Exceptional situations," whatever.) In Lisp, it's not a big deal to
> throw an error. Or signal a condition.
>
> While reading PHP code, I saw they didn't use exceptions for error-
> handling. Even though that's the main point of exceptions. Instead, they
AFAIK, PHP5 has exception handling. It is still a far cry from Lisp's
condition system, but most languages can't even approach that level
anyhow.
I think that any (high-level) language or language version created after
1990 without at least rudimentary exception handling should be laughed
at. Of course most of the audience that uses PHP and their ilk don't
even know what exception handling is and why it is useful, they are just
coding droids who don't want to deal with abstractions. And then of
course they complain loudly when their jobs are outsourced.
Sadly, languages are constrained by their target audience. I have
recently read a numerical analysis book which example algorithms, and
they signal exceptions (non-termination, etc) by returning an integer
error code. In C++ :-)
Tamas
> If you think you know what a design pattern is, please double-check:
>
> http://c2.com/cgi/wiki?SimplyUnderstoodCode
So, for example aligning the slot descriptions is a design pattern:
(defclass foo-class (bar-class)
((name :accessor foo-name :type string)
(number :accessor foo-number :type integer))
No, because this is too specific for this concrete situation.
See http://c2.com/cgi/wiki?PatternDefinition
Pascal
[...]
>
> Imagine (for a moment) you would write that in Lisp:
>
> (defclass pizza ()
> ((price :type integer :accessor pizza-price)))
>
> (defclass ham-and-mushroom-pizza (pizza)
> ((price :initform 8.50)))
>
price is of type integer? 8.50 is a float.
Yep! Please replace it with REAL. Unfortunately most (all?)
Lisp systems don't complain.
> Rainer Joswig wrote:
> > In article <6k6r6jF...@mid.individual.net>,
> > Pascal Costanza <p...@p-cos.net> wrote:
> >
> >> If you think you know what a design pattern is, please double-check:
> >>
> >> http://c2.com/cgi/wiki?SimplyUnderstoodCode
> >
> > So, for example aligning the slot descriptions is a design pattern:
> >
> > (defclass foo-class (bar-class)
> > ((name :accessor foo-name :type string)
> > (number :accessor foo-number :type integer))
>
> No, because this is too specific for this concrete situation.
Why that? Aligning like that can be used in many contexts
when using multiple property lists.
>
> See http://c2.com/cgi/wiki?PatternDefinition
>
> Pascal
If you describe the forces well that are involved, and describe the
solution in such a way that it becomes relatively obvious how to apply
it in other situations, then it becomes a pattern.
The most important ingredient is the description of the forces, not so
much the solution. This is the main point where the Design Patterns book
failed, not so much that it describes quite boring and uninteresting
solutions. If they had taken more care of the forces, it would have been
clearer that many of their patterns are actually not patterns.
That's also what's wrong in the typical criticism you hear about
patterns. Yes, Norvig is right that in Dylan, many of the patterns of
that book become non-issues. But that's not a problem with the pattern
concept, that's rather a problem with that book.
Not to mention that they can't figure out basic parsing, vis the infamous >>
problem with closing parentheses (remember that >> is the right shift operator
token, lexically analyzed as a single token).
Various ugly workarounds were proposed for this, but it appears that as of the
2003 standard, nothing has been done about it.
But even the lone < and > are problematic, which is why the C++ standard
contains entertaining text like:
``When parsing a temlate-id, the first non nested > (footnote 127)
is taken as the end of the template-argument-list rather than
a greater-than operator: [ Example:
template<int i> class X { /* ... */ };
X< 1>2 > x1; // syntax error
X<(1>2)> x2; // OK
template<class T> class Y { /* ... */ };
Y< X<1> > x3; // OK
Y<X<6>> 1> > x4; // OK: Y< X< (6>>1) > >
---end example]
Footnote 127: A > that encloses the type-id of a dynamic_cast, static_cast,
reinterpret_cast or const_cast, or which encloses the template-arguments of a
subsequent template-id, is considered nested for the purpose of this
description.
:)
> That's also what's wrong in the typical criticism you hear about
> patterns. Yes, Norvig is right that in Dylan, many of the patterns of
> that book become non-issues. But that's not a problem with the pattern
> concept, that's rather a problem with that book.
>
>
So what is an example of good pattern writing, and why should I care;
i.e. are patterns (still) relevant?
Matt
--
Q: Why did the WASP cross the road?
A: To get to the middle.
> Yes, Norvig is right that in Dylan, many of the patterns of that book
> become non-issues. But that's not a problem with the pattern concept,
> that's rather a problem with that book.
Only to a certain extent. One can only see patterns relative to the
tools used. The more feeble the tools, the more "patterns" one will see
since there are many oft-employed workarounds for deficiencies in the
tools that can be characterized as patterns.
If I have a microwave oven and a bag of microwave popcorn, instuctions
can just say "heat for 2 minutes on high." I do not need to use the
HeatFatInPot or the SauteUntilDone pattern.
In addition there are simply different patterns for different kinds of
languages. For example, the LocalVariablesReassignedAboveTheirUses is
simply not meaningful in a language without mutation.
I once saw a job description requiring experience in patterns. I guess
they wanted to be sure I would not think of anything on my own.
Then there was the one requiring a PhD in Comp Sci or a Masters and five
years experience or ten years experience. I like the idea of an exchange
rate, but it should go the other way: I'd take anyone who had programmed
for a year for the fun of it, and the more comp sci courses they have
taken the more work experience they have to have done to prove they are
actually interested in programming computers.
Does that make me crazy? Possibly.
ken
That's like asking whether the concept of metaprogramming is still
relevant. ;)
More seriously: A more recent book which I think is much better is
http://www.iam.unibe.ch/~scg/OORP/
This also holds the other way around: It is true that the more advanced
a tool becomes, some or even many patterns will simply disappear because
they will be absorbed by that tool. However, new patterns will emerge at
a higher level.
Related to Lisp: There are many uncodified suggestions and guidelines
for programming in Lisp. It would be a step forward if we could
formulate them as patterns, because this would create a body of
knowledge that is much easier to communicate than the typical informal
discussions we see in our community over and over again. [1]
> In addition there are simply different patterns for different kinds of
> languages. For example, the LocalVariablesReassignedAboveTheirUses is
> simply not meaningful in a language without mutation.
It is in the state monad. ;)
Pascal
[1] I don't mean "formal" here in the mathematical
"lots-of-greek-letters" sense.
> Related to Lisp: There are many uncodified suggestions and guidelines
> for programming in Lisp. It would be a step forward if we could
> formulate them as patterns, because this would create a body of
> knowledge that is much easier to communicate than the typical informal
> discussions we see in our community over and over again. [1]
Do you have any examples? Because everything that leaps to mind for me
already has perfectly good names and descriptions and there are good
books with solid advice on when to use them and when not (with-
macros, overriding accessors, mapping functions, unintentional
variable capture vs anaphoric macros ...). Hell, even compiling to a
network of closures has a name, and is described nicely by Graham. Is
this just a case of wanting Lispers to sound like C++ dorks and say "I
think you should use the ClosureNetwork pattern here (snort)", or am I
missing something?
They are not codified in the same way as patterns are. The patterns
format is a very powerful way of structuring such descriptions.
Pascal
> Thomas F. Burdick wrote:
>> On 30 sep, 09:02, Pascal Costanza <p...@p-cos.net> wrote:
>>
>>> Related to Lisp: There are many uncodified suggestions and guidelines
>>> for programming in Lisp. It would be a step forward if we could
>>> formulate them as patterns, because this would create a body of
>>> knowledge that is much easier to communicate than the typical informal
>>> discussions we see in our community over and over again. [1]
>>
>> Do you have any examples? Because everything that leaps to mind for me
>> already has perfectly good names and descriptions and there are good
>> books with solid advice on when to use them and when not (with-
>> macros, overriding accessors, mapping functions, unintentional
>> variable capture vs anaphoric macros ...). Hell, even compiling to a
>> network of closures has a name, and is described nicely by Graham.
>
> They are not codified in the same way as patterns are. The patterns
> format is a very powerful way of structuring such descriptions.
Structured in what sense? Which purpose does such a description serve
better than the ones Thomas is referring to?
Björn Lindberg
The pattern structure provides a better understanding what the forces
are that make something problematic, and how a pattern resolves the
forces such that the problem either goes away or is dealt with in some
way. By having a very structured description, the pattern author is
forced to make the ingredients explicit, and the reader then benefits
from having (hopefully) everything explicit.
The problem with the Design Patterns book is that important ingredients,
most notably the forces in the patterns, are not explicit. That's why
it's hard to understand the patterns concept from just that book. It's
like trying to understand Lisp macroprogramming from a book about C macros.
See http://www.dreamsongs.com/Files/PatternsOfSoftware.pdf for a better
introduction, especially the chapter "Pattern Languages".
See also http://www.cmcrossroads.com/bradapp/docs/patterns-intro.html
and http://g.oswego.edu/dl/pd-FAQ/pd-FAQ.html
> It would be a step forward if we could formulate them as patterns,
> because this would create a body of knowledge that is much easier to
> communicate than the typical informal discussions we see in our
> community over and over again. [1]
In many ways Doug Hoyte's recent book Let Over Lambda attempts this -
its title is one of several such patterns explored.