tag:blogger.com,1999:blog-129832812024-03-18T21:28:43.206-07:00I Need Closure(s)A blog about Lisp, general topics that interest me, and other programming languages, in that order.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.comBlogger39125tag:blogger.com,1999:blog-12983281.post-56704352381293609312008-01-30T08:19:00.000-08:002008-12-09T19:14:50.428-08:00'Programming Collective Intelligence' in Common Lisp, Chapter 5 - OptimizationsBack from the holidays and plowing through the chapters again. The explanation for the various optimizations was good, but I had a hard time figuring out a more "Lisp-like" way of doing the code. Either there isn't, or I haven't quite reached Zen-like Lisp nirvana. I'm open to improvements, here's the code for <a href="http://blubparadox.googlepages.com/optimize.lisp">the generic optimizations</a> and <a href="http://blubparadox.googlepages.com/socialnetwork.lisp">the social network</a>.<br /><br />For these python translations I'm growing fond of the <a href="http://superadditive.com/projects/incf-cl/">(incf cl) utilities</a>. It includes macros to make list comprehensions in Lisp, so<br /><pre><code><br />sol=[random.randint(domain[i][0],domain[i][1])<br /> for i in range(len(domain))]<br /></code></pre><br />becomes<br /><pre><code><br />(defun range-random (low high)<br /> (+ low (random (1+ (- high low)))))<br />...<br />(let ((sol (assemble (range-random (car x) (cdr x)) (<- x domain)))) </code></pre><br />I know that one would be just as easy with mapcar, see incf-cl for better examples.<br /><br />Here's a graph made from a simulated annealing optimization of the social network graph:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWUqdoLyFRJzDtWl8iLVRz3ILKbZ4buDeeFux45iOq-dfOMSLhlcAfGbR45WSRO8p18HHHJlJ0CB1tfSkzzAa_rLD6Xe07PEaJcbecj1rAiUHZ0SSle8wh1PpAPnvBRZs_Vm1kjQ/s1600-h/social.jpg"><img style="cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWUqdoLyFRJzDtWl8iLVRz3ILKbZ4buDeeFux45iOq-dfOMSLhlcAfGbR45WSRO8p18HHHJlJ0CB1tfSkzzAa_rLD6Xe07PEaJcbecj1rAiUHZ0SSle8wh1PpAPnvBRZs_Vm1kjQ/s400/social.jpg" alt="" id="BLOGGER_PHOTO_ID_5161306034584884306" border="0" /></a>Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com1tag:blogger.com,1999:blog-12983281.post-22471535250852219122007-10-11T05:25:00.001-07:002008-12-09T19:14:50.722-08:00Programming Collective Intelligence - Viewing Data in Two DimensionsAn example of taking data in multiple dimensions and "scaling" it to two dimensions, where the distances between data points is proportional to the multi-dimensional distance. Again, this one is basically an imperative rewrite. I hope to be able to exploit more of Lisp's power in the next chapter, which includes some neural networks. The big revelation from this, if anyone says Lisp is unreadable, I'd show them<br /><code><br />fakedist[i][j] = sqrt(sum([pow(loc[i][x] - loc[j][x], 2)<br /> for x in range(len(loc[i]))]))<br /></code><br />I replaced <a href="http://blubparadox.googlepages.com/clusters.lisp">clusters.lisp</a>, new code is in it.<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDVI6KHVsM_7gvbYxBsPjnDHhhwCt1Yi3jhGotZhMADAD0T0lt4y2Wf71R4GZg1qva12NhWZRYbeBIAlHKvo_Z9H1LLvH9zc8kzLyI4yfjaFJmh4Ngm3ysmNBDtjbPpL3UA9JUUw/s1600-h/twod.png"><img style="cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDVI6KHVsM_7gvbYxBsPjnDHhhwCt1Yi3jhGotZhMADAD0T0lt4y2Wf71R4GZg1qva12NhWZRYbeBIAlHKvo_Z9H1LLvH9zc8kzLyI4yfjaFJmh4Ngm3ysmNBDtjbPpL3UA9JUUw/s400/twod.png" alt="" id="BLOGGER_PHOTO_ID_5120056177823414770" border="0" /></a><br />Zooming in,<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi9yhfxjE0Sv6gfauLRoekp7Xrx4ivSLIuEWqjjcgolP6qMgzgKX67VXSKJKnu4SisQto3tUB3aDy-rc9fpC7SQpitBjc_gC0xGV0jGFGlwcZZ7xmaJzo5C2ci9Q88vysJZR2NyA/s1600-h/twod-portion.png"><img style="cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi9yhfxjE0Sv6gfauLRoekp7Xrx4ivSLIuEWqjjcgolP6qMgzgKX67VXSKJKnu4SisQto3tUB3aDy-rc9fpC7SQpitBjc_gC0xGV0jGFGlwcZZ7xmaJzo5C2ci9Q88vysJZR2NyA/s400/twod-portion.png" alt="" id="BLOGGER_PHOTO_ID_5120056375391910402" border="0" /></a>Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com4tag:blogger.com,1999:blog-12983281.post-79306272055706067082007-09-27T18:57:00.000-07:002008-12-09T19:14:51.060-08:00'Programming Collective Intelligence' in Common Lisp, Chapter 3 - Hierarchical ClustersI have Common Lisp code for Chapter 3 up to Hierarchical Clusters. It's longer and not that interesting, so I'll just put up a <a href="http://blubparadox.googlepages.com/clusters.lisp">link to the code</a>. Once again, map and reduce are taking the place of list comprehensions, but other than that it's not particularly Lispy, no macrology or anything. Maybe next time.<br /><br />Last time's tangent was a shortcut to hash syntax. This time I'm not happy with deleting items out of the middle of adjustable arrays. The code puts items on the end of an array and deletes them out of the middle, from a particular position.<br />In Python it's<br /><code>del clust[lowestpair[0]]</code><br />What I have is<br /><code><br />(defun truep (x) (declare (ignore x)) t)<br />(setf clust (delete-if #'truep clust :start (car lowestpair) :end (1+ (car lowestpair))))<br /></code><br /><br />Instead of PCI's use of the Python Imaging Library or some Lisp equivalent, I used cl-pdf, which was easy to use to make the simple graphs, lines and text. This is the full hierarchical cluster, then a zoom view.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5ACkibjl2VqQtuJujdV3kmJ1IUIUVkmpV4cJrpYJ_iSlvIYXHMukMdAsgpA87pjOSTun15epzBKNf9ok-drQUQj_N3NQIL8gg6mHfoIC6PEeJjF2jdttNFXP42Hu0zqEkvx3_7Q/s1600-h/ch03-hcluster-dendogram.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5ACkibjl2VqQtuJujdV3kmJ1IUIUVkmpV4cJrpYJ_iSlvIYXHMukMdAsgpA87pjOSTun15epzBKNf9ok-drQUQj_N3NQIL8gg6mHfoIC6PEeJjF2jdttNFXP42Hu0zqEkvx3_7Q/s400/ch03-hcluster-dendogram.jpg" alt="" id="BLOGGER_PHOTO_ID_5115078465346173394" border="0" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJQAlZm5QbQs9POdIspZUI9s1eBmyBvwrZ4sq4fh4YVJoCLFJc6AXChce-fb6dQyJbEbLY5x5dmUPAsTbS40EBhxCQv67p-YPK33R_fPuWV75Onx672WW9v3MKIekkGI6mDuFwsg/s1600-h/ch03-hcluster-dendogram-piece.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJQAlZm5QbQs9POdIspZUI9s1eBmyBvwrZ4sq4fh4YVJoCLFJc6AXChce-fb6dQyJbEbLY5x5dmUPAsTbS40EBhxCQv67p-YPK33R_fPuWV75Onx672WW9v3MKIekkGI6mDuFwsg/s400/ch03-hcluster-dendogram-piece.jpg" alt="" id="BLOGGER_PHOTO_ID_5115078675799570914" border="0" /></a>Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com8tag:blogger.com,1999:blog-12983281.post-92130413076812326712007-09-12T11:35:00.000-07:002007-09-12T11:40:08.642-07:00'Programming Collective Intelligence' in Common Lisp, Chapter 2Like many others, I've been reading Toby Segaran's <a href="http://www.amazon.com/Programming-Collective-Intelligence-Building-Applications/dp/0596529325/ref=pd_bbs_sr_1/103-3854821-2635829?ie=UTF8&s=books&qid=1189621369&sr=8-1">Programming Collective Intelligence</a>. Toby's examples are in Python. Inspired by loucal's <a href="http://loucal.net/index.php?title=programming_collective_intelligence_with&more=1&c=1&tb=1&pb=1">posting of code examples in Ruby</a>, I've decided to put up my own Common Lisp examples. These are from Chapter 2, going up to page 15, "Ranking the Critics".<br /><br />In order to just have the recommendations in the file, I used assoc lists instead of hashes. One place where Python (and Ruby) has it over Lisp is in hash syntax, just <code>critics[person]</code> instead of <code>(gethash person critics)</code> or <code>(cdr (assoc person critics :test #'equalp))</code>. I made a 'critics' function to keep down the verbosity. Is there a good way to change the syntax for hash lookup?<br /><br />The other big difference is my use of <code>mapcar</code> and <code>reduce</code> everywhere instead of Python's list comprehensions, with the occaisional <code>intersection</code> thrown in.<br /><br />If the Python source code gets posted, I may try some benchmarks with later examples.<br /><br /><pre><code><br />(defparameter *RECOMMENDATIONS* <br /> '(<br /> ("Lisa Rose" . (("Lady in the Water" . 2.5) ("Snakes on a Plane" . 3.5) ("Just My Luck" . 3.0) <br /> ("Superman Returns" . 3.5) ("You, Me and Dupree" . 2.5) ("The Night Listener" . 3.0)))<br /> ("Gene Seymour" . (("Lady in the Water" . 3.0) ("Snakes on a Plane" . 3.5) ("Just My Luck" . 1.5) <br /> ("Superman Returns" . 5.0) ("The Night Listener" . 3.0) ("You, Me and Dupree" . 3.5)))<br /> ("Michael Phillips" . (("Lady in the Water" . 2.5) ("Snakes on a Plane" . 3.0) <br /> ("Superman Returns" . 3.5) ("The Night Listener" . 4.0)))<br /> ("Claudia Puig" . (("Snakes on a Plane" . 3.5) ("Just My Luck" . 3.0) ("The Night Listener" . 4.5) <br /> ("Superman Returns" . 4.0) ("You, Me and Dupree" . 2.5)))<br /> ("Mick LaSalle" . (("Lady in the Water" . 3.0) ("Snakes on a Plane" . 4.0) ("Just My Luck" . 2.0) <br /> ("Superman Returns" . 3.0) ("The Night Listener" . 3.0) ("You, Me and Dupree" . 2.0)))<br /> ("Jack Matthews" . (("Lady in the Water" . 3.0) ("Snakes on a Plane" . 4.0) ("The Night Listener" . 3.0) <br /> ("Superman Returns" . 5.0) ("You, Me and Dupree" . 3.5)))<br /> ("Toby" . (("Snakes on a Plane" . 4.5) ("You, Me and Dupree" . 1.0) <br /> ("Superman Returns" . 4.0)))))<br /><br /><br />(defun critics (reviewer &optional movie)<br /> (labels ((get-movie (ms m)<br /> (cdr (assoc m ms :test #'equalp))))<br /> (let ((movies (cdr (assoc reviewer *RECOMMENDATIONS* :test #'equalp))))<br /> (if movie (get-movie movies movie) movies))))<br /><br />(defun similar (person1 person2 distance)<br /> (let* ((movies1 (critics person1))<br /> (movies2 (critics person2))<br /> (common-movies (mapcar #'car (intersection movies1 movies2 <br /> :test #'(lambda (x y) (equalp (car x) (car y)))))))<br /> (if (null common-movies)<br /> nil<br /> (funcall distance person1 person2 common-movies))))<br /><br />(defun euclidean-distance (person1 person2 common-movies)<br /> (let* ((sum-of-squares (reduce #'+ (mapcar <br /> #'(lambda (cm) <br /> (expt (- (critics person1 cm) (critics person2 cm)) 2)) <br /> common-movies)))<br /> (distance (/ 1 (1+ sum-of-squares))))<br /> distance))<br /><br />(defun sim-distance (person1 person2)<br /> (similar person1 person2 #'euclidean-distance))<br /><br /><br />(defun pearson-distance (person1 person2 common-movies)<br /> (let* ((n (length common-movies))<br /> (scores1 (mapcar #'(lambda (x) (critics person1 x)) common-movies))<br /> (scores2 (mapcar #'(lambda (x) (critics person2 x)) common-movies))<br /> (sum1 (reduce #'+ scores1))<br /> (sum2 (reduce #'+ scores2))<br /> (sum1-sq (reduce #'+ (mapcar #'(lambda (x) (* x x)) scores1)))<br /> (sum2-sq (reduce #'+ (mapcar #'(lambda (x) (* x x)) scores2)))<br /> (psum (reduce #'+ (mapcar #'* scores1 scores2)))<br /> (num (- psum (/ (* sum1 sum2) n)))<br /> (den (sqrt (* (- sum1-sq (/ (expt sum1 2) n)) (- sum2-sq (/ (expt sum2 2) n))))))<br /> (if (zerop den) 0 (/ num den))))<br /><br />(defun sim-pearson (person1 person2)<br /> (similar person1 person2 #'pearson-distance))<br /> <br />(defun top-matches (person &optional (n 5) (similarity #'sim-pearson))<br /> (let* ((scores (mapcar #'(lambda (x) (cons (funcall similarity person x) x)) <br /> (remove-if #'(lambda (x) (equalp x person)) (mapcar #'car *RECOMMENDATIONS*))))<br /> (sorted-scores (sort scores #'> :key #'car))<br /> (len (length sorted-scores)))<br /> (if (<= len n)<br /> sorted-scores<br /> (butlast sorted-scores (- len n)))))<br /><br /></code></pre>Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com22tag:blogger.com,1999:blog-12983281.post-2851872111218652002007-05-17T21:06:00.000-07:002008-12-09T19:14:51.340-08:00Redefining it"The best way to solve a problem is often to redefine it." - <span style="font-style: italic;">Michael Rabin, via Paul Graham</span><br />"I don't like assembly language." - <span style="font-style: italic;">Richard Cook</span><br /><br />Last August I got a Lego NXT robot, and of course I wanted to program it in Lisp. I got Frank Klassner's RCXLisp code and the NXT programming manual, and made a start, looking at opcodes and twiddling bits, and jump offsets, but it was, well, opcodes and bits, and I just wanted to make the robot do things, and it was a big hurdle, and so I procrastinated, and did other things. All that munging with assembly wasn't that interesting a problem to me, and when (if?) I got some output, and it didn't work, then I'd have to try to decipher the disassembled code, and act like the NXT VM, and "run" it...<br /><br />The light bulb went off a couple of weeks ago. Other people have done most of the heavy lifting for me - there were compilers for other languages for the NXT. I didn't have to get from Lisp to assembly, I just had to get from Lisp to the intermediary language, which would catch syntax errors and I could look at my program's output without going crazy. After some looking around I decided to go with NXC, which has a C syntax. The advantage of a C syntax instead of, say, Basic or Lua is that I have a pretty good head start. Since Javascript has a syntax that's close to C, I started with parenscript and am modifying it to turn it from a Lisp->Javascript compiler into a Lisp->NXC compiler.<br /><br />I've made a few changes, enough to let me compile a test program<br /><pre><br />(defconstant *MYNUM* 100)<br /><br />(defun runout (offset multiplier)<br />(-num-out 0 offset (* *MYNUM* multiplier)))<br /><br />(defthread (main :primary t) ()<br />(let ((x 0))<br /> (setf x 1)<br /> (runout *LCD_LINE1* x)<br /> (setf x 2)<br /> (runout *LCD_LINE2* x)<br /> (-wait 10000)))<br /></pre><br />into<br /><pre><br />#include "NXCDefs.h"<br /><br />#define MYNUM 100<br /><br /><br />sub runout(int <br /> offset,<br /> int <br /> multiplier) {<br /> NumOut(0, offset, MYNUM * multiplier);<br />}<br /><br />task main() {<br /> int x = 0;<br /> x = 1;<br /> runout(LCD_LINE1, x);<br /> x = 2;<br /> runout(LCD_LINE2, x);<br /> Wait(10000);<br />}<br /><br /><br /></pre><br /><br />which produces<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg72js9s3NAdBwmqaaLpAeLsNvGl-Zk4aY1cqqHQNf9r4k4Mt-ADA53e2eSxU9qjlhFJY7cJJxodZngDzicAHfdnK1lQ9UCd1FAHJdpbwikyYhZxQ0MEoTgCr-wAfZOPCBNLVjd7Q/s1600-h/NXTNumOut.jpg"><img style="margin: 0pt 10px 10px 0pt; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg72js9s3NAdBwmqaaLpAeLsNvGl-Zk4aY1cqqHQNf9r4k4Mt-ADA53e2eSxU9qjlhFJY7cJJxodZngDzicAHfdnK1lQ9UCd1FAHJdpbwikyYhZxQ0MEoTgCr-wAfZOPCBNLVjd7Q/s320/NXTNumOut.jpg" alt="" id="BLOGGER_PHOTO_ID_5065747989936797058" border="0" /></a><br /><br /><br />I may pretty it up more when I understand parenscript more, but it compiles and runs and it isn't meant to be read by humans.<br /><br />The next "architectual/philosophical" decision is how closely to follow the original RCXLisp syntax. That would give me some access to more example programs, but the NXT has more capabilities than the RCX, and there's a Google Summer of Code project "NXTLISP" working on a (I'm assuming) a Lisp->NXT VM compiler, so I may wait to see the syntax for that.<br /><br />Or not. NXC has a rich set of C-style macros for things like turning on multiple sets of motors that's different than how RCXLisp does it, plus I'm thinking about keeping the language more generic so it can target multiple robot C compilers, such as the First VEX.<br /><br />I'll post more as I go - the next thing to ponder is the best way to handle typing of NXC variables; int, short, long, byte, bool and string. Other robot C compilers may have other types. I may try Common Lisp type declares, or I may put the types in a list with the variable name, with just the variable name meaning "int".Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com6tag:blogger.com,1999:blog-12983281.post-14630975544081413992007-02-06T09:52:00.000-08:002007-02-06T10:03:56.554-08:00Latest 'Crossing Borders' is about LispAt <a href="http://www-128.ibm.com/developerworks/java">IBM's Java page</a>, Bruce Tate writes articles about other languages and the features that they have that other languages don't. His latest is on Lisp. The usual intro stuff, but he could have spent a <span style="font-style: italic;">little</span> more time on macros; his example is<br />(defmacro times_two (x) (* 2 x))<br />With not much more he could have shown an actual language extension. Hey Bruce, would a 'while' have killed you?Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com2tag:blogger.com,1999:blog-12983281.post-1163186183933427522006-11-10T10:53:00.000-08:002006-11-10T11:16:23.973-08:00Practical Ocaml - And I had such high hopesIf you go <a href="http://i-need-closures.blogspot.com/2005/05/what-brought-me-to-lisp.html">back</a> to my first posting, you'll see that before Common Lisp the language I used for home programming was OCaml. I still prefer Lisp's uniform syntax and macros, but Ocaml has a soft spot in my heart.<br /><br />That's why I was looking forward to Apress' "Practical OCaml", which was going to be OCaml's answer to "Pratical Common Lisp", even doing some of the same projects - good for comparison. <br /><br />Well, reviews are coming in, and the short version is, except for the prose and the code, the book's just fine. For the sarcasm impaired, <a href="http://www.amazon.com/Practical-OCaml-Joshua-B-Smith/dp/159059620X">the amazon reviews</a> are all one star, and even the book's technical reviewer <a href="http://blog.merjis.com/2006/11/08/practical-ocaml/">is not thrilled with it</a>.<br /><br />Some reviewers are even concerned that this will discourage others from writing a good OCaml book or learning OCaml. Thank you, Peter, for doing such a good job on "Pracital Common Lisp".Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com9tag:blogger.com,1999:blog-12983281.post-1158591771382313212006-09-18T07:58:00.000-07:002006-09-18T08:02:51.423-07:00Parenscript out on its ownI see that <a href="http://parenscript.org/">Parenscript</a> has a home of its own. I'm glad it's out on its own - in the past I've tried to grab just what I needed out of UCW, or use KlammerScript, but the docs never quite matched the code.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com0tag:blogger.com,1999:blog-12983281.post-1149214168218670632006-06-01T18:59:00.000-07:002006-06-01T19:10:19.250-07:00Stealing it backAt <a href="http://www.artima.com">Artima</a>, Topher Cyll wrote an article called <a href="http://www.artima.com/rubycs/articles/patterns_sexp_dslsP.html">If It's Not Nailed Down, Steal It</a> where he implements pattern matching, S-expressions and destructuring in Ruby, then uses this to implement a modified Logo interpreter (it uses "(" and ")" instead of "[" and "]"). The Logo interpreter outputs SVG files for rendering. <br /><br />The interpreter interested me, and hey, Lisp already has the stuff he's stealing, so I thought I'd have a go at implementing the interpreter. I wanted to stick to "vanilla Common Lisp" as much as possible, in other words not pull in big pattern matching libraries, just stick to CLOS multiple dispatch. <br /><br />Most of the "stealing back" is pretty straightforward, except for the "run" methods. It looks like the Ruby code can distinguish methods by variable arguments, where Lisp methods implementing a generic function must all have the same argument signature, even if the last argument is a &rest. Fortunately, the Ruby methods just distinguish by the command argument, and the rest is destructuring, so once we're in the dispatched run method we can destructure afterwards.<br /><br />The next bump was chopping off the "command" argument from the remainder of the S-expression to call the next run, which left a single argument for the &rest, the argument being a list of the remaining arguments, so that had to be car'ed, and you had to remember to do it everywhere, etc. It's messy. <br /><br />"apply" to the rescue. From the CLHS, "apply" uses a "spreadable argument list designator", which means it will turn (1 2 (3 4 5)) into (1 2 3 4 5) when it gets back to dispatching on the method. So<br /><pre><code>(apply #'run lg '(right 90 forward 10))</code></pre><br />turns into<br /><pre><code>(run lg right 90 forward 10)</code></pre><br />which will match on<br /><pre><code>(defmethod run ((lg logo) (command (eql 'right)) &rest args)</code></pre><br />and "args" will have (90 forward 10) in it, ready for destructuring.<br /><br />One feature of the Ruby version that I can't figure out how to duplicate is dispatching on nil, in other words when we're at the end of the program S-expression. It looks like the methods can catch it, but calling "run" or "apply #'run" won't throw it. What I came up with was inserting a special "stop" token at the end of the programs and stopping on it, or checking at the end of each method for an empty remainder before calling run again. I opted for the empty check. If there's a better solution I'd appreciate a comment to let me know.<br /><br />This gives me methods such as<br /><pre><code><br />(defmethod run ((lg logo) (command (eql 'forward)) &rest args)<br /> (destructuring-bind (distance &rest rest-args) args<br /> (move lg distance)<br /> (when rest-args (apply #'run lg rest-args))))<br /><br />(defmethod run ((lg logo) (command (eql 'pendown)) &rest args)<br /> (setf (pen lg) t)<br /> (when args (apply #'run lg args)))<br /><br />(defmethod run ((lg logo) (command (eql 'repeat)) &rest args)<br /> (destructuring-bind (repeat-count code &rest rest-args) args<br /> (dotimes (i repeat-count) (apply #'run lg code))<br /> (when rest-args (apply #'run lg rest-args))))<br /></code></pre><br /><br />This works, but looking at the code you can see a lot of boilerplate code. Like any Lisper, to me this code smelled like it needed a macro to make an internal DSL to help out with the external DSL. So with<br /><pre><code><br />(defmacro defrun ((logo-var command-symbol vars) &body body)<br /> (let ((args (gensym))<br /> (rest-args (gensym)))<br /> `(defmethod run ((,logo-var logo) (command (eql ',command-symbol)) &rest ,args)<br /> (destructuring-bind (,@vars &rest ,rest-args) ,args<br /> ,@body<br /> (when ,rest-args (apply #'run ,logo-var ,rest-args))))))<br /></code></pre><br /><br />the above methods turn into<br /><pre><code><br />(defrun (lg forward (distance)) (move lg distance))<br />(defrun (lg pendown ()) (setf (pen lg) t))<br />(defrun (lg repeat (repeat-count code)) (dotimes (i repeat-count) (apply #'run lg code)))<br /></code></pre><br />which is closer to the Ruby DSL.<br /><br /><a href="http://blubparadox.googlepages.com/logo-steal-when.lisp">logo-steal-when.lisp</a> has the pre-macro version, and <a href="http://blubparadox.googlepages.com/logo-steal-macro.lisp">logo-steal-macro.lisp</a> has the defrun'ned version. Happy stealing.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com12tag:blogger.com,1999:blog-12983281.post-1147802122182062132006-05-16T10:51:00.000-07:002006-05-16T10:55:22.200-07:00Am I supposed to like Smalltalk?When people talk in general about "high level, extensible, dynamic" languages on their blogs, they usually mention Smalltalk and Lisp in the same breath. Smalltalk programmers talk about their language in the same glowing terms as Lisp programmers; this prompts me to read about Smalltalk, and this weekend I tried out Squeak.<br /><br />I think I'll stay with Lisp. I'm sure someone will say I didn't try it long enough, but there were a couple of issues I don't think will go away with extended use. <br /><br />First of all is the environment/IDE. With Lisp/Emacs/Slime, or for that matter Java/Netbeans, to write method after method, you can just type. What I saw in Squeak was a bad combination of mousing and typing. You have to click in the '--all--' window of methods, then mouse down to the editing pane, and replace text, then save, then do it again for the next method. I'm sure this is a YMMV thing, but these mini-interruptions sure wouldn't keep me in 'flow' as much as just typing.<br /><br />Second is the 'OOP all the way' flavor of the Smalltalk language. Sometimes a function is just a function, and doesn't need to be in a class. I prefer the options that Lisp provides, I use imperative, OO, and functional, depending on the problem.<br /><br />All this is without bringing up macros, which I don't use that much now, but will do so more in the future, which will only widen the gap.<br /><br />Have you tried Smalltalk, and why do you prefer Lisp?Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com19tag:blogger.com,1999:blog-12983281.post-1145416974730096012006-04-18T20:20:00.000-07:002006-04-18T20:22:54.743-07:00Herds of Lispers?Ever VNC into a computer that has a VNC window open to the computer that you're on? That's how I feel when I link to a blog that's linked to mine.<br /><br />About twice a week I <a href="http://www.technorati.com/search/lisp">search for 'lisp' on technorati</a>, and it's still a little surprise for me when <a href="http://www.bobcongdon.net/blog/2006/04/java-and-lisp.html">I see someone else writing about one of my articles</a>.<br /><br />Bob Congdon's entry is mainly a discussion about <a href="http://i-need-closures.blogspot.com/2005/11/or.html">my entry about Java vs. Lisp language expansion</a>, but at the end he talks about the likely number of Lisp programmers on one project - he himself worked on a project with five. I can't imagine much more than that, both because you wouldn't need that many and we seem to be so few and far between I'm not sure how you'd get that many together. <br /><br />What's the biggest number of Lisp programmers you've seen on a project?Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com6tag:blogger.com,1999:blog-12983281.post-1141222842370703502006-03-01T05:45:00.000-08:002006-03-01T06:20:42.453-08:00Let's screamI'd read about Screamer so by the time I'd heard of Sudoku I thought that this was something that just "screamed" for Screamer. Plus, when you tell your friends that you're working on a Lisp/AJAX RSS reader as a kind of laboratory for exploring ideas about natural language processing, you get a blank look. Tell them you're writing a Sudoku solver and they understand that.<br /><br /><a href="http://www.cliki.net">Screamer</a> has two parts. The first part is backtracking/search. You can write your program somewhat normally, but where your searching ends you put a "fail" call. Where your code doesn't "fail" is where your answers get collected. <a href="http://briankuhn.com/?p=41">Brian Kuhn</a> posted an "interview question" where you have a sequence of numbers, positive and negative, and you return the subsequence that would give the highest total. He had 100 lines of Java; with Screamer's help I had<br /><br /><pre><code><br />(screamer:define-screamer-package :maxsub <br /> (:export :findmaxsub))<br /><br />(in-package :maxsub)<br /><br />(defun findmaxsub (numbers)<br /> (let ((total -99999))<br /> (car <br /> (nreverse <br /> (all-values <br /> (let ((begin (an-integer-between 0 (1- (length numbers))))<br /> (end (an-integer-between 1 (length numbers))))<br /> (when (>= begin end) (fail))<br /> (let ((subtotal (apply #'+ (subseq numbers begin end))))<br /> (if (> total subtotal)<br /> (fail)<br /> (setf total subtotal)))<br /> (subseq numbers begin end)))))))<br /></code></pre><br /><br />Every time the subtotal is higher than the previous total there's a "success", and the sublist is collected by the "all-values" call. When it's done, the highest of them all is at the end of that list.<br /><p/><br />The other part is the "constraints" part, where you list the constraints on your variables, wind up Screamer, and let it go. The main problem I found with Screamer is that it doesn't handle arrays well, it does lists better, and individual variables best of all. This turned Sudoku into an exercise in typing. I had to list out 81 variables - mostly are "something between 1 and 9", some are the starting numbers - and 27 constraints, 9 row, 9 column and 9 box. <br /><p/><br />You can see the code <a href="http://blubparadox.googlepages.com/sudoku-screamer.lisp">here</a>. If you have a Gmail account, you should get your Google page to host files if you don't want your own domain.<br /><br />I've also wanted to learn LTK, so I wrote a separate LTK front-end for Sudoku. It uses LTK as well as the LTK "mega-widgets" in ltk-mw.lisp. Inside it uses a Sudoku solver that takes a 2D array as input, 0's for blanks, and returns a 2D array. Code is <a href="http://blubparadox.googlepages.com/sudoku-ltk.lisp">here</a>. If you want to try this out as is you'll need to get Screamer loaded.<br /><p/><br /><img src="http://static.flickr.com/43/106103197_6f748e41d4.jpg?v=0"/><br /><p/><br />Something funny in the license for Screamer. Part of the conditions of its use is you have to send an e-mail telling them what version of Common Lisp you're using it with, so they can list it. I'll be adding OpenMCL 1.0 under Mac OSX and SBCL 0.9.0 under Linux. If you try this out then let them know.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com8tag:blogger.com,1999:blog-12983281.post-1139244175666625302006-02-06T08:32:00.000-08:002006-02-06T08:42:56.506-08:00Franz bustin' out all overIn my latest Dr. Dobb's Journal, Franz had a two-page ad talking about what's possible and fast with AllegroCache.<br /><br />Peter Coffee's latest column at eWeek, <a href="http://www.eweek.com/article2/0,1759,1917191,00.asp?kc=EWRSS03119TX1K0000594">'Exotic' Programming Tools Go Mainstream</a>, has this to say:<br/><br /><code>Allegro CL has had two major updates since the last time eWEEK Labs reviewed it. The 8.0 version reflects performance-enhancing improvements so startling that if it played baseball, we'd expect a congressional probe.</code><br/><br />Can't wait to get my hands on the upcoming Express Edition. I knew Lisp was what I wanted to learn so I was willing to get over the Emacs/Slime curve, but I understand people who'd want to explore with an IDE to see if Lisp is for them, and it would be good to be able to recommend a hassle-free Windows IDE.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com1tag:blogger.com,1999:blog-12983281.post-1137525902296956682006-01-17T11:19:00.000-08:002006-01-17T11:25:02.313-08:00Ever closer to Greenspun's TenthAnyone who's glanced at <a href="http://www.cliki.net/Lisp%20Markup%20Languages">LML's</a> should find <a href="http://redhanded.hobix.com/inspect/markabyForRails.html">Markup for Ruby</a> familiar:<br /><pre><code><br /> html do<br /> head do<br /> title action_name<br /> stylesheet_link_tag 'scaffold'<br /> end<br /><br /> body do<br /> p flash[:notice], :style => "color: green" <br /> self << @content_for_layout<br /> end<br /> end<br /></code></pre>Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com2tag:blogger.com,1999:blog-12983281.post-1137165921691961112006-01-13T07:00:00.000-08:002006-01-13T07:25:21.763-08:00Fits like a gloveI've had a dry spell of coding at home, between the holidays and reading "Ajax in Action", but reading some of the long postings at Planet Lisp should be enough to guilt any slacker back into action.<br /><br />This has uncovered yet another advantage of Common Lisp, at least for me; how short a time it takes to get back up to speed with the language after being away from it. It takes me considerably less time to get back "in the groove" than with other languages, with less references to language manuals. I don't know how this is for others, but Lisp fits my brain.<br /><br />I think this is because of the lack of syntax. Instead of having to remember operator precedence, the operators themselves, strings of characters that look like Yosemite Sam swearing, it's just almost-English words and parentheses. The language is built on just a few core concepts that can be mixed and matched, instead of a bunch of rigid rules and details that have to be remembered.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com3tag:blogger.com,1999:blog-12983281.post-1134961565408701262005-12-18T19:03:00.000-08:002005-12-18T19:06:05.426-08:00New Year's ResolutionRegular backups.<br/>Also, thank Apple for putting 'terminal' on the install disk.<br/>Thank you. That is all.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com1tag:blogger.com,1999:blog-12983281.post-1134063871786055402005-12-08T09:31:00.000-08:002005-12-08T09:44:31.826-08:00Portable Allegroserve as a de facto standard?The "Ruby as an acceptable Lisp" and Reddit discussions (I don't really need to supply links, do I?) brought back to the surface a question I ask myself often.<br /><br />It's true that Common Lisp doesn't have multithreading or network clients in the standard, and people want that, but what's wrong with Portable Allegroserve? The acl-compat component has those covered (if the underlying implementation has support) for Allegro, LispWorks, CMUCL, SBCL, OpenMCL, SCL, Corman, CLisp. It appears to be used a lot, and has decent documentation. <br /><br />Why not just use that and move on to the next problem? Is there something horrible about Portable Allegroserve I should know about?Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com12tag:blogger.com,1999:blog-12983281.post-1132886107817468222005-11-24T18:22:00.000-08:002005-11-24T18:35:07.843-08:00Or....I received an email from JavaLobby, which had in it this <a href="http://www.eclipsezone.com/eclipse/forums/t54318.html">article</a> at EclipseZone, which talks about the plusses and minuses of adding features to Java. From the article:<br/><br /><code>Language guys love to tweak. I know, because I'm a language guy myself. I used to work on compilers, code generators, libraries, and debuggers. We were always adding new stuff, new commands, special keywords, etc. Did our users thank us for it? Maybe the one or two that wanted the new things, but the vast majority would just groan when a new release came out. What will this break now? Will I have to upgrade? Will I have to use somebody else's code that requires this new thing before I'm ready? Has it been ported to all the machines I need to run on? Boring, I know, but very practical and important issues. </code><br/><br />Or... you can work in a language where if you want the one or two new things, you can just add them, and let the other guy that wants one or two other things added to the language add what he wants.<br/><br />Or... I could just let Peter Siebel <a href="http://www.gigamonkeys.com/book/macros-standard-control-constructs.html">say it better</a>:<br/><br /><code>DOLIST is similar to Perl's foreach or Python's for. Java added a similar kind of loop construct with the "enhanced" for loop in Java 1.5, as part of JSR-201. Notice what a difference macros make. A Lisp programmer who notices a common pattern in their code can write a macro to give themselves a source-level abstraction of that pattern. A Java programmer who notices the same pattern has to convince Sun that this particular abstraction is worth adding to the language. Then Sun has to publish a JSR and convene an industry-wide "expert group" to hash everything out. That process--according to Sun--takes an average of 18 months. After that, the compiler writers all have to go upgrade their compilers to support the new feature. And even once the Java programmer's favorite compiler supports the new version of Java, they probably still can't use the new feature until they're allowed to break source compatibility with older versions of Java. So an annoyance that Common Lisp programmers can resolve for themselves within five minutes plagues Java programmers for years.</code>Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com8tag:blogger.com,1999:blog-12983281.post-1132686388652817552005-11-22T11:00:00.000-08:002005-11-22T11:11:18.156-08:00Lisp with a different syntaxI've been reading <a href="http://www.amazon.com/gp/product/1932394613/104-8365138-3104767?v=glance&n=283155&n=507846&s=books&v=glance">Ajax in Action</a>, and have been reminded of the quote "Javascript is Lisp with a different syntax". It's usually used to stress the <a href="http://blog.codingforums.com/index.php/main/blogentry/pure_functional_programming_in_javascript/">functional</a> and closure features of Javascript, but there's another part that's also very close - attaching arbritrary properties to an object, including functions.<br/><br />Compare<br /><pre><code><br />>var x = new Object()<br />>x.one = 1<br />1<br />>x.two = 2<br />2<br />>x.doubler = function(n) { return n * 2; }<br />function (n) { return n * 2; }<br />>x.two<br />2<br />>x.doubler(3)<br />6<br /></code></pre><br />with<br /><pre><code><br />CL-USER> (setf (get 'x 'one) 1)<br />1<br />CL-USER> (setf (get 'x 'two) 2)<br />2<br />CL-USER> (setf (get 'x 'doubler) #'(lambda (x) (* x 2)))<br />#<CLOSURE :LAMBDA (X) (* X 2)><br />CL-USER> (get 'x 'two)<br />2<br />CL-USER> (funcall (get 'x 'doubler) 3)<br />6<br /></code></pre><br /><br />In old Lisp textbooks I've seen this referred to as "data driven programming", used before anyone was talking about object-oriented programming. Now, no one would do OO this way in Lisp, but the quote didn't say "Javascript is <i>Common</i> Lisp with a different syntax".Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com2tag:blogger.com,1999:blog-12983281.post-1132246078887313722005-11-17T07:44:00.000-08:002005-11-17T08:47:58.930-08:00Looking for Lisp/NLP starting pointsI guess I'm having trouble with the correct incantations into Google. I'm trying to find information on natural language processing, preferably in Lisp but I'll look at anything, specifically towards taking in text and summarizing it.<br /><br />I'm thinking of doing an RSS reader where long-winded feeds can be automatically summarized down to "abstract" form, and including a link back to the original article that the user can decide to read or just move on to the next article.<br /><br />Any suggestions for starting points would be appreciated.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com6tag:blogger.com,1999:blog-12983281.post-1131250303893440412005-11-05T20:11:00.000-08:002005-11-05T20:37:32.570-08:00A Cocoa-Lisp-Google Maps Mashup<img src="http://static.flickr.com/27/60264697_3e0bdadad8.jpg?v=0" /><br><br />Just your run-of-the-mill Google Maps page, except for a couple of things; the server part is in Portable AllegroServe (no big deal), and the addresses came out of my Mac's Address Book via OpenMCL (a bigger deal).<br />I got interested in this after hearing an O'Reilly "Distributing the Future" podcast where someone mentioned that not everyone will want all their information on the web and there will be applications where part of the data is on the web and part is kept in the user's local storage.<br />The basic understanding of the Mac's AddressBook library came from <a href="http://www.macdevcenter.com/pub/a/mac/2002/08/27/cocoa.html?page=1">this Mac Dev Center article</a>, and help from Gary Byers got OpenMCL loading and searching the Address Book in OpenMCL. The rest is basic Portable AllegroServe. <br />The geocoder I'm using is <a href="http://www.ontok.com/geocode/restapi">Ontok</a>, which has a REST api that takes up to 10 addresses at one time and returns CSVs that start with latitute and longitude co-ordinates. <br /><a href="http://wilshipley.com/blog/2005/05/whats-coolest-thing-in-tiger.html">Wil Shipley mentions</a> that instead of using a database for storing customer orders he keeps them in XML files and uses Spotlight for searching. Something similar could be done with Address Book, just keep addresses in the built-in library instead of a database.<br />Code is <a href="http://paste.lisp.org/display/13249">here</a> if you'd like to see.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com0tag:blogger.com,1999:blog-12983281.post-1130261185774927752005-10-25T10:17:00.000-07:002005-10-25T10:26:25.790-07:00Joel is influential, we must assimilate him<a href="http://bc.tech.coop/blog/051024.html">Bill</a> pointed out Joel Spolsky saying <br/><i>"I had to install Lisp in a Box and start working through Seibel's new book on Common Lisp until my brain started functioning again."</i><br/>What I find interesting is that the number of links to Lisp In A Box on <a href="http://del.icio.us/tag/lisp">Delicious' lisp tag</a> has really shot up the past couple of days. Expect a newbie mini-flood on c.l.l.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com5tag:blogger.com,1999:blog-12983281.post-1128961912774549122005-10-10T05:00:00.000-07:002010-06-07T10:28:47.450-07:00Common Lisp and/or Scheme, and Y I should botherFirst things first. If there's a Y pun, then you know I must be talking about the Y combinator. Understanding it is on the computer science part of the ol' "Things to do before I die" list. To that end, I got and read "The Little Schemer", and had the same reaction I usually get when reading about the Y combinator, or continuations (another on my "to understand" list). It starts out very simple (cons, car cdr), adds a baby step, another baby step, etc. then they're off on this bizarro explanation that makes no sense to me. Instead of steps A, B, C, D, E, F it's more like A, B, C, W, X, Y; there were some important steps in between that I somehow missed. Is this something I need to eventually conquer to achieve enlightenment, or are they happy, productive Lispers out there who don't understand this?<br />
<br />
A related question is Common Lisp and Scheme, specifically PLT Scheme. I've looked at both and prefer Common Lisp to get work done, using emacs and SLIME. Every now and then I look at PLT Scheme, mainly out of IDE envy, and it looks nice. Bill Clementson says it's <a href="http://bc.tech.coop/blog/040111.html">the best Open Source Lisp</a>. Common Lisp vs. PLT Scheme reminds me of the "even though Python is a better language, Java + IDE combos make you more productive" blog entries I see. <br />
<br />
Everyone says the best tool for the job, but for Lisp users I'm thinking that's usually either Common Lisp or Scheme, and other non-Lisp languages. Do Lisp users just stick with one dialect of Lisp, or are there people that use Common Lisp and Scheme for different tasks?Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com16tag:blogger.com,1999:blog-12983281.post-1126837118248760552005-09-15T18:51:00.000-07:002005-09-15T19:18:38.300-07:00An analogy - Lisp as GraffitiThe Palm kind, that is.<br /><br />Whining about Lisp's syntax is a favorite sport on the blogs, especially among students that have a Lisp assignment in a class. I look at it the same way Jeff Hawkins looked at Grafitti when developing the Palm (from <a href="http://www.fastcompany.com/magazine/15/smallthing.html">a Fast Company article</a>):<br /><br /><i>"People are smarter than appliances," Hawkins told his colleagues. "They can learn.'' He recalled a lesson from his Berkeley days: "People like learning. People can learn to work with tools. Computers are tools. People like to learn how to use things that work."</i><br /><br />It's good to make things easy for the user, but there comes a point where if the user can compromise a little in one area they can gain a lot in others. For the original Palm, learning Graffiti meant the PDA could fit in your pocket, be cheap and run on two AAA's since the software could be simplified enough to fit in the restrictions of the machine. For Lisp, making a small change, going to prefix notation and "all those parentheses" gains you code as data as code, macros that match the syntax of the language, and everything else you've been reading about.<br /><br />If you're a whiny student who doesn't want to believe me, try believing <a href="http://en.wikipedia.org/wiki/Robert_Floyd">Robert Floyd</a>, from his Turing Prize lecture (from <a href="http://mago.blogsome.com/2005/08/11/lenguajes-de-programacion-y-su-sintaxis/">here</a> via <a href="http://justavo.blogsome.com/2005/08/11/sobre-lisp/">here</a>):<br /><br /><i>“Contact with the programming written under alien conventions may help. Visiting MIT on sabbatical this year, I have seen numerous examples of the programming power which Lisp programmers obtain from having a single data structure, which is also used as a uniform syntactic structure for all the functions and operations which appear in programs, with the capability to manipulate programs as data. Although my own previous enthusiasm has been for syntactically rich languages like the Algol family, I see now clearly and concretely the force of Minsky’s 1970 Turing Lecture, in which he argued that Lisp’s uniformity of structure and power of self-reference gave the programmer capabilities whose content was well worth the sacrifice of visual form.”</i>Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com3tag:blogger.com,1999:blog-12983281.post-1125880369701575802005-09-04T17:18:00.000-07:002005-09-04T17:32:49.706-07:00Fuzzy Queries on top of CLSQLI originally planned to incorporate Fuzzy SQL more into CLSQL, but I don't think it's necessary for my purposes. With CLSQL's def-view-class extension of CLOS I can use generic methods, and it will look like the rest of the class. Later in the book I'm using, <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0121942759/qid=1122690167/sr=8-1/ref=sr_8_xs_ap_i1_xgl14/102-4566640-7454518?v=glance&s=books&n=507846">Fuzzy Modeling and Genetic Algorithms for Data Mining and Exploration</a> there's a chapter on Genetic Tuning of Fuzzy Models, which I think will work better with external functions and methods as opposed to MOP or macro-based solutions inside CLSQL. Besides, I couldn't even get db-reader to work with my classes, so I'll just leave well enough alone.<br /><br />This will be based on my previous <a href="http://i-need-closures.blogspot.com/2005/07/fuzzy-logic-and-fuzzy-sql-or-richards.html">big and tall entry</a>. My data setup is<br /><code><pre><br />rrc=# select * from btprospects;<br /> name | height | weight | age <br />----------+--------+--------+-----<br /> Saunders | 74 | 215 | 52<br /> Cassey | 73 | 188 | 40<br /> Miller | 71 | 157 | 25<br /> Freeman | 70 | 202 | 34<br /> OMalley | 65 | 163 | 48<br /> Jackson | 63 | 170 | 38<br /></pre></code><br />and I have a corresponding CLSQL view-class<br /><code><pre><br />(clsql:def-view-class btprospects ()<br /> ((name :db-kind :key<br /> :type (string 20)<br /> :initarg :name<br /> :accessor name)<br /> (height :type integer<br /> :initarg :height<br /> :accessor height)<br /> (weight :type integer<br /> :initarg :weight<br /> :accessor weight)<br /> (age :type integer<br /> :initarg :age<br /> :accessor age)))<br /></pre></code><br />The key is each class will have a <b>qcix</b> (query compatibility index) method that will be the overall membership function, doing some combination of individual membership functions of individual columns in the database. For our "big and tall" prospects we'll have a <b>tall</b> method for membership based on height, and a <b>heavy</b> method for membership based on weight. These functions will return a value from 0.0 to 1.0, with a linear scale from the 0 cutoff to the 1 cutoff. For overall membership we'll average the two other membership function.<br /><br /><code><pre><br />(defmethod tall ((prospect btprospects))<br /> (let ((raw-height (slot-value prospect 'height)))<br /> (cond ((<= raw-height 54) 0.0)<br /> ((>= raw-height 72) 1.0)<br /> (t (float (/ (- raw-height 54) (- 72 54)))))))<br /><br />(defmethod heavy ((prospect btprospects))<br /> (let ((raw-weight (slot-value prospect 'weight)))<br /> (cond ((<= raw-weight 182) 0.0)<br /> ((>= raw-weight 220) 1.0)<br /> (t (float (/ (- raw-weight 182) (- 220 182)))))))<br /><br />(defmethod qcix ((prospect btprospects))<br /> (float (/ (+ (tall prospect) (heavy prospect)) 2)))<br /><br />CL-USER> (clsql:map-query 'list #'tall [clsql:select 'btprospects])<br />(1.0 1.0 0.9444444 0.8888889 0.6111111 0.5)<br />CL-USER> (clsql:map-query 'list #'heavy [clsql:select 'btprospects])<br />(0.8684211 0.15789473 0.0 0.5263158 0.0 0.0)<br />CL-USER> (clsql:map-query 'list #'qcix [clsql:select 'btprospects])<br />(0.93421054 0.57894737 0.4722222 0.7076024 0.30555555 0.25)<br /></pre></code><br /><br />So far, so good. Now we want to select only some records that have a qcix over a particular threshold. We can use CLSQL's addition to loop:<br /><code><pre><br />(defun fuzzy-select (class-name cutoff)<br /> (loop for (rec) being the records in (clsql:select class-name)<br /> when (> (qcix rec) cutoff) collect rec))<br /><br />CL-USER> (fuzzy-select 'btprospects 0.5)<br />; Warning: database-query-result-set not implemented for database type POSTGRESQL-SOCKET.<br />; While executing: #<STANDARD-METHOD CLSQL-SYS:DATABASE-QUERY-RESULT-SET (T CLSQL-SYS:DATABASE)><br /></pre></code><br />Oops! Perhaps another reason not to delve into the innards of CLSQL. Plan B:<br /><code><pre><br />(defun fuzzy-select (class-name cutoff)<br /> (let ((result '()))<br /> (clsql:do-query ((rec) [clsql:select class-name])<br /> (when (>= (qcix rec) cutoff)<br /> (push rec result)))<br /> result))<br /><br />CL-USER> (mapcar #'name (fuzzy-select 'btprospects .5))<br />("Freeman" "Cassey" "Saunders")<br /></pre></code><br />So, starting with a CLSQL def-view-class we can externally add generic methods to support fuzzy queries.Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com0