How would I make this Racket code DRYer?
Asked Answered
C

1

7

I'm porting a Python script to Racket as a learning experience, and I have this function:

(define (check-status)
    (define git [find-executable-path "git"])
    (define-values (ckot out in err)
        (subprocess #f #f #f git "checkout" "-q" "master"))
    (define-values (local lout lin lerr)
        (subprocess #f #f #f git "rev-parse" "@"))
    (define-values (remote rout rin rerr)
        (subprocess #f #f #f git "rev-parse" "@{u}"))
    (define-values (merge-base mbout mbin mberr)
        (subprocess #f #f #f git "merge-base" "@" "@{u}"))
    (display-lines (port->lines mbout))
   (define ports '(ckot out in err local lout lin lerr remote rout rin rerr merge-base mbout mbin mberr))
   (map (lambda (x)
        (cond ((input-port? x) (close-input-port x))
              ((output-port? x) (close-output-port x)))) ports))

The problem is that it's not very DRY. Since I'm using a Lisp, and Lisp is known for being able to do crazy things, I want to know if there's a way to take all the subprocess code and extract it so I can do something like:

(define (check-status)
    (define commands '(
                        '("checkout" "-q" "master") 
                        '("rev-parse" "@") 
                        '("rev-parse" "@{u}") 
                        '("merge-base" "@" "@{u}"))
    (map currently-immaginary-git-command-fn commands))

and end up with a list of the output of each command in the list of commands. How would I do this? Since I'm new to the whole Lisp/Scheme thing, I'm figuring out the syntax as I go and I'm not fully aware of the resources available to me.

Convertiplane answered 3/6, 2015 at 16:37 Comment(5)
This would be a good question for StackExchange CodeReview.Prognathous
This already has an accepted answer, although @Convertiplane is welcome to take his working code and post it to Code Review, if he wants it reviewedAbject
BTW, the port-closing code won't work: '(ckot out in ...) is a list of symbols; it's the same as (list 'ckot 'out 'in ...). You want (list ckot out in ...) instead.Chopin
Oh my, thanks. Is there a resource I can look at to see the difference between ' and list?Convertiplane
A little late, but yes: What is the difference between quote and list?Unconditioned
Q
7

First of all, good for you for wanting to come up with a cleaner solution! You're right that there's a more elegant way to do what you've attempted.

To start, using subprocess is almost certainly overkill in your particular use-case. The racket/system module provides a simpler interface that should be sufficient for your needs. Specifically, I'd use the system* function, which executes a single process with the provided arguments, then prints its output to stdout.

Using system*, it's possible to create a very general helper function that can execute a command for a particular executable and returns its output as a string.

(define (execute-command proc-name)
  (define proc (find-executable-path proc-name))
  (λ (args)
    (with-output-to-string
     (thunk (apply system* proc args)))))

This function itself returns a new function when it's called. This means that using it to call a Git command would look like this:

((execute-command "git") '("checkout" "-q" "master"))

The reason for this will become apparent shortly.

Actually looking at the implementation of execute-command, we use with-output-to-string to redirect all of the output from the system* call into a string (instead of just printing it to stdout). This is actually just an abbreviation for using parameterize to set the current-output-port parameter, but it's simpler.

With this function, we can implement check-status very easily.

(define (check-status)
  (define commands
    '(("checkout" "-q" "master") 
      ("rev-parse" "@") 
      ("rev-parse" "@{u}") 
      ("merge-base" "@" "@{u}")))
  (map (execute-command "git") commands))

Now the reason for having (execute-command "git") return a new function becomes apparent: we can use that to create a function which will then map over the commands list to produce a new list of strings.

Also, note that the definition of the commands list only uses a single ' at the beginning. The definition you provided would not work, and in fact, the ports list you defined in your original implementation is not what you'd expect. This is because '(...) is not exactly the same as (list ...)—they are different, so be careful when using them.

Quid answered 3/6, 2015 at 19:1 Comment(1)
Awesome! Thanks. Funny, I figured it'd have something to do with returning a function or macros.Convertiplane

© 2022 - 2024 — McMap. All rights reserved.