Skip to content

Scoping with functions? #6

Open

Description

@jasongrout

@erawn - thanks again for introducing me to this project at the JupyterCon sprint. I think it is a very interesting innovative way to tackle some of the messiness of notebooks.

At the sprint, we talked about some of the ways to approach this problem technically. I was thinking of another way to do it that might be closer to how normal python code is written and offers the user local scoping, but maybe a bit far from the typical notebook paradigm. Anyway, food for thought:

What if the scoping here was just actual Python functions, and the notebook UI let us expand or collapse a function into subcells that broke out each line of the function? For example, I could have a cell with a single function:

def myfun():
    df = pd.read_csv('myfile.csv')
    df.plot()
    s = df.summary()
    return s

Just a normal Python function that already has scoping. Then some affordance in the UI expands this into a page-break like indented block where each top-level statement in the function is a new cell. I can play around with it, run it line by line, etc., then click a button to have it collapse back to a function in a single cell.

This would be a lot easier in languages that have explicit scoping mechanisms, like C++ braces. There the pages idea could be very natural - just put braces around a page.

Activity

  1. erawn commented on Nov 13, 2025

    @erawn
    Owner

    @jasongrout Thank you for the lovely conversation! I'm feeling re-energized to turn this into something more than a prototype :)

    I agree that the interface affordance should support transitioning from a regular function into a pagebreak (and vice-versa). I think I'm wondering how you're imagining python functions as providing the scopes under the hood though (maybe you aren't suggesting this, and I've misunderstood).

    For example, if I have a function like:

    def myfun(inputarg):
          x = inputarg + 1 
          return x 
    

    What are you imagining when I run just x=inputarg+1 as a cell and that is sent to the kernel? Without the function being called, we don't have a real value for inputarg yet. Once the object myfun is instantiated it would have a closure from which we could reference those variables, but I'm unclear on how to handle the input arguments.

    This question seems to be easier when we start with a pagebreak without input arguments like this:

    pagebreak:
          x = globalarg + 1 
     
    pagebreak_export x
    

    and then we could introduce a context menu option to right click onglobalarg and select make into pagebreak argument or something and we could make the global argument a default, so x = inputarg + 1 could still be run individually, but pagebreaks would supply globalarg in the local scope as inputarg:

    pagebreak (inputarg=globalarg):
          x = inputarg + 1 
     
    pagebreak_export x
    

    While this gives us a nice interaction to define default arguments, it still doesn't help us run just a section of an instantiated function. While we could construct a function definition on the fly, and manually insert variables into the closure, it seems a little dangerous.

    Right now it seems like the solution we discussed last week—pushing and popping dictionaries of state depending on which cell is being run—seems like decent solution which feels fairly natural. As I'm writing this I'm wondering now if you're suggesting that we keep this implementation approach but the interface looks mostly like a regular function, but its not the literal function object that is providing the scope from the interpreter's perspective (but from a ui perspective, the function is what delimits the scope). If so, I think that would be a good paradigm.

    One thought then is whether the right implementation for Pagebreaks would be to define a new notebook type with a custom cell type to handle this, so you could have a "header" cell which includes def myfun(inputarg): (and a corresponding footer cell for the return). This would let people use syntax they're already familiar with, but the notebooks wouldn't be executable without the extension then (maybe this is a good thing, as failing obviously is probably better than failing silently). One design choice I made in the current iteration is that the "export" or returns in a pagebreak aren't runnable, because I didn't want people to face confusion when referencing a variable inside and outside a pagebreak returned different values. One danger perhaps of using a "return" syntax is that it would imply having to run it. Not a huge danger perhaps, but if we're trying to mirror the semantics of how functions normally work (and communicate that to the user), it might be more confusing when a pagebreak works differently.

    I will keep thinking about this! Thank you for the thoughts!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions