Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there function prototypes in Common Lisp?

I have been programming in common lisp for a little while now, and throughout my experience using lisp, I have yet to see any function/macro that acts anything similar to function prototypes in C or C++.

Currently I have to very careful about the ordering of my functions, otherwise, when I try to call a function from another, Lisp says the function "does not exist" because it is defined later in the file. Is there a way to get around this? Can I declare all my function prototypes at the top of the file, and the full definitions below?

like image 312
Brooks Avatar asked Aug 30 '14 21:08

Brooks


2 Answers

Declaim and Proclaim

You can use declaim to globally declare that a certain thing has a certain function type. For instance, look at what happens first if you define foo1 that calls undefined baz (in SBCL):

CL-USER> (defun foo1 ()
           (baz))

; in: DEFUN FOO1
;     (BAZ)
; 
; caught STYLE-WARNING:
;   undefined function: BAZ
; 
; compilation unit finished
;   Undefined function:
;     BAZ
;   caught 1 STYLE-WARNING condition
FOO1

Now, let's add a declamation that says that baz is a function of no arguments, and returns something. You could obviously add more type information if you wanted to, but this will at least provide the arity and the knowledge that baz is a function.

CL-USER> (declaim (ftype (function () t) baz))
; No value

Now when you define foo2 that also calls baz, you'll get no warning:

CL-USER> (defun foo2 ()
           (baz))
FOO2

Declaim is a macro, but if you need to be able to generate some of these things at runtime, you can use proclaim, which is a function. E.g.,

CL-USER> (dolist (f '(square cube))
           (proclaim `(ftype (function (number) number) ,f)))
NIL
CL-USER> (defun add-square-and-cube (x y)
           (+ (square x) (cube y)))
ADD-SQUARE-AND-CUBE

That said, this is not very idiomatic Common Lisp. It's much more common to put the code you need into a file and then to compile that file and load it. If that's not possible for some reason, this will work, but it's worth considering other options of loading your code if they're available.

Muffling warnings

It's also worth noting that while SBCL will take the hint from proclaim or declaim and silence the undefined function warning, the function is still actually undefined. Other implementations (e.g., CLISP) will still issue a warning about the undefined function.

I don't really recommend the following approach, because warnings are around for a reason, but you can choose to muffle warnings when you evaluate code. E.g., in CLISP, we get a warning when we compile with undefined functions:

CL-USER> (compile nil (lambda () (baz)))
WARNING: Function BAZ is not defined
#<COMPILED-FUNCTION NIL>
1
1

We can bind a handler that will muffle any warnings that occur when the form is evaluated, though:

CL-USER> (handler-bind ((warning
                         (lambda (x)
                           (muffle-warning x))))
           (compile nil (lambda () (baz))))
#<COMPILED-FUNCTION NIL>
1
1

This has its ow caveats, too, since the type of warning that you might get for compiling a reference to an undefined function might vary, and what muffling the warning does may vary.

like image 67
Joshua Taylor Avatar answered Sep 28 '22 06:09

Joshua Taylor


As Rainer pointed out you can ignore this issue if the forward reference is within a single compilation unit. But that won't help if the forward reference crosses compilation units. Usually that's a sign that your code is poorly layered. Low level code makes calls to higher level code? Well, people will say the low level code is providing a hook for the high level code.

That said I certainly have seen and written code that had this problem. Spaghetti code, yum! It can arise when you start breaking huge source files into smaller ones. A compilation unit is usually a single file. But take a look at with-compilation-unit. I don't recall asdf doesn't provide easy access that though.

I don't know if Joshua's solution of using providing a declaration will work across all CL implementations. My memory is that when I had to solve this problem many many years ago we had to implement something more crude, we would give the function a stand in definition, and then hack together a way to suppress the definition warnings.

No doubt cl-launch could be used to see if Joshua's solution works across the spectrum of implementations.

like image 41
Ben Hyde Avatar answered Sep 28 '22 07:09

Ben Hyde