Hi list,
I've enhanced my (newer) keywords arguments library to optimize
processing at expand-time, which significantly reduces the run-time
cost, and I want to show it off now :-) Also, unlike my previous
keywords library, this one does not use the clunky [:- [kw ---] ...]
syntax I for some reason found amusing, and it is more generalized. I'm
still not decided if the semantics of "additional" (analog to "rest")
arguments are how they should be, see my question at the end.
Ikarus Scheme version 0.0.3+ (revision 1693, build 2008-11-25)
Copyright (c) 2006-2008 Abdulaziz Ghuloum
> (import (xitomatl keywords))
>
> (define/kw (f0 x y [a] [b] . r) ;; defines f0 as a new macro
(list x y a b r))
> (f0 1 2 "foo" 'b 3 'bar 4 5 'a 6) ;; expand-time processing
(1 2 6 3 ("foo" bar 4 5))
> (f0 1 2 'b 3 'a 4 'b 5 'a 6 'b 7)
(1 2 6 7 ())
> (f0 1 2 'b 3)
Unhandled exception
Condition components:
1. &assertion
2. &who: f0
3. &message: "missing required keyword"
4. &keyword: a
> (f0 1 2 'a 3 'b)
Unhandled exception
Condition components:
1. &assertion
2. &who: f0
3. &message: "keyword missing value"
4. &keyword: b
> (f0)
Unhandled exception
Condition components:
1. &message: "invalid syntax"
2. &syntax:
form: (f0)
subform: #f
3. &trace: #<syntax (f0)>
>
;; letrec* semantics for :default and :predicate expressions
;; referring to sibling arguments
> (define/kw (f1 x y [a :default (+ x y)]
[b :default (- a)
:predicate (if (number? a) string? char?)]
[c :boolean])
(list x y a b c))
> (f1 1 2)
(1 2 3 -3 #f)
> (f1 1 2 'a 54321)
(1 2 54321 -54321 #f)
> (f1 1 2 "ignored" 'c 'ignored 'b "zab")
(1 2 3 "zab" #t)
> (f1 1 2 "ignored" 'c 'ignored 'b #\λ 'a 'blah)
(1 2 blah #\λ #t)
> (f1 1 2 'b #\λ 'a 3)
Unhandled exception
Condition components:
1. &assertion
2. &who: f1
3. &message: "keyword predicate false"
4. &keyword: b
5. &predicate: (if (number? a) string? char?)
6. &irritants: (#\λ)
> (f1 1 2 'a 'blah)
Unhandled exception
Condition components:
1. &assertion
2. &who: -
3. &message: "not a number"
4. &irritants: (blah)
>
> (define first-class (car (let ([x f1]) (list x))))
> (first-class 1 2 "ignored" 'c 'ignored 'b #\λ 'a 'blah)
(1 2 blah #\λ #t)
> (first-class 1 2 'b 'oops 'a 321)
Unhandled exception
Condition components:
1. &assertion
2. &who: f1
3. &message: "keyword predicate false"
4. &keyword: b
5. &predicate: (if (number? a) string? char?)
6. &irritants: (oops)
>
> (define f2
(lambda/kw (x [a] . r) ;; always run-time processing
(apply + x a r)))
> (f2 1 2 3 'a 4 5)
15
>
> (define f3
(case-lambda/kw ;; always run-time processing
[(x [a :predicate number?])
(list x a)]
[([a])
(vector a)]
[r
(reverse r)]))
> (f3 1 'a 2)
(1 2)
> (f3 1 'a "λ")
#("λ")
> (f3 1 'b 'c 2 "foo" 'd "bar")
("bar" d "foo" 2 c b 1)
>
> (define f4
(case-lambda/kw
[([a]) 'first]
[([b] [c]) 'second]))
>
> (f4 'c 1 'a 2 'b 3)
first
> (f4 'c 1 'b 3)
second
> (f4 'c 1)
Unhandled exception
Condition components:
1. &assertion
2. &who: "a case-lambda/kw procedure"
3. &message: "no clause matches arguments"
4. &irritants: (c 1)
>
> (define n #e1e7)
> (define-syntax big-loop
(syntax-rules ()
[(_ expr ...)
(let loop ([i 0])
(unless (= i n)
expr ...
(loop (add1 i))))]))
>
> (time (big-loop))
running stats for (big-loop):
no collections
144 ms elapsed cpu time, including 0 ms collecting
162 ms elapsed real time, including 0 ms collecting
0 bytes allocated
> (define (f5 x y a b c)
(list x y a b c))
;; Normal procedure call
> (time (big-loop (f5 1 2 3 4 5)))
running stats for (big-loop (f5 1 2 3 4 5)):
96 collections
652 ms elapsed cpu time, including 81 ms collecting
682 ms elapsed real time, including 95 ms collecting
400000000 bytes allocated
;; Mostly all processed at expand-time, once
> (time (big-loop (f1 1 2 'c 'b "λ" 'a 3)))
running stats for (big-loop (f1 1 2 'c 'b "λ" 'a 3)):
95 collections
901 ms elapsed cpu time, including 20 ms collecting
993 ms elapsed real time, including 23 ms collecting
400000000 bytes allocated
;; All processed at run-time, every call
> (time (big-loop (first-class 1 2 'c 'b "λ" 'a 3)))
running stats for (big-loop (first-class 1 2 'c 'b "λ" 'a 3)):
1223 collections
21605 ms elapsed cpu time, including 536 ms collecting
23213 ms elapsed real time, including 532 ms collecting
5120004096 bytes allocated
;; case-lambda/kw is even more costly
> (time (big-loop (f3 1 'a "λ")))
running stats for (big-loop (f3 1 'a "λ")):
2141 collections
39535 ms elapsed cpu time, including 2053 ms collecting
42176 ms elapsed real time, including 2099 ms collecting
8960008672 bytes allocated
>
;; Keywords arguments lists can be constructed
> (apply f1 1 (append (list 2 'c (string->symbol "b"))
(list "λ" 'a 543)))
(1 2 543 "λ" #t)
;; But only for first-class / run-time, not macro / expand-time optimized
> (f1 1 2 (string->symbol "a") 543 (string->symbol "c"))
(1 2 3 -3 #f)
> (f1 1 2 (begin (display "ran\n") 'ignored))
(1 2 3 -3 #f)
> (f1 1 2 'a (begin (display "ran\n") 'evaled) 'b #\c)
ran
(1 2 evaled #\c #f)
>
;; General keywords arguments parsing available
> (define kwp (keywords-parser [a :predicate char?]
[b :default 'λ]
[c :boolean]))
> (kwp '("foo" a #\A bar c c "zab"))
#\A
λ
#t
("foo" bar "zab")
> (kwp '())
Unhandled exception
Condition components:
1. &assertion
2. &who: "a keywords parser"
3. &message: "missing required keyword"
4. &keyword: a
>
Question: Do you think additionals should cause an error if the
keyword-formals do not specify taking an "additionals" argument? This
would also change the semantics of case-lambda/kw clause selection.
Allowing additionals that are ignored seems good for possible future
uses of keywords lists where they are appended / merged with other
keywords lists that might have keywords / values that are ignored.
[Jibe at explicit phasing: Take a look at the (xitomatl keywords ---)
implementation to see what it took to make it work for explicitly phased
systems.]
As of revision 140. Available at:
https://code.launchpad.net/~derick-eddington/ikarus-libraries/xitomatl
Thanks for any feedback,
--
: Derick
----------------------------------------------------------------