[Chicken-users] possible lazy-let macro?

2005-11-16 Thread Daishi Kato
Hi,

this is not a chicken-specific question.

Does anyone know of any existence of a lazy-let macro,
which does the following?



(lazy-let ([a (get-a)][b (get-b)])
  (if (condition) a b))



(if (condition) (get-a) (get-b))

the above example is fairly easy, but how about this;



(lazy-let ([a (get-a)][b (get-b)])
  (if (condition)
  (begin (display a) a)
  (begin (display b) b)))



(if (condition)
(let ([a (get-a)]) (begin (display a) a))
(let ([b (get-b)]) (begin (display b) b)))

My question is whether this is a known situation and there exists
a macro for it, if no, is it feasible, or could this be
written in a macro in general way?

I hope my explanation does make sense.
Daishi


___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users


Re: [Chicken-users] possible lazy-let macro?

2005-11-16 Thread Thomas Chust

Am 16.11.2005, 09:18 Uhr, schrieb Daishi Kato <[EMAIL PROTECTED]>:


[...]
Does anyone know of any existence of a lazy-let macro,
which does the following?



(lazy-let ([a (get-a)][b (get-b)])
  (if (condition) a b))



(if (condition) (get-a) (get-b))
[...]


Hello,

I haven't seen any macro like this, yet. As a workaround, I would
maybe use promises like this (transformation of the above example)
  (let ((a (delay (get-a))) (b (delay (get-b
(if (condition) (force a) (force b)))

Of course this could fairly easily be automated using a non-hygienic
macro:
  (use match)

  (define-macro (lazy-let vars . body)
`(let ,(map (match-lambda ((var xpr) `(,var (delay ,xpr vars)
   ,@(map
   (let ((names (map car vars)))
 (rec (auto-force xpr)
   (cond
((list? xpr)
 (map auto-force xpr))
((and (symbol? xpr) (memq xpr names))
 `(force ,xpr))
(else
 xpr
   body)))

But it has the disadvantage, that you cannot transparently use set!
on the variables you declare with this form.

cu,
Thomas


___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users


Re: [Chicken-users] possible lazy-let macro?

2005-11-16 Thread Daishi Kato
Hi,

I have first considered the use of delay/force,
but it turns out that it is a little bit costly.
So, I want it to be done at the complie time.
This should probablly be called let-ahead instead of lazy-let.
Anyway, your macro example is helpful to me.
Let me think again.

Thanks,
Daishi

At Wed, 16 Nov 2005 15:06:17 -,
Thomas Chust wrote:
> 
> Am 16.11.2005, 09:18 Uhr, schrieb Daishi Kato <[EMAIL PROTECTED]>:
> 
> > [...]
> > Does anyone know of any existence of a lazy-let macro,
> > which does the following?
> >
> > 
> >
> > (lazy-let ([a (get-a)][b (get-b)])
> >   (if (condition) a b))
> >
> > 
> >
> > (if (condition) (get-a) (get-b))
> > [...]
> 
> Hello,
> 
> I haven't seen any macro like this, yet. As a workaround, I would
> maybe use promises like this (transformation of the above example)
>(let ((a (delay (get-a))) (b (delay (get-b
>  (if (condition) (force a) (force b)))
> 
> Of course this could fairly easily be automated using a non-hygienic
> macro:
>(use match)
> 
>(define-macro (lazy-let vars . body)
>  `(let ,(map (match-lambda ((var xpr) `(,var (delay ,xpr vars)
> ,@(map
> (let ((names (map car vars)))
>   (rec (auto-force xpr)
> (cond
>  ((list? xpr)
>   (map auto-force xpr))
>  ((and (symbol? xpr) (memq xpr names))
>   `(force ,xpr))
>  (else
>   xpr
> body)))
> 
> But it has the disadvantage, that you cannot transparently use set!
> on the variables you declare with this form.
> 
> cu,
> Thomas



___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users


Re: [Chicken-users] possible lazy-let macro?

2005-11-16 Thread Alex Shinn
At Wed, 16 Nov 2005 18:18:00 +0900, Daishi Kato wrote:
> 
> Does anyone know of any existence of a lazy-let macro,
> which does the following?
> 
> 
>
[...]
> 
> (lazy-let ([a (get-a)][b (get-b)])
>   (if (condition)
>   (begin (display a) a)
>   (begin (display b) b)))
> 
> 
> 
> (if (condition)
> (let ([a (get-a)]) (begin (display a) a))
> (let ([b (get-b)]) (begin (display b) b)))

This is sort of thing identifier-syntax was made for.  Basically,

(require-extension syntax-case)

(define-syntax foo (identifier-syntax bar))

replaces the symbol foo everywhere with bar (which can be any
expression).  There's also a form

(identifier-syntax (id1 tmpl1) ((set! id2 e2) tmpl2))

which let's you specify what happens when the variable is set (id1 and
id2 are dummy symbols for the sake of pattern matching).  Thus the
example given in the manual shows how to conceptually treat a symbol
as the car of a list:

(let ([x (list 0)])
  (define-syntax car-x
(identifier-syntax
  (id (car x))
  ((set! id e) (set-car! x e
  (let ([before car-x])
(set! car-x 1)
(list before car-x x)))
=> (0 1 (1))

Replacing a with (get-a) everywhere is thus trivial, but to only
compute (get-a) once we need to check if it has already been computed.
The implementation below stores the result in a temp variable (which
is also used to allow setting a):

;; using define-macro since syntax-case is so ugly
(define-macro (lazy-let params . body)
  (append (list 'lazy-let-with-temp-ids)
  (list (map (lambda (x) (append x (list (gensym params))
  body))

;; same lazy-let but of the form (lazy-let ((var val tmp) ...) . body)
;; so that we have a unique temp-var per var to store the values
(define-syntax lazy-let-with-temp-ids
  (syntax-rules ()
((lazy-let ((var val tmp) ...) . body)
 (let ((undef (list 'undef)))
   (let ((tmp undef) ...)
 (let-syntax
 ((var (identifier-syntax
(id (begin
  (if (eq? tmp undef)
(set! tmp val))
  tmp))
((set! id e) (set! tmp e
  ...)
   . body))

;; some definitions to use the example as-is:
(define (condition) (zero? (random 2)))
(define (get-a) (print "getting a") 'a-val)
(define (get-b) (print "getting b") 'b-val)

;; works as expected:
#;7> (lazy-let ([a (get-a)][b (get-b)])
   (if (condition)
   (begin (print a) a)
   (begin (print b) b)))
getting a
a-val
a-val

;; we can also set the lazy variables
#;8> (lazy-let ([a (get-a)][b (get-b)])
   (if (condition)
   (begin (print a) (set! a 'new-a-val) a)
   (begin (print b) (set! b 'new-b-val) b)))
getting a
a-val
new-a-val


-- 
Alex


___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users


Re: [Chicken-users] possible lazy-let macro?

2005-11-17 Thread Daishi Kato
Hi!

Nice to know of identifier-syntax.
It looks useful once you understand how it works.
I also found it is faster than delay/force,
the functions are different though.

Well, nonetheless, there still are some overheads,
assigning variables, and cheking them if they are new.
What I want is no overhead, so that I can basically
replace all let with lazy-let, unless there is a side-effect.

Here is my solution, I hope someone review it.

(use srfi-1)

(define-macro (lazy-let vars . body)
  (let loop ([names (map car vars)] [xpr (cons 'begin body)])
(cond
 ((list? xpr)
  (let* ([flat (map flatten xpr)]
 [cnt (lambda (x)
(length (filter (lambda (y) (memq x y)) flat)))])
(let-values ([(nms1 nms2)
  (partition (lambda (x) (> (cnt x) 1)) names)])
  (if (null? nms1)
  (map (lambda (x) (loop names x)) xpr)
  `(let ,(filter (lambda (x) (memq (car x) nms1)) vars)
 ,(map (lambda (x) (loop nms2 x)) xpr))
 ((and (symbol? xpr) (memq xpr names))
  (cadr (assq xpr vars)))
 (else
  xpr

Note:
- not tested much
- not sure if there are any restrictions
- macro expanding may be costly, could be improved?

Daishi


At Wed, 16 Nov 2005 20:11:15 -0600,
Alex Shinn wrote:
> 
> At Wed, 16 Nov 2005 18:18:00 +0900, Daishi Kato wrote:
> > 
> > Does anyone know of any existence of a lazy-let macro,
> > which does the following?
> > 
> > 
> >
> [...]
> > 
> > (lazy-let ([a (get-a)][b (get-b)])
> >   (if (condition)
> >   (begin (display a) a)
> >   (begin (display b) b)))
> > 
> > 
> > 
> > (if (condition)
> > (let ([a (get-a)]) (begin (display a) a))
> > (let ([b (get-b)]) (begin (display b) b)))
> 
> This is sort of thing identifier-syntax was made for.  Basically,
> 
> (require-extension syntax-case)
> 
> (define-syntax foo (identifier-syntax bar))
> 
> replaces the symbol foo everywhere with bar (which can be any
> expression).  There's also a form
> 
> (identifier-syntax (id1 tmpl1) ((set! id2 e2) tmpl2))
> 
> which let's you specify what happens when the variable is set (id1 and
> id2 are dummy symbols for the sake of pattern matching).  Thus the
> example given in the manual shows how to conceptually treat a symbol
> as the car of a list:
> 
> (let ([x (list 0)])
>   (define-syntax car-x
> (identifier-syntax
>   (id (car x))
>   ((set! id e) (set-car! x e
>   (let ([before car-x])
> (set! car-x 1)
> (list before car-x x)))
> => (0 1 (1))
> 
> Replacing a with (get-a) everywhere is thus trivial, but to only
> compute (get-a) once we need to check if it has already been computed.
> The implementation below stores the result in a temp variable (which
> is also used to allow setting a):
> 
> ;; using define-macro since syntax-case is so ugly
> (define-macro (lazy-let params . body)
>   (append (list 'lazy-let-with-temp-ids)
>   (list (map (lambda (x) (append x (list (gensym params))
>   body))
> 
> ;; same lazy-let but of the form (lazy-let ((var val tmp) ...) . body)
> ;; so that we have a unique temp-var per var to store the values
> (define-syntax lazy-let-with-temp-ids
>   (syntax-rules ()
> ((lazy-let ((var val tmp) ...) . body)
>  (let ((undef (list 'undef)))
>(let ((tmp undef) ...)
>  (let-syntax
>  ((var (identifier-syntax
> (id (begin
>   (if (eq? tmp undef)
> (set! tmp val))
>   tmp))
> ((set! id e) (set! tmp e
>   ...)
>. body))
> 
> ;; some definitions to use the example as-is:
> (define (condition) (zero? (random 2)))
> (define (get-a) (print "getting a") 'a-val)
> (define (get-b) (print "getting b") 'b-val)
> 
> ;; works as expected:
> #;7> (lazy-let ([a (get-a)][b (get-b)])
>(if (condition)
>(begin (print a) a)
>(begin (print b) b)))
> getting a
> a-val
> a-val
> 
> ;; we can also set the lazy variables
> #;8> (lazy-let ([a (get-a)][b (get-b)])
>(if (condition)
>  (begin (print a) (set! a 'new-a-val) a)
>  (begin (print b) (set! b 'new-b-val) b)))
> getting a
> a-val
> new-a-val
> 
> 
> -- 
> Alex


___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users


Re: [Chicken-users] possible lazy-let macro?

2005-11-17 Thread Thomas Chust

Am 17.11.2005, 12:17 Uhr, schrieb Daishi Kato <[EMAIL PROTECTED]>:


[...]
Here is my solution, I hope someone review it.

(use srfi-1)

(define-macro (lazy-let vars . body)
  (let loop ([names (map car vars)] [xpr (cons 'begin body)])
(cond
 ((list? xpr)
  (let* ([flat (map flatten xpr)]
 [cnt (lambda (x)
(length (filter (lambda (y) (memq x y)) flat)))])
(let-values ([(nms1 nms2)
  (partition (lambda (x) (> (cnt x) 1)) names)])
  (if (null? nms1)
  (map (lambda (x) (loop names x)) xpr)
  `(let ,(filter (lambda (x) (memq (car x) nms1)) vars)
 ,(map (lambda (x) (loop nms2 x)) xpr))
 ((and (symbol? xpr) (memq xpr names))
  (cadr (assq xpr vars)))
 (else
  xpr
[...]


Hello,

this may usually get the job done, but it fails to work in cases where
simple scanning for the occurrence of an identifier is not enough to
see whether the variable is referenced.

For example
  (lazy-let ((a (get-a)))
(let ((a (get-something-else)))
  (if (condition) a #f)))
always evaluates (get-a) even though it is never needed, because it
macroexpands into
  (begin
(let ((a (get-a)))
  (let ((a (get-something-else)))
(if (condition) a #f

A more academic example can also generate invalid code:
  (lazy-let ((a (get-a)))
(let ((a (get-something-else)))
  (do-something-unrelated)))
transforms into
  (begin
(let (((get-a) (get-something-else)))
  (do-something-unrelated)))

The same type of problem also occurs with this kind of expression:
  (lazy-let ((a (get-a)))
(if (condition) a 'a))
transforming into
  (begin
(let ((a (get-a)))
  (if (condition) a 'a)))
where we have evaluation in unnecessary cases again. Or even worse
with another academic example:
  (lazy-let ((a (get-a)))
'a)
transforming into
  (begin
'(get-a))

I cannot think of an easy way to fix this without having to make the
macro quite syntax-aware, though. Therefore I would rather suggest to
implement the whole thing for regular let forms in one of the compiler's
optimization passes ;)

cu,
Thomas


___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users


Re: [Chicken-users] possible lazy-let macro?

2005-11-17 Thread Daishi Kato
Hi,

Thanks for many examples!

At Thu, 17 Nov 2005 17:31:06 -,
Thomas Chust wrote:
> For example
>(lazy-let ((a (get-a)))
>  (let ((a (get-something-else)))
>(if (condition) a #f)))
> always evaluates (get-a) even though it is never needed, because it
> macroexpands into
>(begin
>  (let ((a (get-a)))
>(let ((a (get-something-else)))
>  (if (condition) a #f

This one is OK for me, it just did nothing.

> A more academic example can also generate invalid code:
>(lazy-let ((a (get-a)))
>  (let ((a (get-something-else)))
>(do-something-unrelated)))
> transforms into
>(begin
>  (let (((get-a) (get-something-else)))
>(do-something-unrelated)))

Oh, this is a big problem. I didn't know that.
I have expected such a limitation.

> The same type of problem also occurs with this kind of expression:
>(lazy-let ((a (get-a)))
>  (if (condition) a 'a))
> transforming into
>(begin
>  (let ((a (get-a)))
>(if (condition) a 'a)))

Yes, you are right. It also occurs with "a".

> where we have evaluation in unnecessary cases again. Or even worse
> with another academic example:
>(lazy-let ((a (get-a)))
>  'a)
> transforming into
>(begin
>  '(get-a))

Ah, how bad.

Another counter example could be made by combining with
other macros that do some dynamic code creation.

> I cannot think of an easy way to fix this without having to make the
> macro quite syntax-aware, though. Therefore I would rather suggest to
> implement the whole thing for regular let forms in one of the compiler's
> optimization passes ;)

I first thought this was an optimization issue in the complier,
but it might be not, because in some case there is a side effect,
while assigning let variables.  If it is possible for the compiler
to optimize with the normal let, I would prefer it.

Would it be worth improving the lazy-let macro
so that it understands at least let and quote forms?

Daishi


___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users


Re: [Chicken-users] possible lazy-let macro?

2005-11-17 Thread Thomas Chust

Am 17.11.2005, 23:18 Uhr, schrieb Daishi Kato <[EMAIL PROTECTED]>:


[...]

I cannot think of an easy way to fix this without having to make the
macro quite syntax-aware, though. Therefore I would rather suggest to
implement the whole thing for regular let forms in one of the compiler's
optimization passes ;)


I first thought this was an optimization issue in the complier,
but it might be not, because in some case there is a side effect,
while assigning let variables.  If it is possible for the compiler
to optimize with the normal let, I would prefer it.


Right, with sideeffects this can get quite nasty... So optimization by
default is not really possible :(



Would it be worth improving the lazy-let macro
so that it understands at least let and quote forms?
[...]


I think it is rather complicated. Maybe that's worth it -- but it depends
on what you want to use it for. If you are careful about what you are  
doing,

the present version can be useful as well...

By the way, another thing that is problematic except let constructs and
quotes are lambda expressions. If I write something like
  (let ((a (get-a)))
(lambda (blubb) (work-with a blubb)))
I do that because I want (get-a) to be called only once and the result  
stored
in a to be part of the anonymous procedure's environment. However,  
replacing

let with lazy let in the above snippet will cause it to macroexpand into
  (begin
(lambda (blubb)
  (work-with (get-a) blubb)))
On the other hand
  (lazy-let ((a (get-a)))
a
(lambda (blubb)
  (work-with a blubb)))
expands into
  (let ((a (get-a)))
(begin
  a
  (lambda (blubb)
(work-with a blubb
And so we encounter the philosophical question which one is the desired
behaviour and the technical question how to make it consistent :)

cu,
Thomas


___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users


Re: [Chicken-users] possible lazy-let macro?

2005-11-17 Thread Alex Shinn
At Fri, 18 Nov 2005 08:18:11 +0900, Daishi Kato wrote:
> 
> Would it be worth improving the lazy-let macro
> so that it understands at least let and quote forms?

Not let forms.  Analyzing subforms of a macro is called code-walking,
and the first thing you then need to do is sc-expand the body or
lazy-let won't play well with other macros.  At that point there are
no let's remaining, and everything is in terms of lambda.

But I think the problem you're trying to solve is in general
unsolvable.  Consider the form:

  (lazy-let ((a (get-a)))
(list (if (condition-1) a #f)
  (if (condition-2) a #f)))

If you expand this to

  (list (if (condition-1) (get-a) #f)
(if (condition-2) (get-a) #f))

then you may end up calling get-a twice.  However, if you compute the
value once outside the nearest enclosing form for all occurances:

  (let ((a (get-a)))
(list (if (condition-1) a #f)
  (if (condition-2) a #f)))

then you always compute get-a even when it may not be needed.

-- 
Alex


___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users


Re: [Chicken-users] possible lazy-let macro?

2005-11-17 Thread Daishi Kato
Thanks, Tohmas and Alex!

I think I understand that simple code-walking does not help,
in many cases, for example, a loop.

  (lazy-let ((a (get-a)))
(letrec ([b (lambda () (when (condition) (print a) (b)))])
  (b)))

OK, I would not need a general solution,
let me think about it for a while.

Thanks for comments, and any more comments are welcome.
Daishi

At Thu, 17 Nov 2005 19:19:46 -0600,
Alex Shinn wrote:
> 
> At Fri, 18 Nov 2005 08:18:11 +0900, Daishi Kato wrote:
> > 
> > Would it be worth improving the lazy-let macro
> > so that it understands at least let and quote forms?
> 
> Not let forms.  Analyzing subforms of a macro is called code-walking,
> and the first thing you then need to do is sc-expand the body or
> lazy-let won't play well with other macros.  At that point there are
> no let's remaining, and everything is in terms of lambda.
> 
> But I think the problem you're trying to solve is in general
> unsolvable.  Consider the form:
> 
>   (lazy-let ((a (get-a)))
> (list (if (condition-1) a #f)
>   (if (condition-2) a #f)))
> 
> If you expand this to
> 
>   (list (if (condition-1) (get-a) #f)
> (if (condition-2) (get-a) #f))
> 
> then you may end up calling get-a twice.  However, if you compute the
> value once outside the nearest enclosing form for all occurances:
> 
>   (let ((a (get-a)))
> (list (if (condition-1) a #f)
>   (if (condition-2) a #f)))
> 
> then you always compute get-a even when it may not be needed.
> 
> -- 
> Alex


___
Chicken-users mailing list
Chicken-users@nongnu.org
http://lists.nongnu.org/mailman/listinfo/chicken-users