[Chicken-users] possible lazy-let macro?
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?
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?
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?
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?
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?
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?
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?
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?
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?
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