Bike / introspect-environment

CL environment introspection portability layer
35 stars 5 forks source link

Distinguishing between a function closed over a null-lexical environment vs a non-null one #4

Open digikar99 opened 4 years ago

digikar99 commented 4 years ago

On SBCL, I just learnt that, there is sb-kernel:closurep.

On CCL, type-of simply returns compiled-lexical-closure vs function.

Perhaps, these, along with other implementations, could be unified into a single function?

Bike commented 4 years ago

Maybe. What would this be useful for?

digikar99 commented 4 years ago

With SBCL, compiling the following produces the note lexical environment too hairy, can't inline DEFUN FOO

(let ((a 5))
  (declare (inline foo))
  (defun foo () a))

I wanted to do something similar while using compiler macros. Is there some other way to go about it?

Bike commented 4 years ago

Inlining closures is conceptually difficult. If you had another function that mutated a, would you expect inlined calls in foo wherever else to get the mutated a value?

digikar99 commented 4 years ago

Ah, so, I want to check if a function is a closure, so I could avoid inlining/optimizing it; and inline only if it is not a closure.

Bike commented 4 years ago

I don't understand. If you're talking about looking at the actual function object, that only exists after whatever macroexpansion here declares it inline. And you could just declare it inline unconditionally and let sbcl decide not to inline it.

digikar99 commented 4 years ago

😅 The full story: I'm trying to make typed-dispatch that intends to provide a syntax like

(define-typed-function my= (a b)) 
(defun-typed my= ((a string) (b string))
  (string= a b)) 
(defun-typed my= ((a character) (b character))
  (char= a b))

with the internal structure comprising of lambdas, defstructs and hash tables. I also intend to provide an inlining ability; and one factor about whether to inline the concerned lambda function is whether the lambda is a closure. And hence, the need for something like "closurep".

Bike commented 4 years ago

Do you mean you're doing your own inlining? Like, you save the definition form and insert it into the call site with a compiler macro or whatever? In that case checking if the function is a closure might not be enough. For example, in my SBCL, (let ((a 4)) (lambda () a)) is not a closure, because SBCL is smart enough to propagate the constant; but if you insert (lambda () a) into code elsewhere it will have an unbound variable.

digikar99 commented 4 years ago

Hmm, as of SBCL commit 501633af38d (post SBCL 2.0.8), (sb-kernel:closurep (let ((a 4)) (lambda () a))) does return T; although in a recent discussion, stassats did mention that that lambda is not closed over anything; perhaps he meant what you are saying.

Bike commented 4 years ago

just rebuild sbcl HEAD, still not a closure for me. But whatever. The point is that it sounds like what you want is a free variable analysis. closurep is not that, it deals with the runtime representation of the function, and is internal and can vary with implementation strategies.

digikar99 commented 4 years ago

If a function has free (lexical) variables, then it is a closure, right? Am I missing something?

Any other way you know of this could be done?

Bike commented 4 years ago

my example was intended to demonstrate that a function that, in source code, has free variables need not be a closure. Hopefully you can see that an implementation can process (let ((a 4)) (lambda () a)) exactly the same way as it would (lambda () 4), i.e. not as a closure.

What you want is probably a code walker or other heavier duty compilation tool.

digikar99 commented 4 years ago

Hmm, true. Thanks for the pointers!

digikar99 commented 3 years ago

TIL: There is (iterate::free-variables form) that could be "light-weight" (and may be somewhat pretty restrictive) if someone is already using iterate elsewhere.

Bike commented 3 years ago

there are a few libraries with things like that and they're generally not completely accurate. for example, this function (https://github.com/lisp-mirror/iterate/blob/master/iterate.lisp#L2085-L2147 here for reference) doesn't seem to account for destructuring lambda lists in macrolet, and more fundamentally does not expand macrolet macros, which can in general introduce new free variables (though this is rare in practice i would say). And it doesn't handle implementation-specific special operators at all, of course. Since iterate only seems to use it for a syntax check it's probably fine for its purposes, though.

digikar99 commented 3 years ago

Yup, been using hu.dwim.walker, but it accounts for about half the size of the files generated in the ~/.cache folder. No proper alternative until implementations could emit a special condition for undefined variables.