Why is shiny designed to call a function as a string?

As shown in the example code below, the function show_value is called in the ui.value_box as a string, rather then show_value().

If I change the name of this function, I need to find that string and change it manully.

I'm struggling with this while learning shiny for the following reason:

  1. It's conterintuitive regarding the way how a function is called.
  2. In popular code editors, such as neovim and vscode, you can use the keybinding gd ("go to definition") and gr ("go to reference") to quickly jump betwen where a function is defined and where it is used in the code.
  3. Also, in the popular code editors with LSP, once you change the name of a function, it will be change automatically at all the places it's called in the code. But this is not applicable for functions in shiny. You have to find all the strings of the function at the places it's called and change them manually, one by one. This makes navigating and editing the code inefficient as the code base grows.

May I ask why shiny for python is designed in this way? And, if there's away to make function name related navigation and editing more efficient?

from shiny import App, Inputs, Outputs, Session, render, ui, reactive, run_app

app_ui = ui.page_sidebar(
    ui.sidebar(
        ui.input_slider("n", "N", min=0, max=100, value=20),
    ),
    ui.value_box(
        'Title',
        ui.output_text('show_value'),
        'Subtitle',
    ),
)

def server(input, output, session):
    @render.text
    def show_value():
        return '12345'

app = App(app_ui, server)

You do not specify a function in ui.output_text('show_value') but a (partial) id! That you can define the function that will populate that id using the id as function name is just for convenience.

Consider using inputs, esp. multiple inputs in one function. The you can use an anonymous function (_()) and it will be bound to all inputs you use inside it (or you specify using a decorator). It's the same structure for outputs.

Thanks.

The following is the reply from Claude.ai regarding this question. Hope it's helpful for others having the same question while learning the reactive programming structure of shiny for python.

# Why Shiny for Python Uses String References in the UI

That's an excellent question about Shiny for Python's architecture. This design choice of referencing server-side reactive functions as strings in the UI might seem counterintuitive at first, especially if you're coming from other Python frameworks.

## The Core Reason: Execution Order and Dependency Tracking

The main reason is related to how Shiny implements its reactive programming model:

1. **Execution Timing**: The UI is constructed first, before the server function even runs. When you define `app_ui`, the server's reactive functions don't exist yet as runtime objects.
2. **String-Based Binding System**: Shiny creates a binding system where UI elements and server functions connect via string identifiers rather than direct object references.
3. **Reactive Graph Construction**: Shiny builds a dependency graph of reactive elements at runtime. Using strings allows Shiny to:
  * Establish input/output relationships without actual function execution
  * Defer evaluation until needed
  * Track dependencies between reactive elements

Why Not Direct References?

This doesn't work because:

1. **Cross-Module Dependencies**: The server function hasn't been executed yet when UI is defined
2. **Reactive Context**: Functions like `result()` need to be evaluated within the server's reactive context
3. **Runtime Flexibility**: String references allow dynamic construction of the reactive network

## R Heritage Influence

This design also comes from Shiny's R heritage. The Python version maintains similar patterns to make it familiar for R users and to preserve the reactive programming model that makes Shiny powerful.

## Benefits of This Approach

Despite initially seeming unusual, this approach offers advantages:

* **Clear separation** between UI layout and application logic
* **Lazy evaluation** of reactive expressions only when needed
* **Automatic dependency tracking** without manual declarations
* **Dynamic reactivity** where the dependency graph adapts at runtime