Page 1 of 1

Macro to construct function names dynamically

Posted: Thu Jul 28, 2011 6:49 am
by foregam
Hello,

I'm new to Lisp and my previous programming experience was mostly C, Perl and shell scripts. I'm still heavily influenced by it so please bear with me.
So: Is there a way to define a macro which constructs a function name when given a list of symbols? I'd like to call it like this:

Code: Select all

(do-expand-functions '(first second third fourth fifth) '(a b c) (list foo bar baz quux))
and get as a result

Code: Select all

(progn
  (do-function-a-first foo)
  (do-function-a-second foo)
  (do-function-a-third foo)
  (do-function-a-fourth foo)
  (do-function-a-fifth foo)
  (do-function-b-first bar)
  (do-function-b-second bar)
  .
  .
  .
  (do-function-c-fifth quux))
How do I construct the do-function-* names? Once I get past that the rest is easy. Is there a common idiom for this kind of things? If I were using Bash I'd probably say

Code: Select all

for I in a b c; do
    for J in first second third; do
        for K in foo bar baz quux; do
            eval do_function_${I}_${J} $K
        done
    done
done
although it's not quite the same.

Thank you in advance.

Re: Macro to construct function names dynamically

Posted: Thu Jul 28, 2011 10:34 am
by nuntius
Here are a few tools at your disposal:

Code: Select all

