From: hyperstring.net ltd
Subject: Caching function results
Date: 
Message-ID: <1164141841.804827.122710@m73g2000cwd.googlegroups.com>
Ok

Here's some interesting code - I wanted to kinda throw it in for
discussion - what it does is to allow you to cache the results so that
after the first call to your function it never runs it again till the
program is restarted.

This is really useful if you generate web pages from sql tables that
are static in fact most of the time.  You can ofcourse use the

Paul
http://www.hyperstring.net

;
;
; ** Lisp Cache **
;
; The idea here is to cache the result of a function
; this would mean that if you have a fucntion that
; creates something but only needs to do it once
; you save re-running the funciton by just looking it up in a
; hash table such as if you are building a web-page from a database
;
(defvar *f-cache* (make-hash-table))

(defmacro cache-response (function-name &body body)
  "Cache the result of the function in body and store it as
function-name"
  `(progn
     (if (equal  (gethash (read-from-string ,function-name) *f-cache*)
nil)
       (progn
	 (setf (gethash (read-from-string ,function-name) *f-cache*) ,@body)))
     (gethash (read-from-string ,function-name) *f-cache*)))


(defmacro clear-cache (function-name)
  "remove the result from the cache"
  `(remhash (read-from-string ,function-name) *f-cache*))


(defun test-response ()
 "heres a test - if it always gives the same results then its working"
  (cache-response "testresponse"
    (let ((result nil))
     (dotimes (i-count (random 10))
       (setf result (concatenate 'string
				 result
				 "ehllo")))
     result)

))

From: Kaz Kylheku
Subject: Re: Caching function results
Date: 
Message-ID: <1164143823.303010.175390@h54g2000cwb.googlegroups.com>
hyperstring.net ltd wrote:
> Ok
>
> Here's some interesting code - I wanted to kinda throw it in for
> discussion - what it does is to allow you to cache the results so that
> after the first call to your function it never runs it again till the
> program is restarted.

You might want to learn about things like memoization and
LOAD-TIME-VALUE.

What you seem to be doing is memoizing functions with no arguments.

The LOAD-TIME-VALUE form can be used to write an expression whose value
is computed once when it is loaded.

> (defvar *f-cache* (make-hash-table))
>
> (defmacro cache-response (function-name &body body)
>   "Cache the result of the function in body and store it as
> function-name"
>   `(progn
>      (if (equal  (gethash (read-from-string ,function-name) *f-cache*)

This use of READ-FROM-STRING doesn't make sense. Why can't
FUNCTION-NAME just be a symbol?

With this move, you have ensured that the Lisp reader is invoked every
time the code is executed. Not a good move if you are looking to
optimize the execution of a function.

What if someone wants the function to have arguments? Your cache cannot
be applied.

> nil)

Don't use EQUAL to compare a value to NIL. The value already behaves
like a boolean, and so can be tested directly:

  (if (gethash ...) ...)

>        (progn
> 	 (setf (gethash (read-from-string ,function-name) *f-cache*) ,@body)))
>      (gethash (read-from-string ,function-name) *f-cache*)))

LOL

It's ironic that you are writing an expression cache, yet in its
implementation you fail to cache the result of READ-FROM-STRING, which
is applied three times to the same datum.

> (defmacro clear-cache (function-name)
>   "remove the result from the cache"
>   `(remhash (read-from-string ,function-name) *f-cache*))
>
>
> (defun test-response ()
>  "heres a test - if it always gives the same results then its working"
>   (cache-response "testresponse"
>     (let ((result nil))
>      (dotimes (i-count (random 10))
>        (setf result (concatenate 'string
> 				 result
> 				 "ehllo")))
>      result)
> ))

Try:

(let ((cached-value))
  (defun test-response ()
    (if cached-value
      cached-value
      (setf cached-value ... computation ...))))

You only need a hash to do memoization if there are function arguments.
Then each function that is memoized has its own hash, keyed on the
arguments.
From: hyperstring.net ltd
Subject: Re: Caching function results
Date: 
Message-ID: <1164145223.816480.130200@m7g2000cwm.googlegroups.com>
I'm really glad I posted - thanks folks. I'll do some of this stuff and
see how it fits what we do!

Paul
http://www.hyperstring.net
From: hyperstring.net ltd
Subject: Re: Caching function results
Date: 
Message-ID: <1164176976.344269.35080@m7g2000cwm.googlegroups.com>
OK


