How would I make this Racket code DRYer? - scheme

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.

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.

Related

What is the racket translation of this piece of lisp code?

;; loads a board from the given file
;; it expects the board to be in the format of a single S-expression:
;; a list of nine lists, each containing 9 numbers
(defun get-board-from-file file
(let ((in (open file :if-does-not-exist nil)))
(when in (return-from get-board-from-file (read in)))
(when (not in) (format t "~%Unable to open file ~A" file))
)
)
This is part of a soluton for a sudoku solver.
Try the following, it's a functional equivalent of the Lisp code in the question, but written in Racket:
(define (get-board-from-file file)
(with-handlers ([exn:fail:filesystem?
(lambda (exn) (printf "~%Unable to open file ~A" file))])
(call-with-input-file file
(lambda (in) (read in)))))
The above code handles an exception if the file doesn't exist, and makes sure that the port is closed after the file is read.

elisp parse output of asynchronous shell command

I have a simple elisp interactive function that I use to launch a Clojure repl.
(defun boot-repl ()
(interactive)
(shell-command "boot repl wait &"))
It opens an *Async Shell Command* buffer, and after a while the following text appears :
nREPL server started on port 59795 on host 127.0.0.1 - nrepl://127.0.0.1:59795
Implicit target dir is deprecated, please use
the target task instead. Set BOOT_EMIT_TARGET=no to disable implicit
target dir.
I would like to monitor the output of this command to be able to parse the port ("59795" in this example).
Even just the first line (in the case without warnings) would be alright.
This way I could be able to use another command to connect to the Clojure REPL waiting for me.
I cannot use shell-command-to-string as the command does not return and it blocks emacs forever (boot repl wait is supposed to last for my whole programming session, possibly more).
There may be something easy to do with cider also, but I haven't found it.
So, how do I parse the result of an asynchronous bash command in Elisp ?
Alternatively, how can I set-up Cider to launch this REPL for my and connect to it ?
To answer the question directly, you can definitely parse the output of an asyncronous shell command, using start-process and set-process-filter:
(let ((proc (start-process "find" "find" "find"
(expand-file-name "~") "-name" "*el")))
(set-process-filter proc (lambda (proc line)
(message "process output: %s" line))))
(Docs for filter function)
However, note that line above is not necessarily a line, and may include multiple lines or broken lines. Your filter is called whenever the process or emacs decides to flush some ouput:
...
/home/user/gopath/src/github.com/gongo/json-reformat/test/json-reformat-test.el
/home/user/gopath/src/github.com/gongo/json-reformat/test/test-
process output: helper.el
In your case, this could mean that your port number might be broken into two separate process-filter calls.
To fix this, we can introduce a line-buffering and line-splitting wrapper, which calls your filter for each process output line:
(defun process-filter-line-buffer (real-filter)
(let ((cum-string-sym (gensym "proc-filter-buff"))
(newline (string-to-char "\n"))
(string-indexof (lambda (string char start)
(loop for i from start below (length string)
thereis (and (eq char (aref string i))
i)))))
(set cum-string-sym "")
`(lambda (proc string)
(setf string (concat ,cum-string-sym string))
(let ((start 0) new-start)
(while (setf new-start
(funcall ,string-indexof string ,newline start))
;;does not include newline
(funcall ,real-filter proc (substring string start new-start))
(setf start (1+ new-start)));;past newline
(setf ,cum-string-sym (substring string start))))))
Then, you can safely expect your lines to be whole:
(let* ((test-output "\nREPL server started on port 59795 on host 127.0.0.1 - \nrepl://127.0.0.1:59795 Implicit target dir is deprecated, please use the target task instead. Set BOOT_EMIT_TARGET=no to disable implicit target dir.")
(proc (start-process "echo-test" "echo-test" "echo" test-output)))
(set-process-filter proc (process-filter-line-buffer
(lambda (proc line)
(when (string-match
"REPL server started on port \\([0-9]+\\)"
line)
(let ((port (match-string 1 line)))
;;take whatever action here with PORT
(message "port found: %s" port)))))))
Finally, I'm not familiar with cider but this kind of low-level work probably belongs in an inferior-type mode and has probably already been solved.
shell-command
allows to name optional output- and error-buffers. Than the error should appear inside the latter and not clutter the output any more.
A better answer to the other one I provided is to simply use cider as you suggested:
(progn
(package-refresh-contents)
(package-install 'cider)
(cider-jack-in))

Setting mode through a function in Emacs Lisp

I have the following code in my .emacs file, which works as you'd expect:
;; Ruby
(global-font-lock-mode 1)
(autoload 'ruby-mode "ruby-mode" "Ruby editing mode." t)
(setq auto-mode-alist (cons '("\\.rb$" . ruby-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("\\.rsel$" . ruby-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("\\.rhtml$" . html-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("\\.erb$" . html-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("\\.prawn$" . html-mode) auto-mode-alist))
(setq auto-mode-alist (cons '("Rakefile$" . ruby-mode) auto-mode-alist))
However, my attempts to DRY it up a bit fail:
(defun set-mode-for-filename-patterns (mode filename-pattern-list)
(mapcar
(lambda (filename-pattern)
(setq
auto-mode-alist
(cons '(filename-pattern . mode) auto-mode-alist)))
filename-pattern-list))
;; Ruby
(global-font-lock-mode 1)
(autoload 'ruby-mode "ruby-mode" "Ruby editing mode." t)
(set-mode-for-filename-patterns
ruby-mode
'("\\.rb$"
"\\.rsel$"
"\\.rhtml$"
"\\.erb$"
"\\.prawn$"
"Rakefile$"
"Gemfile$"))
... with the following error:
Debugger entered--Lisp error: (void-variable ruby-mode)
(set-mode-for-filename-patterns ruby-mode (quote ("\\.rb$" "\\.rsel$" "\\.rhtml$" "\\.erb$" "\\.prawn$" "Rakefile$" "Gemfile$")))
eval-buffer(#<buffer *load*> nil "/home/duncan/.emacs" nil t) ; Reading at buffer position 1768
load-with-code-conversion("/home/duncan/.emacs" "/home/duncan/.emacs" t t)
load("~/.emacs" t t)
#[nil "\205\264
I'm a bit confused here ... in particular, I don't understand how ruby-mode is void & so can't be passed to a function, but can be consed into a pair?
Any pointers (heh) would be greatly appreciated.
In the form:
(cons '("\\.rb$" . ruby-mode) ...
ruby-mode is part of a quoted list. That means it is read as a symbol name, not evaluated as a variable. In other words, Emacs sees it as the symbol ruby-mode and accepts it as is.
In the form:
(set-mode-for-filename-patterns
ruby-mode
'("\\.rb$"
"\\.rsel$"
...
ruby-mode is not quoted, and so is read as the argument to a function. Function arguments are evaluated. Which means Emacs reads ruby-mode, recognizes it as a symbol, and tries to evaluate it. Evaluating a symbol means looking for the value that it points to, which in this case doesn't exist.
EDIT:
Your function still doesn't work, there's another problem. You've used a quoted list inside set-mode-for-filename-patterns. This works fine in your original code:
(setq auto-mode-alist (cons '("\\.rb$" . ruby-mode) auto-mode-alist))
as you are in effect manually supplying the value for filename-pattern and mode. Inside your function, you need these symbols to be evaluated, which doesn't happen when they're quoted! The result is that instead of adding each different string from your list to auto-mode-alist, you get the symbol filename-pattern instead.
To fix this, you need to recognize that the '(filename-pattern . mode) is meant to produce a cons cell with the values of filename-pattern and mode. Which we can produce with (cons filename-pattern mode). So the corrected function would be:
(defun set-mode-for-filename-patterns (mode filename-pattern-list)
(mapcar
(lambda (filename-pattern)
(setq
auto-mode-alist
(cons (cons filename-pattern mode) auto-mode-alist)))
filename-pattern-list))
And the corrected function call is:
(set-mode-for-filename-patterns
'ruby-mode
'("\\.rb$"
"\\.rsel$"
"\\.rhtml$"
"\\.erb$"
"\\.prawn$"
"Rakefile$"
"Gemfile$"))
Look here:
(setq auto-mode-alist (cons '("\\.rb$" . ruby-mode) auto-mode-alist))
----------------------------^
This is a quote which means you prevent the evaluation of the next
form, thus '("\\.rb$" . ruby-mode) is equivalent to (cons '"hello"
'ruby-mode).
But when you call the function set-mode-for-filename-patterns the
arguments are first evaluate then their result is passed to the
function. That's why evaluation (set-mode-for-filename-patterns
ruby-mode ..) raise an error, because the emacs-lisp interpreter tries
to evaluate ruby-mode as a variable, but ruby-mode has no value in
this context hence the error (void-variable ruby-mode). What you
want here, is to pass the symbol ruby-mode so you have to quote it
like this (set-mode-for-filename-patterns 'ruby-mode ...)
Note that you could have set a value to ruby-mode mode with let.
(let ((ruby-mode 'ruby-mode))
(set-mode-for-filename-patterns ruby-mode ...))
Here when the argument the form (set-...) is evaluated it evaluates
ruby-mode and can find a value for it (which is the symbol
ruby-mode) and then pass it to the function.
I think set-mode-for-filename-patterns is an interesting function. I'm going to add it to my config but use a more optimized implementation.
The implementations here all add one item to the auto-mode-alist variable for each file suffix. Emacs searches this list every time it finds a file. So the shorter the auto-mode-alist, the faster Emacs will find files.
This version is probably slower at startup but faster when finding files:
(defun set-mode-for-filename-patterns (mode filename-pattern-list)
(push (cons (regexp-opt filename-pattern-list) mode)
auto-mode-alist))`
This will work with the the same call:
(set-mode-for-filename-patterns
'ruby-mode
'("\\.rb$"
"\\.rsel$"
"\\.rhtml$"
"\\.erb$"
"\\.prawn$"
"Rakefile$"
"Gemfile$"))
If you look at the value of auto-mode-alist you'll see that many of the built-in modes use regexps for the same performance reason.
BTW, I advise that you just trust regexp-opt to do the right thing. The regexps it makes are pretty hard on the eye (and brain).

Scheme: Proper application of the eval function?

at work I encountered a basic problem when trying to implement a configuration script with Scheme. To avoid the need of inventing an artificial and restricted language the script should contain actual code. This code shall be evaluated later on. To make the configuration work as desired it has to have access to certain variables. These variables are only known in the context of the evaluation. Therefore the configuration script has to be evaluated in the current environment. Here is a primitive example of what I am talking about:
(let ((a #t))
(wr "a is ..."
(eval '(if a "true" "false"))))
When running this code I'd always get an error message telling me that the variable 'a' is unknown. So the question is: Do you know how to evaluate frozen code inside the current environment?
P.S.: I use the bigloo compiler.
/////////////////////////////////////////////
EDIT: //////////////////////////////////////////////////////
When using the approach suggested by Chris I came to another interesting problem, the usage of the case keyword. The following two examples both use the same case construction which should trigger the output of a "yes!" line. Unfortunately they behave differently.
Usual -> output is "yes!" as expected:
(define testit "test")
(case testit
(("test")
(begin (newline) (write "yes!") (newline)))
(else
(begin (newline) (write "no!") (newline)))))
With eval -> output is surprisingly "no":
(define env (null-environment 5))
(eval '(define testit "test") env)
(eval '(case testit
(("test")
(begin (newline) (write "yes!") (newline)))
(else
(begin (newline) (write "no!") (newline)))))
Does that make any sense?
eval cannot access lexical variables, such as those defined using let.
Instead, you have to create an environment, and populate it with the variables you want to make available. For example:
(define env (null-environment 5))
(eval '(define a #t) env)
(wr "a is ..."
(eval '(if a "true" "false") env))
To answer your edit, you aren't passing env as an argument to the last eval. testit doesn't exist in the environment that eval creates if that argument isn't given.
That may be a typo, but if not, that's your problem.

How do you halt in DrScheme's implementation of R5RS?

When using DrScheme with R5RS, there is no error function. I plan to write my own, but can't figure out how to halt the program execution. I tried commands such as:
(halt)
(exit)
(error)
and none worked. How do you halt program execution?
SLIB (the portable Scheme library) has an implementation of ERROR. You might want to either look at that, or use SLIB in your programs.
Other than that, one way to halt the program is simply to raise a different error! Try something like this (thanks to Stephen Houben):
(define (error reason . args)
(display "Error: ")
(display reason)
(for-each (lambda (arg)
(display " ")
(write arg))
args)
(newline)
(scheme-report-environment -1)) ;; we hope that this will signal an error
While this does raise a second (unrelated) error, it will surely halt program execution.
Is there a reason you need to use R5RS? Other language definitions in DrScheme define error and exit. For example, the (module ...) PLT language defines error and exit. Invoking mzscheme from the command line also gives you these definitions.
Note: I have DrScheme 372, which is pretty old. Things shouldn't have changed too much, though.
an ugly solution is to define abort to become a runtime error.
for example any of these should do the trick
(define abort "the program was aborted")
(define abort 123)
(define abort #f)
any call to abort
(abort)
should generate a runtime error, if your lucky even show the string , error code or whatever you care.

Resources