You are viewing archived messages.
Go here to search the history.

Benji York 2023-11-21 21:28:47

"We wanted tools to make sheds, we got tools to make skyscrapers."

This article raises some interesting questions:

Ivan Reese 2023-11-21 21:51:27

Ink & Switch just released a new essay: Embark — Dynamic documents for making plans.

Starting from a desire to "unbundle the app" in the name of malleable software, they explore using a bullet-point outliner as a substrate for dynamic properties, computations, and views. They ground the work with a specific use case — trip planning — and they hard-code a bunch of stuff that ought to be pushed into user space, granted. But it's easy to look at this, imagine how it could be expanded, and see some really compelling possibilities for a different way to approach creating and distributing software.

Congrats to Paul Sonnentag, @Alexander Obenauer, and Geoffrey Litt!

Konrad Hinsen 2023-11-22 16:51:17

Bullet-point outliner, dynamic properties, computations... That's what I have been using for years, via Emacs' org-mode! It's set in a different universe, the realm of textual data and command-line tools. Not a good substrate for maps, obviously. But the architecture looks very much the same, and tons of people have long years of experience with this way of working.

Another outliner supporting similar features is Leo, which has also been around for more than 20 years. No personal experience, but I know people who use it for dynamic documents.

Shalabh 2023-11-24 20:19:13

I love the ideas here and also the example use case because it is always such a pain. Here’s me from a twitter thread from a while back:


Shalabh 2023-11-25 01:04:43

Integration is still an open problem and the main problem with scaling something like this


Duncan Cragg 2023-11-25 10:34:26

Paul Sonnentag it would be really great to have your input and feedback here. Also, I see Jonathan Edwards referred to Chorus in this context on Twitter I think.

Seems like so many of us are interested in the no-apps ideas. Maybe Slack simply has too much friction to help build community around something like this.

Mattia Fregola 2023-11-22 03:49:30

Pickcode – a visual language to teach kids to code.

Jason Morris 2023-11-22 05:35:55

Everyone starts out thinking they are making it easier for kids. Eventually, they realize that kids are people, and if you make something easier for kids, you have made it easier for people. It took Blockly about 10 years to really get it through their head. Pickcode will figure it out in 9 more years, I guess. 😅

Joe Nash 2023-11-22 12:48:27

Though tbf pickcode doesn’t actually seem to mention being for kids anywhere on the home page so it may have been editorialisation in the posting

Mattia Fregola 2023-11-22 14:31:18

Original HN post wording by the pickcode team Joe Nash

Joe Nash 2023-11-22 15:25:35

Aha! Interesting. Thanks for sharing the source Mattia

Justin Blank 2023-11-23 13:59:46

An essay on the challenges of image based/live systems:

Kartik Agaram 2023-11-23 15:12:13

This is outstanding, Jamie Brandon!

Justin Blank 2023-11-23 15:13:09

Ah yeah, I would’ve tagged had I remembered he joined here.

Konrad Hinsen 2023-11-23 15:49:54

Nice! This reminds me of another post I saw yesterday, on what you need to keep in mind when doing interactive development in Lisp. It's quite a bit, but also because Common Lisp is a rather complex language.


A saner option is to recompile and reload the entire codebase whenever a change is made, while preserving the state of the heap

Are there any real-life implementations of that idea?

📝 Interactive Common Lisp development

Common Lisp programming is often presented as “interactive”. In most languages, modifications to your program are applied by recompiling it and restarting it. In contrast, Common Lisp lets…

Jamie Brandon 2023-11-23 18:42:45

Are there any real-life implementations of that idea?

Sort of erlang. It's designed for upgrading production services so it has a lot more toggles and manual controls than you'd want for interactive editing, and there's no undo if you screw up the migration. But it does work fairly well. Certainly compared to everything else.

I also see a lot of game developers do this in C and it seems pretty useful even with the complete lack of type safety.

Jamie Brandon 2023-11-23 18:45:34

Also tagging me did nothing, someone had to manually tell me about this thread. Is sending notification emails a paid feature on slack?

Justin Blank 2023-11-24 10:12:59

I know I’ve only ever seen notifications inside slack, or on my iPhone screen when I gave slack that permission. So it’s not a default that it does it.

Jamie Brandon 2023-11-24 17:15:59

I have set the preference though. It just doesn't seem to work. Makes slack much less useful for occasionally visited communities.


Kartik Agaram 2023-11-24 17:21:36

It's definitely a bug.

Shalabh 2023-11-24 18:35:35

Excellent essay!