(symbol-name 'a) ; "A"
(string 'A); "A"
(format nil "~{~A~}" '(a b c)) ; "ABC"
(intern "C") ; 'C
(make-symbol "D") ; '#:D
The first three lines show ways of getting strings from symbols; the second show the opposite transform. There are several things to be careful of here; you might want to call these inside with-standard-io-syntax, bind *print-case*, etc. There are a couple existing functions to do this but none have widespread popularity.

Part of the problem is that people can customize the CL reader and printer. Part of the problem is getting the symbols in the right package. In the end, some experienced users don't like machine-generated names. They prefer APIs which allow you to specify the name for everything (see for example defstruct vs defclass). Also, it is often better to use generic functions or a data structure (e.g. plist, hashtable, or array) than constructed names.

Re: Macro to construct function names dynamically

Posted: Fri Jul 29, 2011 12:41 am
by Kompottkin
foregam wrote:

Code: Select all

(do-expand-functions '(first second third fourth fifth) '(a b c) (list foo bar baz quux))
Since it looks like you want the arguments to be evaluated at run-time, you may want a function rather than a macro.

In that case, you can do it basically the same way as in Bash, except without using eval. In addition to the functions that nuntius has referenced, funcall is your friend.

Re: Macro to construct function names dynamically

Posted: Fri Jul 29, 2011 4:46 am
by foregam
Thank you both very much for your concise answers. @Kompottkin, no, I actually want it as a shorthand for writing a big number of similarly looking lines by hand, which I consider a very likely source of errors. Maybe I should refactor my source further on. The Bash example I gave was simplified to make my general intent clear.

Re: Macro to construct function names dynamically

Posted: Tue Aug 30, 2011 7:18 pm
by adam33147
Here is a version of a macro which creates function calls according to every possable combination of concatination of the symbols in symbol-list as the functions, and every possable concatination of the args. If an atom is applied anywhere, it is included in its place according to the pattern.

Code: Select all

(defmacro funcall-matrix (symbol-list args-list)
	   (let ((functions (apply #'symbol-matrix symbol-list))
		 (args (apply #'map-each-to-every args-list)))
	     (cons 'list
		   (mapcar #'(lambda (elt)
			       (cons (car elt) (cadr elt)))
			   (map-each-to-every functions args)))))

(defun symbol-matrix (&rest symbols)
	   (mapcar 
	  #'(lambda (symbol-list)
	      (apply #'symbol-concat symbol-list))
	  (apply #'map-each-to-every symbols)))

(defun map-each-to-every (&rest atoms-or-lsts)
	   (labels ((mp (&rest lsts)
		      (if (cdr lsts)
			  (apply #'mp
				 (cons 
				  (apply #'append
					 (mapcar #'(lambda (each-elt)
						     (mapcar #'(lambda (every-elt)
								 (append each-elt (list every-elt)))
							     (ensure-list (second lsts))))
						 (ensure-list (first lsts))))
				  (cddr lsts)))
			  (first lsts))))
	     (apply #'mp 
		    (cons (mapcar #'list (ensure-list (car atoms-or-lsts)))
			  (cdr atoms-or-lsts)))))

(defun symbol-concat (&rest symbols)
	   (intern
	    (apply #'string-concat
		   (mapcar 
		    #'(lambda (elt)
			(if (symbolp elt)
			    (symbol-name elt)
			    (write-to-string elt)))
		    symbols))))

(defun ensure-list (elt)
	   (if (listp elt)
	       elt
	       (list elt)))

For example, calling
>(macroexpand '(funcall-matrix ((a b c) - (1 2 3 4)) ((arg-a arg-b) 15 (arg-x arg-y arg-z))))

(LIST (A-1 ARG-A 15 ARG-X) (A-1 ARG-A 15 ARG-Y) (A-1 ARG-A 15 ARG-Z)
(A-1 ARG-B 15 ARG-X) (A-1 ARG-B 15 ARG-Y) (A-1 ARG-B 15 ARG-Z) (A-2 ARG-A 15 ARG-X)
(A-2 ARG-A 15 ARG-Y) (A-2 ARG-A 15 ARG-Z) (A-2 ARG-B 15 ARG-X) (A-2 ARG-B 15 ARG-Y)
(A-2 ARG-B 15 ARG-Z) (A-3 ARG-A 15 ARG-X) (A-3 ARG-A 15 ARG-Y) (A-3 ARG-A 15 ARG-Z)
(A-3 ARG-B 15 ARG-X) (A-3 ARG-B 15 ARG-Y) (A-3 ARG-B 15 ARG-Z) (A-4 ARG-A 15 ARG-X)
(A-4 ARG-A 15 ARG-Y) (A-4 ARG-A 15 ARG-Z) (A-4 ARG-B 15 ARG-X) (A-4 ARG-B 15 ARG-Y)
(A-4 ARG-B 15 ARG-Z) (B-1 ARG-A 15 ARG-X) (B-1 ARG-A 15 ARG-Y) (B-1 ARG-A 15 ARG-Z)
(B-1 ARG-B 15 ARG-X) (B-1 ARG-B 15 ARG-Y) (B-1 ARG-B 15 ARG-Z) (B-2 ARG-A 15 ARG-X)
(B-2 ARG-A 15 ARG-Y) (B-2 ARG-A 15 ARG-Z) (B-2 ARG-B 15 ARG-X) (B-2 ARG-B 15 ARG-Y)
(B-2 ARG-B 15 ARG-Z) (B-3 ARG-A 15 ARG-X) (B-3 ARG-A 15 ARG-Y) (B-3 ARG-A 15 ARG-Z)
(B-3 ARG-B 15 ARG-X) (B-3 ARG-B 15 ARG-Y) (B-3 ARG-B 15 ARG-Z) (B-4 ARG-A 15 ARG-X)
(B-4 ARG-A 15 ARG-Y) (B-4 ARG-A 15 ARG-Z) (B-4 ARG-B 15 ARG-X) (B-4 ARG-B 15 ARG-Y)
(B-4 ARG-B 15 ARG-Z) (C-1 ARG-A 15 ARG-X) (C-1 ARG-A 15 ARG-Y) (C-1 ARG-A 15 ARG-Z)
(C-1 ARG-B 15 ARG-X) (C-1 ARG-B 15 ARG-Y) (C-1 ARG-B 15 ARG-Z) (C-2 ARG-A 15 ARG-X)
(C-2 ARG-A 15 ARG-Y) (C-2 ARG-A 15 ARG-Z) (C-2 ARG-B 15 ARG-X) (C-2 ARG-B 15 ARG-Y)
(C-2 ARG-B 15 ARG-Z) (C-3 ARG-A 15 ARG-X) (C-3 ARG-A 15 ARG-Y) (C-3 ARG-A 15 ARG-Z)
(C-3 ARG-B 15 ARG-X) (C-3 ARG-B 15 ARG-Y) (C-3 ARG-B 15 ARG-Z) (C-4 ARG-A 15 ARG-X)
(C-4 ARG-A 15 ARG-Y) (C-4 ARG-A 15 ARG-Z) (C-4 ARG-B 15 ARG-X) (C-4 ARG-B 15 ARG-Y)
(C-4 ARG-B 15 ARG-Z))

I remeber when I started programming in Lisp, I was comming from Java and implemented a simple OO system. Its funny how influneced you are by the languages you use.