Pluto.jl presentation at JupyterCon 2023

This spring I gave a talk about Pluto.jl at JupyterCon 2023 (Jupyter, not Julia) in Paris. If you want to learn more about the latest Pluto features, and how it compares to Jupyter and Python, then take a look!

This was my first in-person conference! It was a super interesting audience to present to and we made lots of nice connections, including Stephen Macke, who presented a reactive Jupyter kernel for Python. And I’m really happy with how my talk went, I wish they also filmed the audience’s faces when I did my magic tricks :blush:

I did my best to also advertise Julia during my talk because… I gave the only Julia-related talk in the entire conference! :frowning: I am sure they would welcome more Julia submissions, so consider presenting in 2024! :slight_smile:


This is an awesome talk! I for one believe in Pluto :slight_smile: If JupyterCon 2024 is not too far away, I might lend you some backup

1 Like

Great talk, definitely something with huge application in education!

Hi, congratulations for a great talk.

One thing that I missed in your talks (and most other presentations about Pluto) is the user case in which your Pluto code (let me call it application code) depends on many functions (let me call them based code) within your own package. This is often the case in my field (particle physics). Within a given package we may end up with dozens (or hundredths) of base code functions while the application code on itself is small.

One solution is to pack all the case code at the end of notebook, but I find this not very satisfactory. Another, is to include base code with “include” statements. Yet, another one is to “load” the base functions, using a function like this (should sound familiar to you):

function ingredients(path::String)
# this is from the Julia source code (evalfile in base/loading.jl)
# but with the modification that it returns the module instead of the last object
name = Symbol(basename(path))
m = Module(name)
:(eval(x) = $(Expr(:core, :eval))(name, x)), :(include(x) = (Expr(:top, :include))(name, x)), :(include(mapexpr::Function, x) = (Expr(:top, :include))(mapexpr, $name, x)),

This is the solution I use. Then in my notebook (for example):

lfi = ingredients(“…/src/LaserLab.jl”)

h1dtns0 = lfi.LaserLab.h1d(PhotonDF.dtime *ttrns, fdat.nbins, 50.0, fdat.dtmax)

Where h1d is an auxiliary function (a wrapper for histograms) defined in my base code.

Apologies for the lengthy explanation. My points:

  1. The notion of “one notebook to program it all” is nice when it applies to demo code, in particular for teaching (I use it for that purpose all the time).

  2. In realistic applications, the base code becomes large (and is still not packed in a tidy library), while the application code is often small (load dataframes, select, filter, plot). Then you need a number of “ugly” (actually not very ugly) fixed to have a practical solution.

  3. However, I don’t see that case discussed in talks, and I have the feeling that the “fixes” could be improved. I think the developers of Pluto are overlooking a rather important user case.

Thanks for your attention!


If you just need to evaluate a file inside a named module, why go to the trouble of altering evalfile, couldn’t you just do the straightforward module lfi include(".../src/LaserLab.jl") end? This doesn’t seem like something for Pluto to handle, it’s just code structuring.

Is not that simple. if you have structs defined in your code, then you can load them only once with include (e.g, you cannot change the definitions in your struct and load again). If you do, you get an error like this:

LoadError: cannot assign a value to imported variable workspace#23.CrstDetector from module workspace#24

in expression starting at /Users/jjgomezcadenas/Projects/JPET/src/crstDet.jl:6

  1. top-level scope@crstDet.jl:6
  2. include(::Module, ::String)@Base.jl:457
  3. include(::String)@PlutoRunner.jl:93
  4. top-level scope@Local: 4

This is the reason to use the “ingredient” tricks. Not something I invented. I copied it from a fix that Fons himself suggested some time ago.

So my point is that Pluto does not seem able to handle code structuring in a tidy way (e.g, simple “Includes” do not work, as they do, for example in Jupyter).


I can guess what you’re doing now, you have a file holding a module, and you’re rerunning its include in a Pluto cell after changes to a file. I see how this is particular to Pluto, redefining structs is not usually feasible in REPL-based workflows, but Pluto’s live coding has very aggressive reevaluation. That error is also particular to Pluto’s underlying module mechanism because you actually can re-include a module file in a REPL workflow with a warning about the obsolete module sticking around in other places, and it has nothing to do with the contents of the module let alone redefining structs:

### MWE replicating the error

# cell 1
write("A.jl", """
module A

# cell 2, throws the error on 2nd run, no edits to A.jl needed

My point still stands, the error goes away if you include the module file into another module. Your ingredients does it with a module named after the file and assigned to a non-constant global lfi. That doesn’t seem necessary, just using a normal module seems to work:

# cell 2, no error and can rerun with edits to A.jl
module B

You can also interact with Pluto’s underlying module mechanism on the 2nd run onwards with this, though it’s impractical to make this edit. You’ll also see the module redefinition warning that happens in the REPL, so I’m guessing Pluto’s reevaluation will not work so well here.

# cell 2, 2nd run onwards when module A already exists
Base.include(parentmodule(A), "A.jl")