Reading lambda expressions from keyboard in Common Lisp
Asked Answered
D

4

8

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.

Derringer answered 13/6, 2019 at 13:39 Comment(0)
C
2
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>
Condyloid answered 13/6, 2019 at 15:51 Comment(0)
L
9

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
Let answered 13/6, 2019 at 14:4 Comment(0)
A
8

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.

Almsman answered 13/6, 2019 at 14:14 Comment(0)
A
3

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.

Alfonsoalfonzo answered 13/6, 2019 at 13:52 Comment(0)
C
2
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>
Condyloid answered 13/6, 2019 at 15:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.