I believe the issues described with live systems are not actually solved with dead systems, but just deferred until later. That’s because dead systems don’t often represent entire systems but only a slice of the system. Consider a system with a Rust (or another dead lang) web app backed by another web service and a db. One problem is evolving the web app code while preserving the db data and migrating the db schema. This is similar to evolving live code while the heap remains intact. Another problem is that upgrading the downstream service may change or delete an API method and it leave it incompatible with the caller web app. This is similar to upgrading a subset of functions within the same live program. Dead-lang approaches don’t solve any of these problems of “external couplings” and “externalized data” but simply exclude them from their scope, leaving us to wrangle these with other approaches. Live systems seems less tractable because these problems are harder, and more worthwhile (my position).

Shalabh 2023-11-24 18:38:37

Another note on this:

For a start, you can actually find the code as a single artifact rather than it being the product of a log of mutations.

Some of this gets lost even in dead-langs when you’re dealing with macros, metaprogramming or subsclassing at some level of complexity. Not only do you have to simulate the runtime semantics in your head, you also have to simulate the type checker and compiler semantics.

Shalabh 2023-11-24 18:41:05

Reading the essay, another idea that clicked for me was having a “version-aware runtime” - ie the system should be aware of the different versions of various artifacts (code, data, etc.). No dead-lang does this - there is only one version of all types.

Justin Blank 2023-11-24 18:44:39

Mentioning web apps and databases makes me think about how there are playbooks for migrations there—you (if you’re being careful) use monitoring, let both versions coexist, try to find callers using the old versions and encourage them to switch, etc.

I wonder what the playbook looks like in a programming environment.

Justin Blank 2023-11-24 18:45:24

It’s possible to do monitoring in your runtime.

Kartik Agaram 2023-11-24 19:15:11

Shalabh Chaturvedi

I believe the issues described with live systems are not actually solved with dead systems, but just deferred until later. That’s because dead systems don’t often represent entire systems but only a slice of the system.. upgrading [a] downstream service may change or delete an API method and it leave it incompatible with the caller.

This depends on the context. If you think of the upstream and downstream as within a common ownership boundary, then such checks are valuable. However, if they span ownership boundaries then these checks can seem onerous.

You're right that dead systems work almost accidentally for the second case, by just not doing some work. Ideally we'd have both tools in our pocket, and the flexibility to select from them depending on the situation. However, this is a lot of complexity, and all code carries costs. So worse may be better here.

This situation is analogous to structured editing like in Tom Lieber's recent submission 💬 #thinking-together@2023-11-19T14:58:02.617Z. It's easy for a structured editor to feel like an overbearing presence. The implementor has to juggle both technical complexity and UX nuance. Meanwhile plain text is often "good enough" and so we continue to muddle along..

Shalabh 2023-11-24 19:45:59

It’s possible to do monitoring in your runtime.

Yeah, the key requirement is version-awareness - whether the endpoint name encodes the version or some introspection api returns the version of the service, any strategy needs a representation of the version of functions etc within the system itself, and should allow multiple versions to coexist. An interesting implementation here is gemstone smalltalk - it is a live system where you can evolve the schema by providing migration methods and the objects can be migrated to newer definitions either opportunistically or in batch.

Shalabh 2023-11-24 19:46:16

Kartik Agaram

Ideally we’d have both tools in our pocket, and the flexibility to select from them depending on the situation.


I’d even add that ideally these would not be two separate tools but a single model that can be applied in varying use cases - from “early bound, validated” bundles of code to “late bound, but also validated” and in between.

However, this is a lot of complexity, and all code carries costs. So worse may be better here.

Would this be more complexity than present day type checkers? 😜

Kartik Agaram 2023-11-24 19:54:25

I absolutely think so! You're adding a whole new dimension to the problem. What are you taking out?

Shalabh 2023-11-24 20:11:18

Ah good point. If we keep all the existing static lang ideas intact and then layer on dynamic bindings, yes it’s a whole another dimension. However if we first reduce the language features (eg like the essay suggests, 2nd class functions only) and design the types and semantics of the language to be amenable to both static and dynamic bindings we want, then maybe we have a chance.

Shalabh 2023-11-24 20:17:23

The problem as I see it is that dyanmic+live langs have typically not been designed to make validation or optimization easy, and static langs have not been designed to make late binding easy. I don’t think we want to total freedom of dynamic+live langs. We want some snapshotting of state, we want visualization and provenance tracking of all dynamically generated code. Once we start adding these we can introduce ways to validate and optimize the slices of the live system that have stabilized.

Konrad Hinsen 2023-11-25 17:01:43

Throwing a somewhat related paper into the discussion: "World Age in Julia"

More about metaprogramming than live modification, but the issues are similar. Abstract:

Dynamic programming languages face semantic and performance challenges in the presence of features, such

as eval, that can inject new code into a running program. The Julia programming language introduces the

novel concept of world age to insulate optimized code from one of the most disruptive side-effects of eval:

changes to the definition of an existing function. This paper provides the first formal semantics of world age

