Feature request: conditional imports

Recently, @kappelmann and I were discussing a notebook where it would be convenient to import a different notebook depending on user input, e.g. something like this (or maybe with more specialized syntax):

import {cell} with {...} from `${toggle 
  ? 'notebook1' 
  : 'notebook2'
}`

instead of:

import {cell as cell1} from 'notebook1'
import {cell as cell2} from 'notebook2'
cell = toggle ? cell1 : cell2

For instance if evaluating cell1 and cell2 require downloading large files, the latter will require that both sets of files are downloaded before cell resolves. In principle it’s possible to change things upstream to delay their execution (e.g. wrapping things in “thunks”) but this could require a lot of work.

The main obstacle to this suggestion is that it would require such an import cell to be treated at a “meta” level, since dependencies for some cells could change at “run time” rather than “compile time”. I haven’t thought too hard about this yet, but it does seem like variables which modify the dependency graph would have to be treated very carefully.

2 Likes

I don’t have a great answer for you right now, @bgchen — but I just wanted to let you know that this sort of thing is an important topic that we’ve been thinking through quite seriously. To briefly describe the problem space:

  • Observable notebooks are versioned, but can load code and data in many ways: import from a notebook, dynamic import(), require() from npm, fetch(), XMLHTTPRequest, DatabaseClient(), and more…
  • Some of those forms are amenable to static analysis, where we can determine (without running the code), what exact external resources a notebook depends on, and some are not.
  • Some forms are dynamic, where you can conditionally choose to load dependencies, or generate identifiers to load different resources, and some are not.
  • Some forms are lazy, where even if what you are going to load can be determined statically, you don’t have to actually load it until first use.
  • Some forms are versioned, where there are semantics around which version of the named resource you receive when you request it, and some just load by opaque identifier.
  • Some forms are cached, where if you request the same identifier multiple times, only one request will be made (and therefore unsuitable for frequently mutating resources).

Improvements to the status quo need to take these qualities and differing behaviors into account.

1 Like

Possibly related: "Recompute with...", or: a way to "climb the ladder of abstraction"

1 Like

If I might add my two cents: I think the current import with syntax should be deprecated and replaced with an import helper in the stdlib or runtime that works similarly to d3-require (and would likely just end up being a custom resolver?).

Let’s be honest, the current syntax is confusing as hell when you’re not familiar with this Observable feature:

  • syntax mimics static import syntax, but only supports a fraction of the actual syntax
  • allows only notebook imports
  • notebook imports look like npm package identifiers, but are notebooks exclusively hosted on observablehq.com
  • with syntax is completely non-standard (unless I’ve missed something?) and makes the whole thing even weirder

viewof and mutable are less problematic because they don’t redefine existing keywords.

4 Likes

+1 for this, what should one do for conditional imports right now?

This might help:

4 Likes

I imagine this may also apply to “lazy” imports. Right now notebook imports get transcoded to static imports, which means that every referenced notebook gets fetched, even if none of its cells are imported.

1 Like