CL-USER 100 > (defun unroll (n exp) `(lambda () ,(loop repeat n collect exp)))
UNROLL
CL-USER 101 > (unroll 3 '(foo))
(LAMBDA NIL ((FOO) (FOO) (FOO)))
CL-USER 102 > (eval *)
#<anonymous interpreted function 8020000EC9>
CL-USER 103 > (describe *)
#<anonymous interpreted function 8020000EC9> is a TYPE::INTERPRETED-FUNCTION
CODE (LAMBDA NIL ((FOO) (FOO) (FOO)))
As you can see, the thing is basically the same as a cons cell with two entries the type and the code:
(TYPE::INTERPRETED-FUNCTION . (LAMBDA NIL ((FOO) (FOO) (FOO))))
The above Lisp implementation does not use a cons cell, but a different type mechanism to easily and reliably identify the runtime type.
I picolisp this is hardwired into the interpreter. The interpreter will also need to check every time at runtime, if the list structure is actually a function and what kind of function it is.
In above Lisp, the type of the function is encoded during EVAL and the check for the type is then a type tag check.
for this example here, using the LispWorks implementation, it also makes no difference for EVAL if it is a function with 10 or with 100000 subforms. The execution time is small. No special processing of the list of subforms takes place. For example the code is not compiled, not converted to byte code, not converted to another representation.
CL-USER 111 > (let ((f (unroll 10 '(foo)))) (time (eval f)))
Timing the evaluation of (EVAL F)
User time = 0.000
System time = 0.000
Elapsed time = 0.000
Allocation = 184 bytes
0 Page faults
GC time = 0.000
#<anonymous interpreted function 8020001DA9>
CL-USER 112 > (let ((f (unroll 100000 '(foo)))) (time (eval f)))
Timing the evaluation of (EVAL F)
User time = 0.000
System time = 0.000
Elapsed time = 0.000
Allocation = 184 bytes
0 Page faults
GC time = 0.000
#<anonymous interpreted function 8020000839>
CL-USER 113 > (defun unroll (n exp) `(lambda () ,(loop repeat n collect exp)))
UNROLL