Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
digikar99 opened this issue Sep 10, 2020 · 15 comments

Comments

@digikar99
Copy link

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
Copy link
Owner

Bike commented Sep 11, 2020

Maybe. What would this be useful for?

@digikar99
Copy link
Author

digikar99 commented Sep 11, 2020

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
Copy link
Owner

Bike commented Sep 11, 2020

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
Copy link
Author

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
Copy link
Owner

Bike commented Sep 15, 2020

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
Copy link
Author

😅 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
Copy link
Owner

Bike commented Sep 15, 2020

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
Copy link
Author

digikar99 commented Sep 15, 2020

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
Copy link
Owner

Bike commented Sep 15, 2020

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
Copy link
Author

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
Copy link
Owner

Bike commented Sep 15, 2020

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
Copy link
Author

Hmm, true. Thanks for the pointers!

@digikar99
Copy link
Author

digikar99 commented Mar 14, 2021

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
Copy link
Owner

Bike commented Mar 14, 2021

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
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants