I'm fairly new to Scheme and have recently discovered the power of define-syntax macros but I'm struggling with something that I'm hoping someone can correct me on.
I'm writing a little program to automate windows applications like this :-
(using-window "Home - Microsoft Internet Explorer" (send-keys "{TAB}Hello") (click-button "OK") )
send-keys and click-button are procedures that operate on a window handle (hwnd) so I could have written
(let ((hwnd (find-window "Home - Microsoft Internet Explorer"))) (send-keys hwnd "{TAB}Hello") ... )
In order to avoid all that nasty passing around hwnd, I made send-keys assume that hwnd was already defined and defined the macro using- window :-
AndrewW <and...@trailgauge.com> writes: >I'm writing a little program to automate windows applications like >this :- >(using-window "Home - Microsoft Internet Explorer" > (send-keys "{TAB}Hello") > (click-button "OK") >) >send-keys and click-button are procedures that operate on a window >handle (hwnd) so I could have written
Must these be separate procedures? It is possible for you to do this exact example using either a syntax with aux keywords or to get rid of the need for keywords entirely, by just passing USING-WINDOW three arguments.
>(let ((hwnd (find-window "Home - Microsoft Internet Explorer"))) > (send-keys hwnd "{TAB}Hello") >... >) >In order to avoid all that nasty passing around hwnd, I made send-keys >assume that hwnd was already defined and defined the macro using- >window :- >(define-syntax using-window > (syntax-rules () > ((using-window title expr...) > (let ((hwnd (find-window title))) > expr...) > ) > ) > ) >and of course it doesn't work because the macro is hygienic and hwnd >only exists within the macro and not when it's expanded. >This desire for encapsulation must crop up all the time, what's the >correct way to do it in Scheme ?
You could handle USING-WINDOW like CALL-WITH-CURRENT-INPUT-PORT
Or, you could introduce a name as well as the title:
(define-syntax using-window (syntax-rules () [(_ id title expr ...) (let ([id (find-window title)]) (void) expr ...)]))
(using-window hwnd "title" (send-keys hwnd ...))
But regardless, you are going to have to deal with a window object somehow, and you need to obey the scoping rules. This isn't really an issue of encapsulation, I think. What you are wanting is a shortcut to avoid having to pass a specific name all the time. You can use parameters to store these values, such as WITH-INPUT-FROM-STRING does.
If this really is an issue of encapsulation, then the normal way of doing it *is* to use a let to bind the handle only in the scope you want. If it's a matter of saving a few keystrokes, there are some non-hygenic solutions, but I don't think that's a good idea for this.
If you are relaly so addicted to the api you gave, you could possibly do it with something like a maker:
But that may or may not be what you want. Module systems are another way of hiding values for encapsulation. As are internal definitions.
The bottom line is that I would recommend you reevaluate the way you are designing your library. You can do what you want, but I don't recommend it.
-- Aaron W. Hsu <arcf...@sacrideo.us> | <http://www.sacrideo.us> "Government is the great fiction, through which everybody endeavors to live at the expense of everybody else." -- Frederic Bastiat +++++++++++++++ ((lambda (x) (x x)) (lambda (x) (x x))) ++++++++++++++
Thanks kindly for your answer which has some useful suggestions. I didn't really like the idea of putting the hwnd as an extra argument although functionally it was probably the correct thing to do because it is needless typing and detracts from the readabilty. Whilst it wouldn't be a problem for this simple example, imagine a case where a larger set of data was setup and needed to be shared. Also I read the chapter on makers in SICP and these would be a nice idea but each of my operations would have to be messages to pass into the function and I couldn't therefore mix operations on the hwnd with other arbritary scheme operations. In the end I went for a dirty CL style macro :-
Gambit seems to support these with greater ease than define-syntax. I know it's disgusting and possibly not very portable but the rest of the program depends on c-lambdas so that won't be portable either.
The one disadvantage with this is it will only accept one s-expression so I have to wrap the operations in a (begin ...) but I can fix this with a Scheme macro on top :-
(define-syntax using-window (syntax-rules () ((using-window title expr ...) (u-w-m title (begin expr ...)) ) ) )
I can hear your scorn already :-)
Thanks again - I really appreciate the service that you and others provide via this group to us novices.
On Jan 22, 10:36 am, AndrewW <and...@trailgauge.com> wrote:
> Aaron,
> Thanks kindly for your answer which has some useful suggestions. I > didn't really like the idea of putting the hwnd as an extra argument > although functionally it was probably the correct thing to do because > it is needless typing and detracts from the readabilty. Whilst
Hi Andrew
I think the approach Aaron was suggesting when he talked about current- input-port could be done using optional window arguments. Then you can use a scope 'with-' macro for good readability, but you still retain the option of passing explicit windows to functions when necessary (say, when you want to interact with two different windows within the same scope). Here is my attempt at doing something like that:
;; A dynamic variable in the style of current-input-port (SRFI 39). (define current-window (make-parameter #f))
;; Looks up a window. For this example just returns the name itself. (define (find-window name) name)
;; Sends keys to a window. The second parameter is an optional window, but ;; if not provided, then the parameter current-window is used. This is ;; modelled on display, read, write and friends which use current-XXX- port ;; if you don't specify a port. (define (send-keys data . rest) (let ((window (if (null? rest) (current-window) (car rest)))) (display "send-keys: sending ") (display data) (display " to window ") (display window) (newline)))
;; A syntax for looking up a window, and making it current for a scope. (define-syntax with-window (syntax-rules () ((_ name expression ...) (parameterize ((current-window (find-window name))) expression ...))))
;; Example of using the macro. (with-window "Home - Microsoft Internet Explorer" (send-keys "{TAB}Hello"))
;; Example of using explicit windows. (let ((window (find-window "Windows is pants"))) (send-keys "{CTRL-ALT-DELETE}" window))
That looks perfect and more versatile, I've not come across make- parameter and parameterize before - I'll read up on them. I presume that is copes with this sort of thing ? :-
Well after some wrestling with Gambit, I discovered that define-syntax can't be used with Gambit special forms of which parameterize is one. So I changed with-window to :-
which saves away the value of current-window parameter, assigns it to your new window and then restores the old one when you return. All seems to work.
I had a great deal of difficulty getting Gambit to recognise define- syntax first in the interpreter and then again when compiler. It seems that you need to pass the -:s parameter to both gsi and gsc in order to use define-syntax.
Thanks again both, I now have a solution that I'm happy with and I've learnt a little more in the process.
Great question. I always wonder what is the idiomatic way to solve problems in Scheme. It is helpful and fun to see how other people solve it.
That said, you mention two topics:
1. How to deal with this particular example. 2. Encapsulation in general in Scheme
I assume you only care about #1 in this post, so my reponse is to that topic.
I would deal with the problem by starting out with a simple functions as humanly possible. Those functions are small, easy to document and easy to test: find-window, send-keys, and click-button are all candidates here. Write and test those.
Next, write a function called "using-window" that takes a window name and "the work that you want to do", as you have listed. You can do all of this in a function. The problem you need to solve, though; is how to passa HWND reference to all of the functions that want to operate on that window, when you don't know what is the HWND for that window yet? The solution is simple, use lambda.
You can create thunks for each of the thunks of work you want to perform, and within using-window you can create a binding for the HWND and pass it to all of the thunks. Still, you want a nice syntax. The first step is to simply creation of the list of thunks; it is a trivial change but "nice" nonetheless. The big one is to write a macro that provides for you your original syntax:
(using-window "Home - Microsoft Internet Explorer" (send-keys "{TAB}Hello") (click-button "OK"))
You can use this syntax, hygienically, if you satisfy one contract: Every function that works with a window (given its HWND) will take the HWND reference as its first argument. When you stick with that approach, you can simply compile down your desired shape into the one that satisfies the contract you defined here. The following code walks through the whole thing; along with providing macro expansions. It should work on any R5RS with syntax-case, or R6RS (I tested it in PLT #lang scheme and Chez 7.4). An aside, if you want you could use SRFI-26 to create the thunks.
Let me know what you guys think:
; find-window : string -> HWND ; to determine the Window handle for the window with the given title (define (find-window title) ; do something 1024)
; send-keys : HWND list -> boolean ; to generate "key events" on the window specified by the handle for the given ; list of keys (define (send-keys hwnd keys) ; do something #t)
; click-button : HWND string -> boolean ; to generate a "click event" on the for the button specified by the label ; that exists on the window specified by the handle. (define (click-button hwnd label) ; do something #t)
; using-window-v1 : string list-of-thunks -> list-of-booleans ; a helper function to provide a convenient syntax for looking up ; windows and performing actions upon them in an unspecified order until ; one of them fails. Each thunk expects the HWND of the window specified by ; the title string as an argument. (define (using-window-v1 title thunks) (let ((hwnd (find-window title))) (map (lambda (thunk) (thunk hwnd)) thunks)))
(display (using-window-v1 "Home - Microsoft Internet Explorer" (list (lambda (hwnd) (send-keys hwnd "{TAB}Hello")) (lambda (hwnd) (click-button hwnd "OK"))))) (newline)
; using-window-v2 : syntax ; a helper syntax to make using using-window-v1 more pleasant (define-syntax using-window-v2 (syntax-rules () ((_ title thunks ...) (using-window-v1 title (list thunks ...)))))
(display (using-window-v2 "Home - Microsoft Internet Explorer" (lambda (hwnd) (send-keys hwnd "{TAB}Hello")) (lambda (hwnd) (click-button hwnd "OK")))) (newline)
; using-window-v3 : syntax ; a helper syntax to make using using-window-1 even more pleasant ; by reusing using-window-v2. This macro makes the assumption that ; all functions that operate on HWND refences ALWAYS take the HWND as the first ; argument to the function. Consequently, expressions that are enclosed by this ; macro adhere to this approach and we can rewrite them accordingly. (define-syntax using-window-v3 (syntax-rules () ((_ title (fun args ...) ...) (using-window-v2 title (lambda (hwnd) (fun hwnd args ...)) ...))))
(display (using-window-v3 "Home - Microsoft Internet Explorer" (send-keys "{TAB}Hello") (click-button "OK"))) (newline)
Grant Rettke <gret...@gmail.com> writes: >Next, write a function called "using-window" that takes a window name >and "the work that you want to do", as you have listed. You can do all >of this in a function. The problem you need to solve, though; is how >to passa HWND reference to all of the functions that want to operate >on that window, when you don't know what is the HWND for that window >yet? The solution is simple, use lambda.
I think Andrew's problem is not passing the HWND to the procedures, but how to do so without having to explicitly pass them.
>You can create thunks for each of the thunks of work you want to >perform, and within using-window you can create a binding for the HWND >and pass it to all of the thunks. Still, you want a nice syntax. The >first step is to simply creation of the list of thunks; it is a >trivial change but "nice" nonetheless. The big one is to write a macro >that provides for you your original syntax:
This is of course one very common idiom for doing things, but it still requires the explicit passing of the hwnd value to all the procedures, and then wrapping lot's of single argument procedures around this code. In the end, this is what Andrew -- I believe -- is trying to avoid. [Also, as a side note, "thunks" are procedures of no arguments.]
However, if he really wants to eliminate such things, and wants to do it in clean ways, as has already been discussed, one could use parameters, or do some rather nasty macrology to get it working.
This is essentially just another way of using parameterize and parameters, but it's a little more flexible. Parameters are probably nicer if you can use them.
-- Aaron W. Hsu <arcf...@sacrideo.us> | <http://www.sacrideo.us> "Government is the great fiction, through which everybody endeavors to live at the expense of everybody else." -- Frederic Bastiat +++++++++++++++ ((lambda (x) (x x)) (lambda (x) (x x))) ++++++++++++++