nine_k 3 days ago

Nice! This approach can be used to implement coeffects (as e.g. seen in Hack [1]), by only passing explicit effect-producing objects. Imagine a function that's guaranteed to not write to any files, because it can't access `open()`, or can only access a version of `open()` that only accepts read-only access.

[1]: https://docs.hhvm.com/hack/contexts-and-capabilities/introdu...

kazinator 2 days ago

Simple localscope in TXR Lisp:

  $ txr -i localscope.tl
  Pour le service en la langue Shell, appuyez sur Ctrl-D.
  1> (localscope (+ %pi% %pi%)) ;; OK, %pi% is built-in
  6.28318530717959
  2> (localscope *print-base*) ;; likewise
  10
  3> (localscope (+ a b c)) ;; bad
  ** expr-3:1: localscope: global variables (c b a) used
Code:

  (defmacro localscope (:form f :env e . forms)
    (let ((body ^(progn ,*forms)))
      (tree-bind (exp fv-inner ff-inner fv-outer ff-outer) (expand-with-free-refs
                                                             body e)
        (ignore ff-inner fv-outer ff-outer)
        (let* ((usr (find-package :usr))
               (globals [keep-if (do or (not (boundp @1))
                                        (neq (symbol-package @1) usr))
                                 fv-inner]))
          (if globals
            (compile-error f "global variables ~s used" globals)))
        body)))
We get a list of the free variable references emanating from the enclosed forms, and filter them: we are interested in all that do not have bindings as globals in the standard library. I.e. either are not bound at all, or else if they are, are not symbols in the usr: ("user space") package.
  • kazinator 2 days ago

    Of course, we need test cases like this:

      1> (let (x) (localscope (+ x y)))
      ** expr-1:1: localscope: global variables (y) used
mpeg 2 days ago

I wrote myself a similar decorator for a completely different purpose – ensuring a function I'm going to serialise over the network doesn't have any outside dependencies

This is actually a cleaner API so might switch my code to it, amazing work

veilgen a day ago

This looks like a handy tool, especially for catching unintended global variable access in environments like Jupyter notebooks, where scope bugs can easily creep in. The ability to disassemble functions and analyze their dependencies could be particularly useful for debugging and enforcing best practices in functional programming, as seen in JAX.

Would be interesting to see how it compares to static analysis tools like mypy or linters—does it catch edge cases they might miss? Nice work!

dleeftink 3 days ago

> Everything works nicely, and you package the code in a function for later use but forget about the scale factor introduced earlier in the notebook.