Kaz Kylheku wrote:
> hyperstring.net ltd wrote:
> > Ok
> >
> > Here's some interesting code - I wanted to kinda throw it in for
> > discussion - what it does is to allow you to cache the results so that
> > after the first call to your function it never runs it again till the
> > program is restarted.
>
> You might want to learn about things like memoization and
> LOAD-TIME-VALUE.
>
> What you seem to be doing is memoizing functions with no arguments.
>
> The LOAD-TIME-VALUE form can be used to write an expression whose value
> is computed once when it is loaded.
>
> > (defvar *f-cache* (make-hash-table))
> >
> > (defmacro cache-response (function-name &body body)
> >   "Cache the result of the function in body and store it as
> > function-name"
> >   `(progn
> >      (if (equal  (gethash (read-from-string ,function-name) *f-cache*)
>
> This use of READ-FROM-STRING doesn't make sense. Why can't
> FUNCTION-NAME just be a symbol?
>
Aha you're right - this is because I didn't think LOL anyway - you can
still pass arguments because this is a wrapper, function name just
stores a key to this function for the hash table.


> With this move, you have ensured that the Lisp reader is invoked every
> time the code is executed. Not a good move if you are looking to
> optimize the execution of a function.
>
> What if someone wants the function to have arguments? Your cache cannot
> be applied.
>
> > nil)
>
> Don't use EQUAL to compare a value to NIL. The value already behaves
> like a boolean, and so can be tested directly:
>
>   (if (gethash ...) ...)
>
> >        (progn
> > 	 (setf (gethash (read-from-string ,function-name) *f-cache*) ,@body)))
> >      (gethash (read-from-string ,function-name) *f-cache*)))
>
> LOL
>
> It's ironic that you are writing an expression cache, yet in its
> implementation you fail to cache the result of READ-FROM-STRING, which
> is applied three times to the same datum.
>
> > (defmacro clear-cache (function-name)
> >   "remove the result from the cache"
> >   `(remhash (read-from-string ,function-name) *f-cache*))
> >
> >
> > (defun test-response ()
> >  "heres a test - if it always gives the same results then its working"
> >   (cache-response "testresponse"
> >     (let ((result nil))
> >      (dotimes (i-count (random 10))
> >        (setf result (concatenate 'string
> > 				 result
> > 				 "ehllo")))
> >      result)
> > ))
>
> Try:
>
> (let ((cached-value))
>   (defun test-response ()
>     (if cached-value
>       cached-value
>       (setf cached-value ... computation ...))))
>
> You only need a hash to do memoization if there are function arguments.
> Then each function that is memoized has its own hash, keyed on the
> arguments.

True  - more research needed!
From: Pascal Costanza
Subject: Re: Caching function results
Date: 
Message-ID: <4sh7l4FvseahU1@mid.individual.net>
hyperstring.net ltd wrote:
> Ok
> 
> Here's some interesting code - I wanted to kinda throw it in for
> discussion - what it does is to allow you to cache the results so that
> after the first call to your function it never runs it again till the
> program is restarted.
[...]

It's not really what you are trying to achieve here. The code caches a 
function result based on a function name. The typical case is that you 
want to cache based on function parameters.

The code below can already be much more succinctly expressed like this:

(defun test-response ()
   (load-time-value
     (let (result)
       (dotimes ...)
       result)))

Load-time-value takes care of running some code only once in compiled code.

This is still weird, because the even more straightforward way is to 
just use a variable:

(defvar *test-response*
   (let (result)
     (dotimes ...)
     result))

(defun test-response () *test-response*)


A cache for a function where the caching is based on parameters is 
typically done like this:

(defvar *cache* (make-hash-table :test #'equal))

(defun some-function (&rest args)
   (or (gethash args *cache*)
       (setf (gethash args *cache*)
             ... the computation ...)))

This can be easily abstracted away in a macro:

(defmacro define-cached-function (name (&rest args) &body body)
   (let ((cache (gensym))
         (arg-list (gensym)))
     `(progn
        (defvar ,cache-name (make-hash-table :test #'equal))
        (defun ,name (&rest ,arg-list)
          (or (gethash ,arg-list ,cache)
              (setf (gethash ,arg-list ,cache)
                    (apply (lambda ,args ,@body) arg-list)))))))

It's probably better to check the second return value of gethash, but 
this is left as an exercise to the reader. ;)


Pascal

> ;
> ;
> ; ** Lisp Cache **
> ;
> ; The idea here is to cache the result of a function
> ; this would mean that if you have a fucntion that
> ; creates something but only needs to do it once
> ; you save re-running the funciton by just looking it up in a
> ; hash table such as if you are building a web-page from a database
> ;
> (defvar *f-cache* (make-hash-table))
> 
> (defmacro cache-response (function-name &body body)
>   "Cache the result of the function in body and store it as
> function-name"
>   `(progn
>      (if (equal  (gethash (read-from-string ,function-name) *f-cache*)
> nil)
>        (progn
> 	 (setf (gethash (read-from-string ,function-name) *f-cache*) ,@body)))
>      (gethash (read-from-string ,function-name) *f-cache*)))
> 
> 
> (defmacro clear-cache (function-name)
>   "remove the result from the cache"
>   `(remhash (read-from-string ,function-name) *f-cache*))
> 
> 
> (defun test-response ()
>  "heres a test - if it always gives the same results then its working"
>   (cache-response "testresponse"
>     (let ((result nil))
>      (dotimes (i-count (random 10))
>        (setf result (concatenate 'string
> 				 result
> 				 "ehllo")))
>      result)
> 
> ))
> 


-- 
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
From: Lars Rune Nøstdal
Subject: Re: Caching function results
Date: 
Message-ID: <pan.2006.11.21.22.00.24.251466@gmail.com>
On Tue, 21 Nov 2006 12:44:01 -0800, hyperstring.net ltd wrote:

> Ok
> 
> Here's some interesting code - I wanted to kinda throw it in for
> discussion - what it does is to allow you to cache the results so that
> after the first call to your function it never runs it again till the
> program is restarted.

"On Lisp" mentions something like that:
  http://www.bookshelf.jp/texi/onlisp/onlisp_6.html#SEC40

`file-write-date' is nice for a somewhat similar situation when working
with files by the way. 


(defmacro aequal ((result-symbol &optional (test 'equal))
                  orig possibly-new 
                  &body then-else)
  "Anaphoric equal-thing?"
  `(let ((,result-symbol ,possibly-new))
     (if (,test ,orig ,result-symbol)
         ,(first then-else)
         ,(second then-else))))
         
         
(defun createFileWatcher (file)
  (let ((last-change (file-write-date file)))
    (lambda ()
      (aequal (change) last-change (file-write-date file)
        nil
        (setf last-change change)))))


cl-user> (defparameter *test.txt* (createFileWatcher "/home/lars/test.txt"))
*test.txt*
cl-user> (funcall *test.txt*)
nil
cl-user> (sb-ext:run-program "/bin/touch" `("/home/lars/test.txt") :output *standard-output*)
#<sb-impl::process :exited 0>
cl-user> (funcall *test.txt*)
3373134021
cl-user> (funcall *test.txt*)
nil
cl-user> (sb-ext:run-program "/bin/touch" `("/home/lars/test.txt") :output *standard-output*)
#<sb-impl::process :exited 0>
cl-user> (funcall *test.txt*)
3373134026
cl-user> (funcall *test.txt*)
nil


..or something like that.

-- 
Lars Rune Nøstdal
http://lars.nostdal.org/
From: hyperstring.net ltd
Subject: Re: Caching function results
Date: 
Message-ID: <1164177319.878069.295870@h48g2000cwc.googlegroups.com>
Lars Rune Nøstdal wrote:
> On Tue, 21 Nov 2006 12:44:01 -0800, hyperstring.net ltd wrote:
>
> > Ok
> >
> > Here's some interesting code - I wanted to kinda throw it in for
> > discussion - what it does is to allow you to cache the results so that
> > after the first call to your function it never runs it again till the
> > program is restarted.
>
> "On Lisp" mentions something like that:
>   http://www.bookshelf.jp/texi/onlisp/onlisp_6.html#SEC40
>
> `file-write-date' is nice for a somewhat similar situation when working
> with files by the way.
>
>
> (defmacro aequal ((result-symbol &optional (test 'equal))
>                   orig possibly-new
>                   &body then-else)
>   "Anaphoric equal-thing?"
>   `(let ((,result-symbol ,possibly-new))
>      (if (,test ,orig ,result-symbol)
>          ,(first then-else)
>          ,(second then-else))))
>
>
> (defun createFileWatcher (file)
>   (let ((last-change (file-write-date file)))
>     (lambda ()
>       (aequal (change) last-change (file-write-date file)
>         nil
>         (setf last-change change)))))
>
>
> cl-user> (defparameter *test.txt* (createFileWatcher "/home/lars/test.txt"))
> *test.txt*
> cl-user> (funcall *test.txt*)
> nil
> cl-user> (sb-ext:run-program "/bin/touch" `("/home/lars/test.txt") :output *standard-output*)
> #<sb-impl::process :exited 0>
> cl-user> (funcall *test.txt*)
> 3373134021
> cl-user> (funcall *test.txt*)
> nil
> cl-user> (sb-ext:run-program "/bin/touch" `("/home/lars/test.txt") :output *standard-output*)
> #<sb-impl::process :exited 0>
> cl-user> (funcall *test.txt*)
> 3373134026
> cl-user> (funcall *test.txt*)
> nil
>
>
> ..or something like that.
>
> --
> Lars Rune Nøstdal
> http://lars.nostdal.org/

Hi Lars

yeah this does what I need! Very nicely!

(defun memoize (fn)
  (let ((cache (make-hash-table :test #'equal)))
    #'(lambda (&rest args)
	(multiple-value-bind (val win) (gethash args cache)
	  (if win
	      val
	    (setf (gethash args cache)
		  (apply fn args)))))))

Paul