I'm trying to write a timeline generator that takes a plist with times and functions to execute:
(defun run-timeline (timeline)
"Timeline is a plist with alternating timecodes and code to execute"
(alexandria:doplist (time form timeline)
(sb-ext:schedule-timer
(sb-ext:make-timer form
:thread t)
time)))
(run-timeline '(0 (print "first")
2 (print "second")))
This, however, gives me this error:
The value
(PRINT "first")
is not of type
(OR FUNCTION SYMBOL)
[Condition of type TYPE-ERROR]
Restarts:
0: [USE-VALUE] Use specified value.
1: [ABORT] abort thread (#<THREAD tid=203285 "Timer NIL" RUNNING {1001179523}>)
Backtrace:
0: (SB-VM::CALL-SYMBOL)
1: ((FLET SB-THREAD::WITH-RECURSIVE-LOCK-THUNK :IN SB-IMPL::MAKE-CANCELLABLE-INTERRUPTOR))
2: ((FLET "WITHOUT-INTERRUPTS-BODY-" :IN SB-THREAD::CALL-WITH-RECURSIVE-LOCK))
3: ((FLET "WITHOUT-INTERRUPTS-BODY-1" :IN SB-IMPL::MAKE-CANCELLABLE-INTERRUPTOR))
4: ((LAMBDA NIL :IN SB-IMPL::MAKE-CANCELLABLE-INTERRUPTOR))
5: ((FLET SB-UNIX::BODY :IN SB-THREAD::RUN))
6: ((FLET "WITHOUT-INTERRUPTS-BODY-" :IN SB-THREAD::RUN))
7: ((FLET SB-UNIX::BODY :IN SB-THREAD::RUN))
8: ((FLET "WITHOUT-INTERRUPTS-BODY-" :IN SB-THREAD::RUN))
9: (SB-THREAD::RUN)
10: ("foreign function: call_into_lisp_")
When hard-coding a lambda expression I get no errors, and it works as expected:
(defun run-timeline (timeline)
"Timeline is a plist with alternating timecodes and code to execute"
(alexandria:doplist (time form timeline)
(sb-ext:schedule-timer
(sb-ext:make-timer (lambda ()
(print "hi")
(force-output))
:thread t)
time)))
What am I missing here?
The sb-ext:make-timer
function expects a function designator, which is either a symbol (see FBOUNDP
) or the result of evaluating a (function ...)
form (see FUNCTION
). The notation #'xyz
for example is a shortcut for (function xyz)
and refers to the function value currently associated with symbol xyz
. Likewise, (lambda () (print :ok))
is a shortcut to (function (lambda () (print :ok)))
which evaluates to an anonymous function (or closure).
In your case, make-timer
is given a list, namely (print "first")
. You have to make sure that you transform that list to a function, or you may want to change the values that your property list holds. This one is simpler, and for example you can call your current function with:
(run-timeline (list 0 (lambda () (print "first"))
2 (lambda () (print "second"))))
Note that I dropped the quote character, because you need to evaluate the forms to have a function.
Alternatively, you can keep this syntax:
(run-timeline '(0 (print "first")
2 (print "second")))
But you have to change the implementation so that you can transform any form into a function. For example, you can write the following to produce a form that can be evaluated to a function:
(eval `(lambda () ,form))
The comma injects the current value of form, for example (print "first")
, into the surrounding quasi-quoted form, to obtain the list (lambda () (print "first"))
. The surrounding call to eval
transform this into an actual function object (by compiling it, for example, but this is not necessary).
There is another way of transforming a lambda form to a function which is as follows (see COERCE
):
(coerce `(lambda () ,form) 'function)