You see a problem, you fix it with library, and I applaud that. You have to wonder though, how many years does it take for a reproducible notebook environment to implement out of scope variable guards..

  • jampekka 3 days ago

    Jupyter-style notebooks are a good example of a deep architectural mistake that needs hacks on hacks on hacks to remain barely serviceable.

    Luckily there are new approaches, e.g. Marimo and Pluto, that don't have the same root issue.

    • tillahoffmann 3 days ago

      Yes, Jupyter notebooks are flawed (there's a great talk at https://www.youtube.com/watch?v=7jiPeIFXb6U). But they are also very convenient for scrappy work and exploratory data analysis.

      • jampekka 2 days ago

        Notebooks can be convenient and have a consistent state at the same time, as done in e.g. Marimo.

  • nerdponx 3 days ago

    This is for people who don't want to switch notebook environments, because Jupyter(lab) is getting better faster than alternatives (which support things like reactive cell execution) are becoming usable for day-to-day work.

    It's just a safeguard for well-intentioned people to prevent themselves from making mistakes with their existing tools, instead of changing to a completely different set of tools.

escapecharacter 3 days ago

Since I've started using Jupyter notebooks, I've wanted a feature to "undo" running a cell. This feels so important to for spontaneous exploration. This work feels like an important building block for this!

  • Vaslo 2 days ago

    Only solved by the ever efficient clear outputs, restart, then run all the code all over again…

  • mythrowaway49 3 days ago

    agree! I feel like there must be a good workaround. Currently, I just need to go back and run a bunch of cells again..

nathan_compton 3 days ago

I have this idea of tools which help you do the right thing and tools that let you do the wrong thing longer. Jupyter is already the latter sort of tool, and this is the bad kind of tool on top of the bad kind of tool.

  • anitil 2 days ago

    I think it's a neat approach to a practical problem. I'd love an equivalent in C to be able to smoke out this sort of issue (though of course we'd have to fix 'errno' first)

bpshaver 2 days ago

> Have you ever hunted bugs caused by accidentally using a global variable in a function in a Jupyter notebook?

Nope!

> Have you ever scratched your head because your code broke after restarting the Python kernel?

Also nope!

I'm technically a data scientist but I don't use notebooks very often. Even when I did, though, I can't recall having this error very often. Is this really necessary? Anyway, this kind of thing is only useful if people adopt it. The people who are likely to install this tool and use it probably already avoid this issue while the people who have this issue will never hear of this tool or will not consistently employ it.

I guess I'm just salty from years of dealing with people who give their Python code half-hearted incorrect type hints but never run a static type checker on their code.

  • codetrotter 2 days ago

    > Anyway, this kind of thing is only useful if people adopt it. The people who are likely to install this tool and use it probably already avoid this issue while the people who have this issue will never hear of this tool or will not consistently employ it.

    I could see projects adopting this, or something like it, in their CI pipelines.

    Both for open source projects, but also at companies with a lot of internal repositories with Python code – especially if it is common that other people with little experience for some repo contribute code to it.

    In the case of an open source project if it’s a popular repo that a lot of people are users of and different people frequently contribute to for the first time.

    And in the case of company internal repositories, if it’s code that different teams only touch now and then, with only a smaller group of people intimately familiar with all of the inner workings of the code and the other people being mainly focused on other repos in the company but still having to sometimes contribute changes because their code is relying on the code in that other repos.

serial_dev 3 days ago

But how can you make sure that people annotate their functions with localscope?

  • zahlman 2 days ago

    It's a decorator, not an annotation.

    The decorator syntax is purely sugar:

      @foo
      def bar():
          pass
    
    is equivalent to

      def bar():
          pass
      bar = foo(bar)
    
    except that `bar` only gets assigned once.

    It's possible to use metaprogramming to apply the decorator automatically to every function (by iterating over attributes of the module and filtering for functions).

  • igorguerrero 3 days ago

    Same question people have with hinting, what makes Python cool is how loose it is... You could also make a lint rule for that and make fail CI if that rule failed, or... Write your own compiler you know?

  • goodki-d 2 days ago

    it's pretty easy to write a lint rule for that

pkaodev 3 days ago

Maybe I'm being naive, but it feels like if this is a problem for people they should be looking at how they write their code, not reaching for a library.

  • disgruntledphd2 3 days ago

    This bug basically hits people who run long lived code sessions with large objects (i.e. data people).

    I've hit it in both R and Python in interactive sessions. Otherwise it's generally a non issue.

    I can definitely see this being helpful, particularly for the intended context. It would also be useful when you need to move notebook code to a different environment.

    • RadiozRadioz 3 days ago

      Completely. I interact with data people sometimes at work. There are bespoke one-of-a-kind venvs and giant notebooks everywhere. But that's okay. They don't have our job, it is not their job to build robust & properly deployed software. They know how to do their thing and we shouldn't lecture them on how to do it the "proper" way. Because I sure don't know how to do what they do - Pandas & matplotlib take me hours of googling to do anything.

      I completely see the value in this tool for a particular style of programming.

      • dleeftink 3 days ago

        I don't think the issue is telling someone how to do their jobs, but that when it comes to it, we are not properly communicating our stack preferences at all. Online we do, sure, but let's not shy away from having these discussions in the open. There is much to learn from each other's best and worst practices.

  • aiono 3 days ago

    Software is complex and our memory is bottleneck to create software. We can only remember so many things therefore anything that we can make computer to "think" instead of us we have more memory to use for other work. In this case, instead of worrying if you are accessing some random global variable by accident is unnecessary cognitive work you. Why not just let computer do it while you think for actual things?

    • Yoofie 2 days ago

      Congratulations, you just described the primary reasons for using languages like Rust - the polar opposite of Python & co.

  • anitil 2 days ago

    There's been times when I've been dumped some script and told to get it in production. Yes it would've been better not to need this, but it's a handy first step.

matt3210 2 days ago

If you don't want pyhton flavored scope and lifetime rules, use another language :emothonless-face: