Big change: lexical scoping
This introduces lexical scoping of for clauses. See README.md
This commit is contained in:
		
							parent
							
								
									769553832b
								
							
						
					
					
						commit
						2c323be362
					
				
					 5 changed files with 132 additions and 63 deletions
				
			
		
							
								
								
									
										107
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										107
									
								
								README.md
									
										
									
									
									
								
							|  | @ -1,17 +1,6 @@ | |||
| # goof-loop - a scheme looping facility | ||||
| 
 | ||||
| goof-loops aims to be an amalgamation of the racket for loops and Alex Shinn's (chibi-loop). We are many that found racket's for loops a breeze of fresh air, but in the end their most general forms (for/fold and for/foldr) are kinda odd to work with. If you choose not to use those general for loops, you cannot express arbitrary transformations, like say a fibonacci sequence, since for clauses cannot reference eachother. goof-loop tries to fix this: | ||||
| 
 | ||||
| ``` | ||||
| (loop ((:for a (in 0 b)) | ||||
|        (:for b (in 1 (+ a b))) | ||||
|        (:for count (up-from 0 (to 1000))) | ||||
|        (:acc acc (listing b))) | ||||
|   => acc | ||||
|   (display b) (newline)) | ||||
| ``` | ||||
| 
 | ||||
| The above example will display and accumulate the 1000 first fibonacci numbers. Doing the same thing in racket requires you to manually handle all the state in fold-variables using for/fold. It is a simple example, but proves the usefulness of goof-loop. | ||||
| goof-loops aims to be an amalgamation of the racket for loops and Alex Shinn's (chibi-loop). We are many that found racket's for loops a breeze of fresh air, but in the end their most general forms (for/fold and for/foldr) are kinda odd to work with. If you choose not to use those general for loops, you cannot express arbitrary transformations, like say a fibonacci sequence, since for clauses cannot reference eachother. goof-loop tries to fix this. | ||||
| 
 | ||||
| Compared to foof-loop, some things are added. Apart from minor syntactic changes, subloops are supported. The best way is to show: | ||||
| 
 | ||||
|  | @ -26,18 +15,6 @@ Compared to foof-loop, some things are added. Apart from minor syntactic changes | |||
| 
 | ||||
| This will sum all the sublists of lst and produce the result 21. Any :when, :unless, :break, :final, or :subloop clause will break out a subloop if any subsequent for clauses are found. | ||||
| 
 | ||||
| Accumulators can be in any of the loop's stages: | ||||
| 
 | ||||
| ``` | ||||
| (loop ((:for a (in-list '(1 2 3))) | ||||
|        (:acc aa (summing a)) | ||||
|        :subloop | ||||
|        (:for b (up-from a (to (+ a 2)))) | ||||
|        (:acc ab (listing b))) | ||||
|   => (values aa ab)) | ||||
| ;; => (values 6  (1 2 2 3 3 4)) | ||||
| ``` | ||||
| 
 | ||||
| ## Beta warning | ||||
| 
 | ||||
| This is beta quality software, and some minor details are likely to change. I have gotten most kinks worked out though. | ||||
|  | @ -51,6 +28,11 @@ It is written in a weird markdown/xml chimaera. You can find it in documentation | |||
| 
 | ||||
| ## Differences from foof-loop | ||||
| 
 | ||||
| ### lexical | ||||
| 
 | ||||
| foof-loop has a lot of code movement going on, and it can be hard to understand exactly where things end up. goof employs a more strict lexical hierarchy. The following is not possible in (chibi loop): | ||||
| d into only after the above clauses have been evaluated. | ||||
| 
 | ||||
| ### syntactical | ||||
| 
 | ||||
| for-clauses are split into :for and :acc clauses. This is because the addition of subloops means we have to treat accumulators differently. | ||||
|  | @ -90,8 +72,20 @@ Due to clause reordering, positional updates are not supported. If you want to u | |||
|  guard was a procedure, but now it is an expression. | ||||
| 
 | ||||
|     (with var 10 (- var 1) negative?) => (:for var (in 10 (- var 10) (negative? var))) | ||||
| ## Features | ||||
| 
 | ||||
| ### similarities | ||||
| ### Lexical order of clauses | ||||
| 
 | ||||
|     (loop ((:for a (in-list 1 2 3) | ||||
|            (:bind b (expensive-operation1 a)) | ||||
|            (:when (test? b)) | ||||
|            (:bind c (expensive-operation2 b)) | ||||
|            (:when test2? c) | ||||
|            (:acc acc (listing c)))) | ||||
|       => acc) | ||||
|        | ||||
| 
 | ||||
