Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading lambda expressions from keyboard in Common Lisp

I want to be able to read a lambda expression from the keyboard. For example, if a function square has already been DEFUNed I can enter the symbol name:

(defun square (x) (* x x))

so that when the following is evaluated:

(funcall (read) 2)

the user can type square and the result is 4. But if the user types

(lambda (x) (* x x))

the result is an error, for example in Macintosh Common Lisp,

Error: (LAMBDA (X) (* X X)) can't be FUNCALLed or APPLYed

Is there an easy way to do this I am missing?

Thanks.

like image 614
PaulM Avatar asked Jun 13 '19 13:06

PaulM


4 Answers

read returns a list which has to be evaluated before it can be funcalled.

This can be accomplished using read-time evaluation:

(funcall (read) 2)
#.(lambda (x) (* x x))
==> 4

However, generally speaking, this is a security hole (you are evaluating user-supplied code - what if they typed #.(start-nuclear-war)?) so a cautious engineer will bind *read-eval* to nil when reading input they have no control over.

Thus it is much better to use coerce explicitly:

(funcall (coerce (let ((*read-eval* nil)) (read)) 'function) 2)
1+
==> 3
(funcall (coerce (let ((*read-eval* nil)) (read)) 'function) 2)
(lambda (x) (* x x))
==> 4
like image 198
sds Avatar answered Sep 24 '22 08:09

sds


Since you are using read, in general you would need to evaluate the returned forms to obtain meaningful values. But in your particular case, you can use COERCE. For example, from a REPL:

CL-USER> (coerce '+ 'function)
#<FUNCTION +>

The above finds the function to which the symbol + is fbound.

CL-USER> (coerce '(lambda (x) (* x x)) 'function)
#<FUNCTION (LAMBDA (X)) {53F2BF2B}>

The above takes a lambda expression and turn it into a function object.

like image 29
coredump Avatar answered Sep 25 '22 08:09

coredump


You get that error because READ returns just the list (LAMBDA (X) (* x x)), it does not evaluate it to a function. To do that, you would need to write:

(funcall (eval (read)) 2)

Note though that in that case, just writing square doesn't work anymore, the user would now need to enter #'square.

like image 43
Rörd Avatar answered Sep 24 '22 08:09

Rörd


CL-USER 8 > (defun read-function (&optional (stream *standard-input*))
              (let ((f (read stream)))
                (cond (; function object
                       (functionp f) f)
                      (; symbol naming a function
                       (symbolp f) (symbol-function f))
                      (; (function f)
                       (and (consp f)
                            (eq (first f) 'function))
                       (eval f))
                      (; (lambda ...)
                       (and (consp f)
                            (eq (first f) 'lambda))
                       (eval f)))))
READ-FUNCTION

Examples:

CL-USER 9 > (read-function)
#.#'+
#<Function + 40F0044AD4>

CL-USER 10 > (read-function)
+
#<Function + 40F0044AD4>

CL-USER 11 > (read-function)
#'+
#<Function + 40F0044AD4>

CL-USER 12 > (read-function)
(lambda (a b) (+ a b))
#<anonymous interpreted function 4060000C8C>
like image 34
Rainer Joswig Avatar answered Sep 24 '22 08:09

Rainer Joswig