in a core calculus named Juliette, and shows how world age enables compiler optimizations, such as inlining,

in the presence of eval. While Julia also provides programmers with the means to bypass world age, we found

that this mechanism is not used extensively: a static analysis of over 4,000 registered Julia packages shows

that only 4–9% of packages bypass world age. This suggests that Julia’s semantics aligns with programmer


Jamie Brandon 2023-11-26 00:31:31

Julia's behaviour around closures is among the nicest I've seen:

julia> foo(x, y) = x + y

foo (generic function with 1 method)

julia> bar = x -> foo(x, 1)

#3 (generic function with 1 method)

julia> bar(1)


julia> @code_llvm bar(1)

;  @ REPL[1]:1 within `#3`

define i64 @"julia_#3_122"(i64 signext %0) #0 {


; ┌ @ REPL[7]:1 within `foo`

; │┌ @ int.jl:87 within `+`

    %1 = add i64 %0, 1

; └└

  ret i64 %1


julia> Base.delete_method(@which foo(1,1))

julia> bar(1)

ERROR: MethodError: no method matching foo(::Int64, ::Int64)


 [1] (::var"#3#4")(x::Int64)

   @ Main ./REPL[1]:1

 [2] top-level scope

   @ REPL[6]:1

julia> @code_llvm bar(1)

;  @ REPL[1]:1 within `#3`

; Function Attrs: noreturn

define void @"julia_#3_133"(i64 signext %0) #0 {


  %1 = alloca [2 x {}*], align 8

  %gcframe2 = alloca [3 x {}*], align 16

  %gcframe2.sub = getelementptr inbounds [3 x {}*], [3 x {}*]* %gcframe2, i64 0, i64 0

  %.sub = getelementptr inbounds [2 x {}*], [2 x {}*]* %1, i64 0, i64 0

  %2 = bitcast [3 x {}*]* %gcframe2 to i8*

  call void @llvm.memset.p0i8.i32(i8* noundef nonnull align 16 dereferenceable(24) %2, i8 0, i32 24, i1 false)

  %thread_ptr = call i8* asm "movq %fs:0, $0", "=r"() #6

  %ppgcstack_i8 = getelementptr i8, i8* %thread_ptr, i64 -8

  %ppgcstack = bitcast i8* %ppgcstack_i8 to {}****

  %pgcstack = load {}***, {}**** %ppgcstack, align 8

  %3 = bitcast [3 x {}*]* %gcframe2 to i64*

  store i64 4, i64* %3, align 16

  %4 = getelementptr inbounds [3 x {}*], [3 x {}*]* %gcframe2, i64 0, i64 1

  %5 = bitcast {}** %4 to {}***

  %6 = load {}**, {}*** %pgcstack, align 8

  store {}** %6, {}*** %5, align 8

  %7 = bitcast {}*** %pgcstack to {}***

  store {}** %gcframe2.sub, {}*** %7, align 8

  %8 = call nonnull {}* @ijl_box_int64(i64 signext %0)

  %9 = getelementptr inbounds [3 x {}*], [3 x {}*]* %gcframe2, i64 0, i64 2

  store {}* %8, {}** %9, align 16

  store {}* %8, {}** %.sub, align 8

  %10 = getelementptr inbounds [2 x {}*], [2 x {}*]* %1, i64 0, i64 1

  store {}* inttoptr (i64 140053833609312 to {}*), {}** %10, align 8

  %11 = call nonnull {}* @ijl_apply_generic({}* inttoptr (i64 140053835183152 to {}*), {}** nonnull %.sub, i32 2)

  call void @llvm.trap()


Jamie Brandon 2023-11-26 00:35:04

But it doesn't allow redefining types - you have to delete and recreate a module, which means that functions in the old module don't get redefined. And then you're back to having old closures hanging around.

Konrad Hinsen 2023-11-26 10:52:07

Yes, types are the hard part. They are not part of data and code, but live in the control layer that structures data and code. I doubt there can be a generic technique to update types in a live system in all circumstances. But perhaps there are ways to handle specific practically important cases.

Jonathan Edwards 2023-11-26 20:34:02

I agree with a lot of what Jamie Brandon says. I’d also amplify some of the above comments: versioning is the key unsolved problem of image-based/live programming

Walker Griggs 2023-11-24 18:16:49

A saw a friendly face on the front page of lobsters this morning

Walker Griggs 2023-11-24 18:18:26

You could say I LÖVE seeing this work progress, Kartik Agaram

Kartik Agaram 2023-11-25 04:03:49

I just found an absolutely ancient bit of advocacy for convivial tools. From 2008!


Jonathan Edwards Were y'all aware of this when you organized

Jonathan Edwards 2023-11-26 18:59:20

Kartik Agaram not specifically. Forth is impressively minimal. Personally I did a little Postscript programming and didn’t like it at all.