(elisp dash.el) help rewriting clunky code - elisp

I'm trying to create a composition in dash, that does the following
- split into lines
- remove empty lines
- keep lines starting w/"N"
- return filename extension
However the code below feels clunky - wondering if anyone could help
simplify/rewrite this.
(let ((line-sep "\\\n")
(new-ind "^N ")
(sample-output "
N /dir1/file1.el
N /dir1/file2.dot
/dir1/file3.py
/dir1/file4.sql"))
(funcall
(-compose
(-partial '-map (lambda(x) (file-name-extension (nth 1 (split-string x)))))
(-partial '-filter (-partial 'string-match-p new-ind)) ; keep lines starting w/"N"
(-partial '-filter (lambda(x) (> (length x) 0))) ; remove empty lines
(-partial (-flip 'split-string) line-sep)) ; split into lines
sample-output))

After fooling around with it, it looks like the compose pipeline only works well when all the functions take the same type of argument.. I rewrote it like so, and it seems like a better fit; would like to hear comments..
(let* ((line-sep "\\\n")
(lexical-binding t)
(new-ind "^\\s-*N ")
(sample-output "
N /dir1/file1.el
N /dir1/file2.dot
/dir1/file3.py
/dir1/file4.sql")
(lines (split-string sample-output "\\\n")))
(-annotate
'file-name-extension
(-map
(-compose
(-partial 'nth 2)
(-partial (-flip 'split-string) "\\s-+"))
(--filter
(string-match-p "^\\s-*N " it)
lines))))

Related

Emacs lisp, how to dynamically create quoted expression including special chars like `?1`?

I would like to dynamically create a list of chars to use with read-char-choice. From this answer of similar question, how to construct the list programmatically with unquoted values as choices, ?1 with a function. The function that I ended up is
(defun prompt-list (name-list)
"docstring"
(let ((names name-list)
(name-num 1)
(choice-list (list)))
(dolist (x names)
(add-to-list 'choice-list
`(,name-num ;; in that part how to create the ?1, ?2 but dynamically
,x (lambda () (setq project-headers x))))
(setq name-num (+ 1 name-num)))
choice-list))
when I try to run it it returns error : Format specifier doesn’t match argument type.
In my understanding it need a char type, so my question is how to produce char types programmatically?
?1 is the read syntax for the character '1', which has the integer value 49. You can use the read syntax to initialize your name-num variable, rather than using the integer 1:
(defun prompt-list (name-list)
"docstring"
(let ((names name-list)
(name-num ?1)
(choice-list (list)))
(dolist (x names)
(add-to-list 'choice-list `(,name-num
,x (lambda () (setq project-headers ',x)))
t)
(setq name-num (+ 1 name-num)))
choice-list))
A couple of things to note:
You want the use ,x when setting project-headers, not just x, as the latter won't be valid by the time the lambda is invoked.
You want to append to choice-list as you build it, which is what the final t argument to add-to-list above does, otherwise your read-char-choice prompt will be in reverse order.

Racket/Scheme converting "-0" to "-0.0"

I am writing a simple interpreter that should
output: +inf or -inf for the following computations:
(/ 0)
(/ 1 0)
(/ -0)
I notice that replaced 0 by 0.0 gives me the behavior I want.
But I haven't figured out to convert -0 to -0.0.
exact->inexact loses the negative sign.
(exact->inexact -0) gives 0.0.
To do this, with the literal text -0 you would have to change the Reader so that -0 reads equal to -0.0, an inexact version. To be consistent you might want to read every number as an inexact number, whether it has a decimal point or not.
It's worth noting that you can do this by prefixing a number #i, for example #i-0 reads equal to -0.0. However, it sounds like you might want to change the reader so that every number reads the same as if it had #i prefixed onto it including -0.
One of the easier ways to extend the reader is with a readtable. You can make a function that extends a readtable like this:
;; Readtable -> Readtable
(define (extend-readtable orig-rt)
;; Char InputPort Any Nat Nat Nat -> Any
(define (rt-proc char in src ln col pos)
....)
...
(make-readtable orig-rt
#f 'non-terminating-macro rt-proc
...))
To use this to define a #lang language, you need to put the reader implementation in your-language/lang/reader.rkt. Here that's inexact-number/lang/reader.rkt, where the inexact-number directory is installed as a single-collection package (raco pkg install path/to/inexact-number).
inexact-number/lang/reader.rkt
#lang racket
(provide (rename-out [-read read]
[-read-syntax read-syntax]
[-get-info get-info]))
(require syntax/readerr
syntax/module-reader)
;; Readtable -> Readtable
(define (extend-readtable orig-rt)
;; Char InputPort Any Nat Nat Nat -> Any
(define (rt-proc char in src ln col pos)
....)
...
(make-readtable orig-rt
#f 'non-terminating-macro rt-proc))
;; [X ... -> Y] -> [X ... -> Y]
(define ((wrap-reader rd) . args)
(parameterize ([current-readtable (extend-readtable (current-readtable))])
(apply rd args)))
(define-values [-read -read-syntax -get-info]
(make-meta-reader 'inexact-number
"language path"
lang-reader-module-paths
wrap-reader
wrap-reader
identity))
The main work goes into filling in the .... holes in the extend-readtable function. Within the rt-proc, you can have it "peek" into the input port in to see if its a number, and if it is, call the Racket reader on a port that has #i appended to the front of in. You might be able to do this with something like input-port-append:
(input-port-append #f (open-input-string "#i") in)
In the context of extend-readtable, here's some code that does the peeking and re-reading:
;; Readtable -> Readtable
(define (extend-readtable orig-rt)
;; Char InputPort Any Nat Nat Nat -> Any
(define (rt-proc char in src ln col pos)
(define try-in (peeking-input-port in))
(define try (read/recursive try-in char orig-rt))
(cond
[(number? try)
;; read it as if it had #i on the front
(define prefix (string-append "#i" (string char)))
(define inexact-in
(input-port-append #f (open-input-string prefix) in))
(read-syntax/recursive src inexact-in #f orig-rt)]
[else
;; read normally
(read-syntax/recursive src in char orig-rt)]))
(make-readtable orig-rt
#f 'non-terminating-macro rt-proc))
Once you have this done, you should be able to use it like this:
#lang inexact-number racket
-0
; => -0.0
(/ -0)
; => -inf.0
P.S. I have now made #lang inexact-number available on the Racket package server, as the package inexact-number-lang. By the way, there's also #lang exact-decimal, which has the opposite affect of making numbers like 5.2 into exact rational numbers.
This is because -0 in Racket is the same literal datum that can also be written as 0, +0, 0000000, #e0, #e-0/1, -00000/1, or in various other ways. All of these syntaxes produce the very same value from the reader, which is an exact integer. Unlike floating-point numbers, exact numbers don't have signed zeros. To illustrate:
> (exact-integer? 0)
#t
> (exact-integer? +0)
#t
> (exact-integer? -0)
#t
> (eq? +0 -0)
#t
> (eq? -0 0)
#t
To get the behavior you want, you need to adjust the reader layer so that 0 and -0 don't produce the same value. I don't think there is a built-in parameter for doing what you want (there are for many other reader customizations), but you can do it by creating a custom readtable.
I would need more context to give you further guidance (for example, you say you're writing an "interpreter," which isn't the usual way of making a DSL in Racket), but my first inclination would be that, rather than re-implementing number parsing, you might map - to a "non-terminating-macro" and handle the rest in the expander (or interpreter) layer.
-0 doesn't exist! If you enter something silly like (define x -0) and press the Macro stepper #'> before any transformations you'll see it has read it in as (define x 0). The sign is truncated by the reader since integers doesn't have positive and negative zero like IEEE754 does. Thus unless you create your own language with your own reader that can discriminate on signed zero mapped to a data structure that supports it you cannot solve this.
IEEE754 floating points has the sign as it's own bit and thus every number, including 0, can be both positive and negative.
As for Scheme standard: Before R6RS there was no requirement for a full numeric tower and the report even mentions that a floating point only Scheme might be useful. Thus I believe some implementations of R5RS actually might read -0 as -0.0.

Lisp format list with parameter

I'm trying to create a function that will take a string and display it.
(defun closing (s)
(format t "~{~a~}" ("Sincerely," "\n" s)))
What I hope to get is
Sincerely,
Frank
if "Frank" is the string I passed in. It complains of the variable S is defined but never used. What am I doing wrong?
Trying to use format alone: If I declare urname as a defparameter to be "Frank", the following doesn't print Frank, rather just the variable name. (Without quote it complains of urname not being a function.)
(format t "~{~a~}" '(urname urname urname))
How can I feed variables to format?
There are three issues here: (1) The code you posted doesn't just have the problem of not using s; it's also trying to call the string "Sincerely" as a function; (2) quoting a list means you'll get exactly what's quoted (e.g., a list of symbols, not a list of values of variables); (3) calling format with lists.
(something other-stuff...) is a function call
When I put the code you posted into SBCL, I get some very specific and helpful output:
CL-USER> (defun closing (s)
(format t "~{~a~}" ("Sincerely," "\n" s)))
; in: DEFUN CLOSING
; ("Sincerely," "n" S)
;
; caught ERROR:
; illegal function call
; (SB-INT:NAMED-LAMBDA CLOSING
; (S)
; (BLOCK CLOSING (FORMAT T "~{~a~}" ("Sincerely," "n" S))))
;
; caught STYLE-WARNING:
; The variable S is defined but never used.
;
; compilation unit finished
; caught 1 ERROR condition
; caught 1 STYLE-WARNING condition
("Sincerely," "\n" s) is an illegal function call, since a string, like "Sincerely", can't have a function binding. Since SBCL sees the problem in that, it recognizes that the one thing that s might have been used for (i.e., an argument to a function call), can't happen. That's why you'll get the error, and then the associated style warning.
Creating lists of values
The second is probably answered in other questions already, but the short answer is that you want (list x y z), not '(x y z). The former calls the function list with the values of the variables x, y, and z, while the latter denotes a literal list of the symbols x, y, and z.
CL-USER> (let ((a 42)
(b 89))
(print '(a b)) ; a list of two symbols
(print (list a b))) ; a list of two numbers
(A B)
(42 89)
Format, iteration, &c.
The third is probably more interesting, since format has so many possibilities. The ~{ and ~} in your example are used for iterating over values in a list. First, let's look at a simple example: you can just use the format directive ~a and call format with the arguments you want to splice in:
CL-USER> (let ((closing "Sincerely")
(name "Frank"))
(format t "~a,~%~a" closing name))
Sincerely,
Frank
Now, if you need to print multiple values, you can use ~{ and ~} to have format iterate over a list of values:
CL-USER> (let ((closing "Sincerely")
(names '("Frank" "John")))
(format t "~a,~{~%~a~}" closing names))
Sincerely,
Frank
John
If the names are the values of variables, then you can either create a list containing those values:
CL-USER> (let ((closing "Sincerely")
(name1 "Frank")
(name2 "John"))
(format t "~a,~{~%~a~}" closing (list name1 name2)))
Sincerely,
Frank
John
or you can change ~{ to ~#{ and have format read the remaining arguments as the list:
CL-USER> (let ((closing "Sincerely")
(name1 "Frank")
(name2 "John"))
(format t "~a,~#{~%~a~}" closing name1 name2))
Sincerely,
Frank
John
You should read a tutorial about format, from here for example
For an easy explanation
(format
(destination-stream usually t for standard output nil to form a string)
(here comes the string)
(&rest variables that should write in the string where is an ~A and by position like String.format in java or c))
in your case, you need the symbol ~% or use the character for return in common lisp
CL-USER> (defun closing (s) (format t "~A~%~%~A" "Sincerely," s))
CLOSING
CL-USER> (closing "paco")
Sincerely,
paco
NIL
The nil say that the functions returns null, and the other is the standard output if you want to return a string, put nil instead of t

Generic functions allow different order of arguments

I defined a generic function taking 2 arguments:
(defgeneric interact (a b))
The order of the arguments should not be important, so (interact x y) and (interact y x) should be the same, but I don't want to define two methods that do the same for every combination of different objects.
A Method-Combination of this type should help:
(defmethod interact :around (a b)
(if (some-function a b)
;;some-function has to be true if (eq (class-of a) (class-of b))
;;else (some-function a b) is (not (some-function b a))
;;similar #'<=
(call-next method)
(interact b a))
But I would have to know #'some-function and be able to know the type of the arguments I have to define.
Edit: both proposed approaches have a few limitations discussed in the comments below. Please read them before using this answer!
Can I suggest two options - a working but hacky option for when you only have two arguments, and a vaguely sketched out generic approach which I think should work but I haven't written:
Option 1:
(defparameter *in-interact-generic-call* nil)
(defgeneric interact (x y))
(defmethod interact ((x T) (y T))
; this can be called on pretty much anything
(if *in-interact-generic-call*
(cause-some-kind-of-error) ; Replace this with a more sensible error call
(let ((*in-interact-generic-call* T))
(interact y x))))
(defmethod interact ((x integer) (y string))
; example
(print x )(prin1 y))
(interact 5 "hello") ; should print 5 "hello"
(interact "hello" 5) ; should print 5 "hello"
;(interact "hello" "hello") ; should cause an error
Essentially the idea is to define a generic function which always matches anything, use it to try to swap the arguments (to see if that matches anything better) and if it's already swapped the arguments then to raise some kind of error (I've not really done that right here).
Option 2
Define the generic function as something like interact-impl. Actually call the standard function (defined by defun) interact.
In interact, define a loop over all permutations of the order of your arguments. For each permutation try calling interact-impl (e.g. using (apply #'interact-impl current-permutation).)
At least in sbcl, no matching arguments gives me a simple-error. You probably would want to do a more detailed check that it's actually the right error. Thus the code in interact looks something like
; completely untested!
(do (all-permutations all-permutations (cdr all-permutations))
(...) ; some code to detect when all permutations are exhausted and raise an error
(let (current-permutation (first all-permutations))
(handler-case
(return (apply #'interact-impl current-permutation))
(simple-error () nil)) ; ignore and try the next option
)
)
So what you are looking for is an arbitrary linear order on the class objects.
How about string order on class names?
(defun class-less-p (a b)
"Lexicographic order on printable representation of class names."
(let* ((class-a (class-of a))
(name-a (symbol-name class-a))
(pack-a (package-name (symbol-package name-a)))
(class-b (class-of b))
(name-b (symbol-name class-b))
(pack-b (package-name (symbol-package name-b))))
(or (string< pack-a pack-b)
(and (string= pack-a pack-b)
(string<= name-a name-b)))))

How to convert this code to Scheme

So basically this code's purpose is to simply print out the first n even numbers.
for (i = 0; i <=n; i+= 2)
{
print i;
}
Thing is though, I don't understand Scheme at all. So, help please.
There are several ways to convert the code in the question to Scheme. The first one I can think of:
(define (print-even n)
(let loop ((i 0))
(if (<= i n)
(begin
(print i)
(newline)
(loop (+ i 2))))))
Notice this:
The solution is written as a recursive procedure
Instead of a for loop, I'm using a construct called a named let, which permits the initialization of some iteration variables (i in this case, initialized to 0) and the repeated execution of a recursive procedure (loop in this case), producing an effect similar to a for, even in performance
The stop condition in the "loop" is handled with essentially the same expression: repeat the body of the iteration as long as (<= i n), when that condition becomes false, the iteration ends
The begin surrounds the body of the "loop", just as the curly braces {} do in the original code
The print procedure performs the expected operation; for readability I added a new line after printing each number
The increment part of the original loop i += 2 is handled by the expression (+ i 2), inside the recursive call
So you see, the process being executed is essentially the same, only the way to write it (the syntax!) is different. Give it a try, type this:
(print-even 6)
... And the following will get printed on the screen:
0
2
4
6
Another possible way to implement the procedure, more similar to the original code, although (this is completely subjective) less idiomatic than the previous one:
(define (print-even n)
(do ((i 0 (+ i 2))) ((> i n))
(print i)
(newline)))
Finally, if you're using Racket this will seem even more familiar to you:
#lang racket
(define (print-even n)
(for ((i (in-range 0 (+ n 1) 2)))
(print i)
(newline)))
The first big difference between Scheme and other languages is this: In Scheme, you do (almost) everything recursively.
To implement a simple loop, for instance, you would define a recursive function. This function would first check to see whether it's time to break out of the loop; if is is, it would return the final value. (There is no final value in this case, so it would just return something like (void) or '().) Otherwise, the function would do whatever it's supposed to do, then call itself again.
Any loop variables (such as i) become arguments to the function.
Hopefully this helps you understand how to do this.
The Scheme way to do something like this is using a recursive function like the one below.
(define (doit value n)
(if (<= value n)
(begin
;;...perform loop body with x...
(display value)(newline)
(doit (+ value 2) n))))
To call this function you call (doit 2 n) where n is your n in the for loop.
With regards to learning Scheme, I recommend the first two links below.
For additional information on Scheme see
SICP
How to Design Programs
Schemers
Related Stackoverflow Question
Scheme Cookbook Looping Constructs

Resources