| ### Loop naming to make it "fold right" | ||||
| 
 | ||||
| You can of course still have a larger control of when to loop by naming your loop: | ||||
| 
 | ||||
|  | @ -105,7 +99,7 @@ You can of course still have a larger control of when to loop by naming your loo | |||
| ;; => (-1 4 -9 16 -25 36 -49 64 -81 100) | ||||
| ``` | ||||
| 
 | ||||
| Named updates also work. | ||||
| ### Named updates | ||||
| 
 | ||||
| ``` | ||||
| ;; Shamelessly stolen from Taylor Campbell's foof-loop documentation | ||||
|  | @ -123,6 +117,61 @@ Named updates also work. | |||
| ;; => (values (1 3 5) (2 4)) | ||||
| ``` | ||||
| 
 | ||||
| ### Exposing loop variables | ||||
| 
 | ||||
| The iterator protocol allows exposing the loop variables | ||||
| 
 | ||||
| ``` | ||||
| (loop name ((:for elt pair (in-list '(1 2 3)))) | ||||
|   => '() | ||||
|   (if (null? (cdr pair)) | ||||
|       (list elt) | ||||
|       (cons* elt ': (name)))) | ||||
|        | ||||
| ;; => (1 : 2 : 3) | ||||
| ``` | ||||
| 
 | ||||
| ### :final is context sensitive (compared to Racket's #:final) | ||||
| 
 | ||||
| ``` scheme | ||||
| 
 | ||||
| (loop ((:for elt (in-list '( 1 2 3))) | ||||
|        :final (= elt 2) | ||||
|        (:for ab (in-list '(a b))) | ||||
|        (:acc acc (listing (cons elt ab))) | ||||
|   => acc)) | ||||
|    | ||||
| ;; => ((1 . a) (1 . b) (2 . a) (2 . b)) | ||||
| ``` | ||||
| 
 | ||||
| The racket counterpart would result in ((1 . a) (1 . b) (2 . a)) | ||||
| 
 | ||||
| ### for-clauses can refer to eachother | ||||
| 
 | ||||
| The iterative fibonacci loop is weird to write using for/fold. goof fixes this: | ||||
| ``` scheme | ||||
| (loop ((:for a (in 0 b)) | ||||
|        (:for b (in 1 (+ a b))) | ||||
|        (:for count (up-from 0 (to 100))) | ||||
|        (:acc acc (listing b))) | ||||
|   => acc | ||||
|   (display b) (newline)) | ||||
| ``` | ||||
| ### Accumulators and arbitrary code can be placed in subloops | ||||
| 
 | ||||
| ``` scheme | ||||
| (loop ((:for a (in-list '(1 2 3))) | ||||
|        (:acc aa (summing a)) | ||||
|        (:do (display "Entering subloop!") (newline)) | ||||
|        :subloop | ||||
|        (:for b (up-from a (:to (+ a 2)))) | ||||
|        (:acc ab (listing b))) | ||||
|   => (values aa ab)) | ||||
| ;; => 6 (1 2 2 3 3 4) | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ### Simple forms | ||||
| I also provide simplified forms for many common operations. Omitting :for is allowed, and :acc clauses are not allowed. | ||||
| 
 | ||||
|  | @ -161,9 +210,9 @@ I also provide simplified forms for many common operations. Omitting :for is all | |||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| ### Speed | ||||
| ## Speed | ||||
| 
 | ||||
| Speed is good. Despite the rather involved expansion you can see in the documentation, due to dead-code elimination, the actual expansion shows some good code: | ||||
| Speed is good. Despite the rather involved expansion you can see in the documentation, due to inlining and dead-code elimination, the actual expansion shows some good code: | ||||
| 
 | ||||
| ``` | ||||
| > ,opt (loop ((:for a (in-list '(1 2 3 4))) | ||||
|  | @ -220,8 +269,6 @@ Tests! | |||
| 
 | ||||
| Finish documentation. | ||||
| 
 | ||||
| add generator support for all provided iterators | ||||
| 
 | ||||
| ## foof, what a guy | ||||
| 
 | ||||
| I have previously expressed some admiration for Alex and I will do it again. The source of chibi loop is extremely elegant, and all but the hairiest part is written in syntax-rules. Not only has he written my two favourite SRFIs, his input in all the other discussions I have seen is always on-point, pragmatic and generally fantastic. He neither knows of this project, nor embraces it in any way. Y'all should go look at the source of (chibi loop) though. | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ | |||
| 
 | ||||
| (define-aux-syntaxes | ||||
|   ;; Auxiliary syntax for the loop clauses | ||||
|   :when :unless :break :final :bind :subloop :for :acc | ||||
|   :when :unless :break :final :bind :do :subloop :for :acc | ||||
|   ;; Auxiliary syntax for the iterators. | ||||
|   :gen | ||||
|   ;; auxiliary auxiliary syntax | ||||
|  | @ -121,7 +121,7 @@ | |||
| 
 | ||||
| ;; cl sorts all the clauses into subloops and positions everything where it should be. | ||||
| (define-syntax cl | ||||
|   (syntax-rules (=> :for :acc :when :unless :break :final :bind :subloop) | ||||
|   (syntax-rules (=> :for :acc :when :unless :break :final :do :bind :subloop) | ||||
|     ((_ orig name l a v c r f ff user () => expr . body) | ||||
|      (emit orig name l a v c r f ff user expr . body)) | ||||
|     ((_ orig name l a v c r f ff user () . body) | ||||
|  | @ -143,8 +143,12 @@ | |||
|      (cl orig name l a v c r f ff ((cur-ub ... (:break expr)) . ub-rest) (clauses ...) . body)) | ||||
|     ;; user final | ||||
|     ;; This pushes a #t to the user when expression, thus forcing a subloop if a for-clause is found afterwards. | ||||
|     ((_ orig name l a v c r f ff ((cur-uw ...) . uw-rest)((:final expr) clauses ...) . body) | ||||
|      (cl orig name l a v c r f ff ((cur-uw ... (:final expr)) . uw-rest) (clauses ...) . body)) | ||||
|     ((_ orig name l a v c r f ff user ((:final expr) clauses ...) . body) | ||||
|      (final :acc ((_) (expr)) cl-next/acc orig name l a v c r f ff user (clauses ...) . body)) | ||||
| 
 | ||||
|     ;; User do - sideffecting stuff. | ||||
|     ((_ orig name l a v c r f ff ((cur-uw ...) . uw-rest) ((:do expr ...) clauses ...) . body) | ||||
|      (cl orig name l a v c r f ff ((cur-uw ... (:do expr ...)) . uw-rest) (clauses ...) . body)) | ||||
|      | ||||
|     ;; Explicit subloop. Shorthand for (:when #t) | ||||
|     ((_ orig name l a v c r f ff ((cur-uw ...) . uw-rest) (:subloop clauses ...) . body) | ||||
|  | @ -164,9 +168,9 @@ | |||
| 
 | ||||
|     ;; ERROR HANDLING? | ||||
|     ((_ orig name l a v c r f ff user (clause . rest) . body) | ||||
|      (syntax-error "Invalid clause in loop" clause orig)) | ||||
|      (syntax-error "Invalid clause in loop" clause orig)))) | ||||
| 
 | ||||
| 
 | ||||
|     )) | ||||
| 
 | ||||
| 
 | ||||
| ;; HOLY CODE-DUPLICATION-BATMAN! | ||||
|  | @ -184,15 +188,15 @@ | |||
|         checks | ||||
|         ((refs ...)) | ||||
|         (finals ...) | ||||
|         ff ul uw ((cur-ub ...) . ub-rest) uf clauses . body) | ||||
|         ff ((cur-ub ...) . ub-rest) clauses . body) | ||||
|      (cl orig name | ||||
|          ((lets ... new-lets ...)) | ||||
|          ((accs ... (accvar accinit accupdate) ...)) | ||||
|          ((accs ... (accvar accinit accvar) ...)) | ||||
|          vars | ||||
|          checks | ||||
|          ((refs ... new-refs ...)) | ||||
|          (finals ... new-finals ...) | ||||
|          ff ul uw ((cur-ub ... new-checks ...) . ub-rest) uf clauses . body)) | ||||
|          ff ((cur-ub ... (:break  new-checks) ... (:bind (accvar accupdate) ...)) . ub-rest) clauses . body)) | ||||
|     ;; We have ONE subloop! | ||||
|     ((_ (new-lets ...) ((accvar accinit accupdate) ...) (new-checks ...) (new-refs ...) (new-finals ...) | ||||
|         orig name | ||||
|  | @ -202,15 +206,15 @@ | |||
|         checks | ||||
|         ((refs ...) . refs-rest) | ||||
|         (finals ...) | ||||
|         ff ul uw ((cur-ub ...) . ub-rest) uf clauses . body) | ||||
|         ff ((cur-ub ...) . ub-rest) clauses . body) | ||||
|      (cl orig name | ||||
|          (lets ... (outermost-lets ... new-lets ...)) | ||||
|          ((accs ... (accvar accvar accupdate) ...) ((oldacc oldinit oldupdate) ... (accvar accinit accvar) ...)) | ||||
|          ((accs ... (accvar accvar accvar) ...) ((oldacc oldinit oldupdate) ... (accvar accinit accvar) ...)) | ||||
|          vars | ||||
|          checks | ||||
|          ((refs ... new-refs ...) . refs-rest) | ||||
|          (finals ... new-finals ...) | ||||
|          ff ul uw ((cur-ub ... new-checks ...) . ub-rest) uf clauses . body)) | ||||
|          ff ((cur-ub ... (:break  new-checks) ... (:bind (accvar accupdate) ...)) . ub-rest) clauses . body)) | ||||
|     ;; We have several subloops! | ||||
|     ((_ (new-lets ...) ((accvar accinit accupdate) ...) (new-checks ...) (new-refs ...) (new-finals ...) | ||||
|         orig name | ||||
|  | @ -220,16 +224,16 @@ | |||
|         checks | ||||
|         ((refs ...) . refs-rest) | ||||
|         (finals ...) | ||||
|         ff ul uw ((cur-ub ...) . ub-rest) uf clauses . body) | ||||
|         ff ((cur-ub ...) . ub-rest) clauses . body) | ||||
|      (cl orig name | ||||
|          (lets ... (outermost-lets ... new-lets ...)) | ||||
|          ((accs ... (accvar accvar accupdate) ...) ((oldacc oldinit oldupdate) ... (accvar accvar accvar) ...) ... | ||||
|          ((accs ... (accvar accvar accvar) ...) ((oldacc oldinit oldupdate) ... (accvar accvar accvar) ...) ... | ||||
|           ((oldestacc oldestinit oldestupdate) ... (accvar accinit accvar) ...)) | ||||
|          vars | ||||
|          checks | ||||
|          ((refs ... new-refs ...) . refs-rest) | ||||
|          (finals ... new-finals ...) | ||||
|          ff ul uw ((cur-ub ... new-checks ...) . ub-rest) uf clauses . body)))) | ||||
|          ff ((cur-ub ... (:break  new-checks) ... (:bind (accvar accupdate) ...)) . ub-rest) clauses . body)))) | ||||
| 
 | ||||
| ;; Integrating for clauses is not as involved, since they only want to be introduced into the current | ||||
| ;; loop. Any propagation of for finalizers (ff) is done by push-new-subloop | ||||
|  | @ -259,15 +263,13 @@ | |||
| 
 | ||||
| 
 | ||||
| (define-syntax user | ||||
|   (syntax-rules (:when :bind :break :final :nop) | ||||
|   (syntax-rules (:when :bind :break :do :nop) | ||||
|     ((_ final-expr next outer () body ...) | ||||
|      (begin body ...)) | ||||
| 
 | ||||
|     ((_ f n o (:nop . rest) . body) | ||||
|      (user f n o rest . body)) | ||||
| 
 | ||||
|     ((_ f n o ((:bind pairs ...) . rest) . body) | ||||
|      (let (pairs ...) | ||||
|      (ref-let (pairs ...) | ||||
|        (user f n o rest . body))) | ||||
|     ((_ f n o ((:when test) . rest) . body) | ||||
|      (cond | ||||
|  | @ -277,8 +279,12 @@ | |||
|      (cond | ||||
|       (expr final-expr ...) | ||||
|       (else (user (final-expr ...) n o rest . body)))) | ||||
|     ((_ f n o ((:do expr ...) . rest) . body) | ||||
|      (begin | ||||
|        expr ... | ||||
|        (user f n o rest . body))))) | ||||
| 
 | ||||
| 
 | ||||
|     )) | ||||
| 
 | ||||
| ;; If there are no subloops, we emit to the simple case | ||||
| (define-syntax emit | ||||
|  | @ -413,6 +419,7 @@ | |||
|                       (user (ff-cur ... ff-above ... final) | ||||
|                             (intermediate-loop accstep ... step ...) | ||||
|                             #f | ||||
|                             (us ...) | ||||
|                             (emit-many/rest orig | ||||
|                                             name | ||||
|                                             (intermediate-loop accstep ... step ...) | ||||
|  | @ -427,14 +434,14 @@ | |||
|                                             . body))))))))) | ||||
| 
 | ||||
| (define-syntax forify | ||||
| (syntax-rules (%acc) | ||||
|   ((_ orig name () ((%acc . acc-rest) . argsrest) . body) | ||||
|    (forify* orig name () ((:for ensure-once (up-from 0 1)) (%acc . acc-rest) . argsrest) . body)) | ||||
|   ((_ . rest) | ||||
|    (forify* . rest)))) | ||||
|   (syntax-rules (%acc) | ||||
|     ((_ orig name () ((%acc . acc-rest) . argsrest) . body) | ||||
|      (forify* orig name () ((:for ensure-once (up-from 0 1)) (%acc . acc-rest) . argsrest) . body)) | ||||
|     ((_ . rest) | ||||
|      (forify* . rest)))) | ||||
| 
 | ||||
| (define-syntax forify* | ||||
|   (syntax-rules (:for :acc :when :unless :break :final :subloop :let :let* %acc) | ||||
|   (syntax-rules (:for :acc :when :unless :break :final :subloop :bind :do %acc) | ||||
|     ((_ o n done-clauses () . body) | ||||
|      (%loop o n done-clauses . body)) | ||||
|     ((_      o n (s ...) ((:for c-rest ...) clauses ...) . body) | ||||
|  | @ -447,12 +454,12 @@ | |||
|      (forify* o n (s ... (:break expr)) (clauses ...) . body)) | ||||
|     ((_      o n (s ...) ((:final expr) clauses ...) . body) | ||||
|      (forify* o n (s ... (:final expr)) (clauses ...) . body)) | ||||
|     ((_      o n (s ...) ((:do expr ...) clauses ...) . body) | ||||
|      (forify* o n (s ... (:do expr ...)) (clauses ...) . body)) | ||||
|     ((_      o n (s ...) (:subloop clauses ...) . body) | ||||
|      (forify* o n (s ... :subloop) (clauses ...) . body)) | ||||
|     ((_      o n (s ...) ((:let id id* ... expr) clauses ...) . body) | ||||
|      (forify* o n (s ... (:let id id* ... expr)) (clauses ...) . body)) | ||||
|     ((_      o n (s ...) ((:let* id id* ... expr) clauses ...) . body) | ||||
|      (forify* o n (s ... (:let* id id* ... expr)) (clauses ...) . body)) | ||||
|     ((_      o n (s ...) ((:bind pairs ...) clauses ...) . body) | ||||
|      (forify* o n (s ... (:bind pairs ...)) (clauses ...) . body)) | ||||
|     ((_      o n (s ...) ((%acc c-rest ...) clauses ...) . body) | ||||
|      (forify* o n (s ... (:acc c-rest ...)) (clauses ...) . body)) | ||||
|     ((_      o n (s ...) ((:acc c-rest ...) clauses ...) . body) | ||||
|  |  | |||
							
								
								
									
										2
									
								
								goof.scm
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								goof.scm
									
										
									
									
									
								
							|  | @ -47,7 +47,7 @@ | |||
|              loop/or | ||||
|              loop/list/parallel | ||||
| 
 | ||||
|              :when :unless :break :final :bind :subloop :for :acc | ||||
|              :when :unless :break :final :bind :subloop :do :for :acc | ||||
|              :length :fill | ||||
|              :to :by | ||||
| 
 | ||||
|  |  | |||
|  | @ -508,6 +508,21 @@ | |||
|            ((var var)) | ||||
|            . rest)))) | ||||
| 
 | ||||
| 
 | ||||
| ;; this is an internal "accumulator". It is used for final tests | ||||
| ;; :final in goof differs from in racket. It is lexical, meaning it | ||||
| ;; is tested where it is placed in the clauses, and any subloop is | ||||
| ;; executed completely. | ||||
| (define-syntax final | ||||
|   (syntax-rules (:acc) | ||||
|     ((_ :acc ((var) (test)) n . rest) | ||||
|      (n () | ||||
|         ((final #f test)) | ||||
|         (final) | ||||
|         () | ||||
|         () | ||||
|         . rest)))) | ||||
| 
 | ||||
| ;;; Here starts generator clauses. | ||||
| 
 | ||||
| (define (generator->list gen) | ||||
|  |  | |||
|  | @ -37,8 +37,8 @@ | |||
|     a) | ||||
|   '(0 1 2 3)) | ||||
| 
 | ||||
| (test-equal ":let and :let*" | ||||
|   (loop/list ((a (up-from 0 5)) (:let b (+ a 1)) (:let* c (+ b 1))) | ||||
| (test-equal ":bind" | ||||
|   (loop/list ((a (up-from 0 5)) (:bind (b (+ a 1))) (:bind (c (+ b 1)))) | ||||
|     c) | ||||
|   '(2 3 4 5 6)) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Linus
						Linus