From: Marco Antoniotti
Subject: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <c195d03b-9708-48ef-9a07-87e989823317@k13g2000hse.googlegroups.com>
In a bout of Haskell envy I did a few tricks with macros, closures and
SYMBOL-MACROLET and added true lazy calling to CL.  The result is (as
usual) in common-lisp.net.  Check out the CLAZY project.  Mailing
lists should be available as usual, but they are not yet reachable
from the web site.

The system should be ASDF-installable, YMMV.

Cheers
--
Marco

From: Mariano Montone
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <ac92d3a5-7f0a-4405-8314-376e5331e58d@8g2000hse.googlegroups.com>
Marco,
      here is some code you can take ideas from or publish directly.
See the examples for the difference between your approach and mine.

Cheers,
       Mariano

(defmacro with-gensyms (vars &rest body)
  `(let
       ,(mapcar (lambda (var) `(,var (gensym))) vars)
     ,@body))

(defmacro lazy-function (fname)
  (with-gensyms (value exists)
    `(multiple-value-bind (,value ,exists)
	 (gethash ',fname *lazy-funs*)
       (when (not ,exists)
	 (error (format nil "Lazy function ~A does not exists" ',fname)))
       ,value)))

;; Manual lazy evaluation through delay/force constructs
(defstruct promise
     value
     thunk)

(defun force (promise)
  (when (promise-thunk promise)
    (setf (promise-value promise) (funcall (promise-thunk promise))
	  (promise-thunk promise) nil))
  (promise-value promise))

(defun force (promise)
  (labels ((force (promise)
	     (when (promise-thunk promise)
	       (setf (promise-value promise) (funcall (promise-thunk
promise))
		     (promise-thunk promise) nil))
	     (promise-value promise)))
    (loop
	 with result = (force promise)
	 while (promise-p result) do (setq result (force result))
	 finally (return result))))

(defmacro delay (form)
  `(make-promise :thunk #'(lambda () ,form)))

(defvar *lazy-funs* (make-hash-table) "The lazy functions")

;; NOTE: we use the symbol-macrolet code-walker :)
(defmacro lazy-defun (name lambda-list &rest body)
  (let*
      ((bindings (make-hash-table))
       (body
	`(let
	     ,(mapcar
	       (lambda (arg)
		 (setf (gethash arg bindings) (gensym (string arg)))
		 `(,(gethash arg bindings) ,arg)) lambda-list)
	   (symbol-macrolet
	       ,(mapcar
		 (lambda (arg)
		   `(,arg (force ,(gethash arg bindings)))) lambda-list)
	     ,@body))))
    `(progn
       (defmacro ,name (&rest args)
	 `(funcall
	   (lambda ,',lambda-list
	     ,',body)
	   ,@(mapcar (lambda (farg) `(delay ,farg)) args)))
	 ;; We create a lambda that assumes its arguments to be lambdas
       (setf (gethash ',name *lazy-funs*)
	     (lambda ,lambda-list
	       ,body))
	 ',name)))

(defmacro lazy-lambda (lambda-list &rest body)
  (let
      ((bindings (make-hash-table)))
    `(lambda ,lambda-list
       (let
	   ,(mapcar
	     (lambda (arg)
	       (setf (gethash arg bindings) (gensym (string arg)))
	       `(,(gethash arg bindings) ,arg)) lambda-list)
	 (symbol-macrolet
	     ,(mapcar
	       (lambda (arg)
		 `(,arg (force ,(gethash arg bindings)))) lambda-list)
	   ,@body)))))

(defmacro lazy-let (bindings &rest body)
  (let
      ((binds (make-hash-table)))
    `(let
	 ,(mapcar
	   (lambda (binding)
	     (destructuring-bind (var value) binding
	       (setf (gethash var binds) (gensym (string var)))
	       `(,(gethash var binds) (delay ,value))))
	   bindings)
	 (symbol-macrolet
	     ,(mapcar
	       (lambda (binding)
		 (destructuring-bind (var value) binding
		   (declare (ignore value))
		   `(,var (force ,(gethash var binds)))))
	       bindings)
	   ,@body))))

(defmacro lazy-funcall (func &rest args)
  `(funcall ,func ,@(mapcar (lambda (farg) `(delay ,farg)) args)))

;; Simple currification
(defmacro curry (&rest body)
  (with-gensyms (x)
    `(lambda (,x) (,@body ,x))))
;; Example:
;; (funcall (curry + 1) 2)

;; Examples:
;; (lazy-defun lazy-if (predicate then else)
;;   (if predicate
;;       then
;;       else))
;; (lazy-if t 2 (print "hello")))
;; (lazy-if nil 2 (print "hello"))
;; (lazy-funcall (lazy-function lazy-if)  nil 2 (print "hello"))

;; This demonstrates that we evaluate only once. We are using a hash
table to cache evaluated
;; thunks.
;; (lazy-defun single-eval (lazy-arg)
;;   (print lazy-arg)
;;   (print lazy-arg))
;; (single-eval (progn (print "im printed once") 3))

;; Problems: don't know about efficiency. The interpreter gets
embedded in the function definition. That
;; makes debugging quite difficult.
From: Marco Antoniotti
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <cae4a90b-6197-4512-b83e-c6e03991fac7@z72g2000hsb.googlegroups.com>
Hi

your code works as expected, but your macrofication of the results of
LAZY-DEFUN do not interact well with LAZY-FUNCALL.

   (lazy-funcall 'lazy-if t 42 (loop))

results in an error.  You have to call

   (lazy-funcall (lazy-function lazy-if) 42 (loop))

It is true that it is nicer to write

   (lazy-if t 42 (loop))

rather than

   (lazy:call 'lazy-if t 42 (loop))

but macros and regular functions do not intermix well.  I had thought
of going the macro way myself, but decided against it.  I wanted to
preserve the ability to pass around lazy functional objects and to
keep the LAZY:CALL <=> FUNCALL symmetry as much as possible.  But
maybe macroizing is better aesthetically after all.

I like the caching machinery using hash tables and that could be
reused.

The rest is just mucking the lambda list.

Cheers
--
Marco

http://common-lisp.net/project/clazy




On Jun 10, 3:35 pm, Mariano Montone <··············@gmail.com> wrote:
> Marco,
>       here is some code you can take ideas from or publish directly.
> See the examples for the difference between your approach and mine.
>
> Cheers,
>        Mariano
>
> (defmacro with-gensyms (vars &rest body)
>   `(let
>        ,(mapcar (lambda (var) `(,var (gensym))) vars)
>      ,@body))
>
> (defmacro lazy-function (fname)
>   (with-gensyms (value exists)
>     `(multiple-value-bind (,value ,exists)
>          (gethash ',fname *lazy-funs*)
>        (when (not ,exists)
>          (error (format nil "Lazy function ~A does not exists" ',fname)))
>        ,value)))
>
> ;; Manual lazy evaluation through delay/force constructs
> (defstruct promise
>      value
>      thunk)
>
> (defun force (promise)
>   (when (promise-thunk promise)
>     (setf (promise-value promise) (funcall (promise-thunk promise))
>           (promise-thunk promise) nil))
>   (promise-value promise))
>
> (defun force (promise)
>   (labels ((force (promise)
>              (when (promise-thunk promise)
>                (setf (promise-value promise) (funcall (promise-thunk
> promise))
>                      (promise-thunk promise) nil))
>              (promise-value promise)))
>     (loop
>          with result = (force promise)
>          while (promise-p result) do (setq result (force result))
>          finally (return result))))
>
> (defmacro delay (form)
>   `(make-promise :thunk #'(lambda () ,form)))
>
> (defvar *lazy-funs* (make-hash-table) "The lazy functions")
>
> ;; NOTE: we use the symbol-macrolet code-walker :)
> (defmacro lazy-defun (name lambda-list &rest body)
>   (let*
>       ((bindings (make-hash-table))
>        (body
>         `(let
>              ,(mapcar
>                (lambda (arg)
>                  (setf (gethash arg bindings) (gensym (string arg)))
>                  `(,(gethash arg bindings) ,arg)) lambda-list)
>            (symbol-macrolet
>                ,(mapcar
>                  (lambda (arg)
>                    `(,arg (force ,(gethash arg bindings)))) lambda-list)
>              ,@body))))
>     `(progn
>        (defmacro ,name (&rest args)
>          `(funcall
>            (lambda ,',lambda-list
>              ,',body)
>            ,@(mapcar (lambda (farg) `(delay ,farg)) args)))
>          ;; We create a lambda that assumes its arguments to be lambdas
>        (setf (gethash ',name *lazy-funs*)
>              (lambda ,lambda-list
>                ,body))
>          ',name)))
>
> (defmacro lazy-lambda (lambda-list &rest body)
>   (let
>       ((bindings (make-hash-table)))
>     `(lambda ,lambda-list
>        (let
>            ,(mapcar
>              (lambda (arg)
>                (setf (gethash arg bindings) (gensym (string arg)))
>                `(,(gethash arg bindings) ,arg)) lambda-list)
>          (symbol-macrolet
>              ,(mapcar
>                (lambda (arg)
>                  `(,arg (force ,(gethash arg bindings)))) lambda-list)
>            ,@body)))))
>
> (defmacro lazy-let (bindings &rest body)
>   (let
>       ((binds (make-hash-table)))
>     `(let
>          ,(mapcar
>            (lambda (binding)
>              (destructuring-bind (var value) binding
>                (setf (gethash var binds) (gensym (string var)))
>                `(,(gethash var binds) (delay ,value))))
>            bindings)
>          (symbol-macrolet
>              ,(mapcar
>                (lambda (binding)
>                  (destructuring-bind (var value) binding
>                    (declare (ignore value))
>                    `(,var (force ,(gethash var binds)))))
>                bindings)
>            ,@body))))
>
> (defmacro lazy-funcall (func &rest args)
>   `(funcall ,func ,@(mapcar (lambda (farg) `(delay ,farg)) args)))
>
> ;; Simple currification
> (defmacro curry (&rest body)
>   (with-gensyms (x)
>     `(lambda (,x) (,@body ,x))))
> ;; Example:
> ;; (funcall (curry + 1) 2)
>
> ;; Examples:
> ;; (lazy-defun lazy-if (predicate then else)
> ;;   (if predicate
> ;;       then
> ;;       else))
> ;; (lazy-if t 2 (print "hello")))
> ;; (lazy-if nil 2 (print "hello"))
> ;; (lazy-funcall (lazy-function lazy-if)  nil 2 (print "hello"))
>
> ;; This demonstrates that we evaluate only once. We are using a hash
> table to cache evaluated
> ;; thunks.
> ;; (lazy-defun single-eval (lazy-arg)
> ;;   (print lazy-arg)
> ;;   (print lazy-arg))
> ;; (single-eval (progn (print "im printed once") 3))
>
> ;; Problems: don't know about efficiency. The interpreter gets
> embedded in the function definition. That
> ;; makes debugging quite difficult.
From: vanekl
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <g330as$um6$1@aioe.org>
Marco Antoniotti wrote:
> In a bout of Haskell envy I did a few tricks with macros, closures and
> SYMBOL-MACROLET and added true lazy calling to CL.  The result is (as
> usual) in common-lisp.net.  Check out the CLAZY project.  Mailing
> lists should be available as usual, but they are not yet reachable
> from the web site.
> 
> The system should be ASDF-installable, YMMV.
> 
> Cheers
> --
> Marco
> 

Interesting. Have you written a prime number generator with this?
Something like,

;; http://clj-me.blogspot.com/2008/06/primes.html
;; in clojure:
(def primes (lazy-cons 2 ((fn this[n]
   (let [potential-divisors (take-while #(<= (* % %) n) primes)]
     (if (some #(zero? (rem n %)) potential-divisors)
       (recur (inc n))
       (lazy-cons n (this (inc n)))))) 3)))
From: Marco Antoniotti
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <4d64c386-06ab-4c81-9cec-139a083a4a82@27g2000hsf.googlegroups.com>
On 15 Giu, 14:01, vanekl <·····@acd.net> wrote:
> Marco Antoniotti wrote:
> > In a bout of Haskell envy I did a few tricks with macros, closures and
> > SYMBOL-MACROLET and added true lazy calling to CL.  The result is (as
> > usual) in common-lisp.net.  Check out the CLAZY project.  Mailing
> > lists should be available as usual, but they are not yet reachable
> > from the web site.
>
> > The system should be ASDF-installable, YMMV.
>
> > Cheers
> > --
> > Marco
>
> Interesting. Have you written a prime number generator with this?
> Something like,
>
> ;;http://clj-me.blogspot.com/2008/06/primes.html
> ;; in clojure:
> (def primes (lazy-cons 2 ((fn this[n]
>    (let [potential-divisors (take-while #(<= (* % %) n) primes)]
>      (if (some #(zero? (rem n %)) potential-divisors)
>        (recur (inc n))
>        (lazy-cons n (this (inc n)))))) 3)))

No, but you can try and then report back :)

More seriously, I ma interested in what people would like wrt the
choice of having each lazy function macrofied or not (as it is now).

Cheers
--
Marco
From: Marco Antoniotti
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <eb86d490-9081-4d13-b4a0-a9992a65c43d@f63g2000hsf.googlegroups.com>
On Jun 15, 2:01 pm, vanekl <·····@acd.net> wrote:
> Marco Antoniotti wrote:
> > In a bout of Haskell envy I did a few tricks with macros, closures and
> > SYMBOL-MACROLET and added true lazy calling to CL.  The result is (as
> > usual) in common-lisp.net.  Check out the CLAZY project.  Mailing
> > lists should be available as usual, but they are not yet reachable
> > from the web site.
>
> > The system should be ASDF-installable, YMMV.
>
> > Cheers
> > --
> > Marco
>
> Interesting. Have you written a prime number generator with this?
> Something like,
>
> ;;http://clj-me.blogspot.com/2008/06/primes.html
> ;; in clojure:
> (def primes (lazy-cons 2 ((fn this[n]
>    (let [potential-divisors (take-while #(<= (* % %) n) primes)]
>      (if (some #(zero? (rem n %)) potential-divisors)
>        (recur (inc n))
>        (lazy-cons n (this (inc n)))))) 3)))

So, you have not figured out yet?

Cheers
--
Marco
From: Volkan YAZICI
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <6d910d90-5efe-41c1-aef3-40ac9988fee7@a70g2000hsh.googlegroups.com>
On Jun 15, 3:01 pm, vanekl <·····@acd.net> wrote:
> ;;http://clj-me.blogspot.com/2008/06/primes.html
> ;; in clojure:
> (def primes (lazy-cons 2 ((fn this[n]
>    (let [potential-divisors (take-while #(<= (* % %) n) primes)]
>      (if (some #(zero? (rem n %)) potential-divisors)
>        (recur (inc n))
>        (lazy-cons n (this (inc n)))))) 3)))

I don't think such calculations reflect the actual power behind lazy
evaluation. For instance, AFAIK, it's possible to construct similar
lazy streams using SERIES. While writing this, it makes me wonder that
what are the limits of lazy evaluation capabilities of SERIES package.
How far one can go using serie outputs produced by scanners supplied
by SERIES?
From: vanekl
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <g35rqi$bbg$1@aioe.org>
Volkan YAZICI wrote:
> On Jun 15, 3:01 pm, vanekl <·····@acd.net> wrote:
>> ;;http://clj-me.blogspot.com/2008/06/primes.html
>> ;; in clojure:
>> (def primes (lazy-cons 2 ((fn this[n]
>>    (let [potential-divisors (take-while #(<= (* % %) n) primes)]
>>      (if (some #(zero? (rem n %)) potential-divisors)
>>        (recur (inc n))
>>        (lazy-cons n (this (inc n)))))) 3)))
> 
> I don't think such calculations reflect the actual power behind lazy
> evaluation. For instance, AFAIK, it's possible to construct similar
> lazy streams using SERIES. While writing this, it makes me wonder that
> what are the limits of lazy evaluation capabilities of SERIES package.
> How far one can go using serie outputs produced by scanners supplied
> by SERIES?

Don't know what you mean by "actual power behind lazy evaluation."
This above algo puts off computation by using lazy constructs, which makes
it more efficient. This algo appears to be more efficient than John's
since it is not recomputing the factorization over and over again for
each new test of primality.

And if there's another package that has similar lazy constructs, so
what? I'm sure there is a lot of overlap between some libraries.
Doesn't mean the clazy library is any less useful.
From: John Thingstad
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <op.ucujlzajut4oq5@pandora.alfanett.no>
P� Mon, 16 Jun 2008 16:03:05 +0200, skrev vanekl <·····@acd.net>:

> This algo appears to be more efficient than John's
> since it is not recomputing the factorization over and over again for
> each new test of primality.

Appears being the operative word.
Goes back to the old saying that Lispers know the value of everything and  
the cost of nothing.

My algorithm can rest in the L1 cache. It doesn't need to access memory.  
(no consing)
Although there are fewer iterations each step is sufficiently expensive  
that you still loose.
For bigger primes there are more efficient algorithms also so that one is  
never optimal.

--------------
John Thingstad
From: vanekl
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <g36003$r5b$1@aioe.org>
John Thingstad wrote:
> P� Mon, 16 Jun 2008 16:03:05 +0200, skrev vanekl <·····@acd.net>:
> 
>> This algo appears to be more efficient than John's
>> since it is not recomputing the factorization over and over again for
>> each new test of primality.
> 
> Appears being the operative word.
> Goes back to the old saying that Lispers know the value of everything 
> and the cost of nothing.
> 
> My algorithm can rest in the L1 cache. It doesn't need to access memory. 
> (no consing)
> Although there are fewer iterations each step is sufficiently expensive 
> that you still loose.
> For bigger primes there are more efficient algorithms also so that one 
> is never optimal.
> 
> --------------
> John Thingstad


Agreed. From a high-level analysis, I like the lazy algo better.
Of course implementation details can totally destroy an algorithms
effectiveness, even if the algo has good big-O qualities. The virtual
machine ultimately has the last say, when running the algo in the
real world. However, this doesn't preclude the possibility, in the future,
for the lazy algo to come out faster if the virtual machine this is
run on has different optimizations.
From: John Thingstad
Subject: Re: CLAZY: lazy calling in Common Lisp
Date: 
Message-ID: <op.ucufogj5ut4oq5@pandora.alfanett.no>
P� Mon, 16 Jun 2008 13:10:26 +0200, skrev Volkan YAZICI  
<·············@gmail.com>:

> On Jun 15, 3:01 pm, vanekl <·····@acd.net> wrote:
>> ;;http://clj-me.blogspot.com/2008/06/primes.html
>> ;; in clojure:
>> (def primes (lazy-cons 2 ((fn this[n]
>>    (let [potential-divisors (take-while #(<= (* % %) n) primes)]
>>      (if (some #(zero? (rem n %)) potential-divisors)
>>        (recur (inc n))
>>        (lazy-cons n (this (inc n)))))) 3)))
>
> I don't think such calculations reflect the actual power behind lazy
> evaluation. For instance, AFAIK, it's possible to construct similar
> lazy streams using SERIES. While writing this, it makes me wonder that
> what are the limits of lazy evaluation capabilities of SERIES package.
> How far one can go using serie outputs produced by scanners supplied
> by SERIES?

to lazy for my taste at least.

I wrote a prime number finder and it completed in 0.047 s to count all  
primes from 1 to 100000.

(defun prime-p (n)
   (declare ((speed 3) (safety 0) (fixnum-safety 0))
            (fixnum n))
   (cond
     ((and (<= n 11) (member n '(2 3 5 7 11)) t)
     ((= (rem n 2) 0) nil)
     ((= (rem n 3) 0) nil)
     ((= (rem n 5) 0) nil)
     ((= (rem n 7) 0) nil)
     (t
         (loop for i fixnum from 11 to (isqrt n) by 2
            (when (= (rem n i) 0) (return-from prime-p nil))
         t)))

(defun count-primes (max)
    (check-type max (fixnum 1 #.most-positive-fixnum))
    (loop with count = 1
          for i from 1 to max do
          (when (prime-p i) (incf count))
          finally (return count)))

(time (test 100000))
0.047s (50 ns pr. prime!)
(time (test 1000000))
1.015s (twice as long pr prime)

This is also a naive use of aristofanes but avoids the overhead of  
remembering previous primes.
It isn't elegant, but is near optimally efficient for small numbers. (<  
1000000)

A few tips.
The loop unrolling saves time.
The (< n 11) bit saves time.
rem is 10 times faster than mod because mod conses a remainder ratio on  
the heap.

Of cource finding primes is only interesting for large prime numbers, but  
I only invoke the more complex algorithm based on fermant's little theorem  
on large numbers.

(Gauss observes that lim n-->inf pi(n) / (n / log n) = 1 where pi(n)  
returns the number of prime numbers upto n.
  Thus by aristophanes you would need O(n / log n) divisions to test  
primality for each prime.
  So counting/generating primes upto n would grow by O(n^2) which would  
quickly become impractical.)

--------------
John Thingstad