random question — why aren’t live coding languages...
# thinking-together
s
random question — why aren’t live coding languages more popular? I don’t know many people that use them day to day or in a business, nonprofit, etc setting. Excel is maybe the only exception here
i
Got a sample of one you’d personally like to use more but don’t? Or maybe one you think would fit the bill for some folks to work into their daily rhythm?
t
Agreed, some examples of what you mean would be helpful. I can think of several "live" coding workflows that are widely used, like the hot-reload popular among frontend developers. We've built a similar experience in our code gen tool. Another popular example might be Jupyter Notebooks popular among ML & DataSci people. I think the live coding languages part is confusing, what even is such a language?
j
That so many people accept dead-coding as the norm is one of the saddest things in computing...
1
😢 1
t
What is dead code?
j
dead-coding is the opposite of live-coding
k
@Tony Worm I'd define a live-coding language as a language that allows building and changing code incrementally at the level of the smallest autonomous unit, typically a function. And that requires that the language accept incomplete and/or inconsistent program state, at least temporarily, which is why most (all?) statically typed languages are no good for live coding.
j
I would say it has less to do with type discipline than with having the compiler with you so you can interactively build up your program while it's running, inspect values, &c. (See the example below of doing this sort of thing with OCaml.) That said, nominal type systems do make things harder than using structural ones — and not just for live coding! 🙂 https://global.discourse-cdn.com/standard11/uploads/ocaml/original/2X/5/53856f555462c98314f99209cb3a145a02181428.webp
i
Relatedly, it's wild to me that so-called visual programming, which started off extremely live, is now just as commonly not live as it is live. Talk about screwing up a good thing.
😢 6
t
My take is that it's all about tooling and your setup. Most of these things are achievable to a productive state without being absolute about how that is done. A fast compiler and a solid debugger can effectively create the same experience.
Though I've always wondered how we might move beyond the file for developer experience... 🤔
g
- publications: Microsoft Word - videos: “scrubbing” ; iMovie, Davinci Resolve, etc. - music: DAWs, “scrubbing”, live-editing of “automation”. - musical performance: Ableton Live, sonicPI, etc. - drum sequencing: iterative, layered recording in drum loop editors - ascii Art: vscode, etc., colorization - mind-maps: Kinopio, etc. - accounting artefacts: spreadsheets - robots: industrial robotic arm training - text: emacs - text: Apple Dictation - programs: Lisp, REPLs, etc. - “visualization” of CPU operation: single-stepping debuggers
👍 1
j
A fast compiler and "good debugger" cannot, even remotely, create the same experience. It's only the terribleness of the standard tools that lead people to aim for, much less accept, such mediocrity.
j
@Jack Rusher Why do you put quotes around "good debugger"? I thought that debugger is often the thing that can be improved more easily than a compiler. Since the "liveness" is achieved mainly by tolerating inconsistent/ambiguous code&program state - which is something a debugger can do more easily than a compiler.
j
Because
edit / compile / run / test / debug
should all be one thing, not different tools or modes. Most people have never used a good programming environment or they would not tolerate things working the way they do.
💯 2
☝️ 1
t
I suppose that is a matter of opinion and the difference between being applied and academic about it. Absolutisms is rarely a good policy, same as believing your way is the best way
👎 2
j
@Jack Rusher Well, those activities should work tightly, but they aren't the same thing. You may not like it, but testing these separate is necessary - someone might want to use a different tool for one of those activities, keeping the rest of the toolchain. You might argue that they can just adjust the development environment, but that requires reflection, and still locks you in the environment.
k
One important feature of a live system that I haven't seen done with "just" fast toolchains is that in-memory data objects survive code changes. Example: in Smalltalk, I can add a slot (aka instance variable) to a class without disturbing existing instances. They just acquire a new slot initialized to
nil
.
j
@Jan Ruzicka I mean what I said: they can all be the same thing, using same interface, allowing the programmer a flow state as they go. I know this because I have been working this way for decades.
j
@Jack Rusher That something can be, doesn’t mean it should be 🙂 However, maybe we’re just using different words for the same idea. I can’t agree with the idea of making editing/compiling/running/testing/debugging a single thing (= a single artefact), since that might not suit many people. It also tends to produce a monolith. What one should do is to design a common coherent mediating protocol around edit/compile/run/test/debug, which enables the flow, but which doesn’t limit the tooling decisions. Maybe this is what you had in mind. PS: In the monolith approach, if you want to change some tool, you can never do it drop-in style (you always have to integrate it by programming it yourself or by resolving a merge conflict). I don’t know how the Smalltalk community goes about this issue; if you were dealing with this, tell me your approach.
w
@Jan Ruzicka and @Jack Rusher sounds like you're coming at the practice of programming from very different perspectives. Jack's point, as I understand it, is that for him the activities "editing/compiling/running/testing/debugging" aren't really different things. Then Jan's point, as I understand it, is a concern that if all the activities are one activity, then how do you deal with the resulting, single monolithic tool? A fair question with a potentially interesting answer. So let me phrase it this way... Jack, having worked "this way for decades," where are the natural module lines with what you use? What can be upgraded independently? What's pluggable? For parts of your process are there alternative/multiple tool choices, and do you ever pick both? An example: for Git, I sometimes use the command line but generally use Sourcetree. Occasionally unstable, it went very, very wrong yesterday: constant crashing. So I downloaded and finished with Sublime Merge in the middle of staging changes, with little interruption. I didn't configure Sublime Merge. I didn't read any documentation. I commented to myself "my, my, my, this looks hideous" (read "unfamiliar"), and I was in no mood to learn anything about what might be better/worse/smoother about Sublime Merge. One tool broke, I picked up another, kept working.
👍 1
j
What would live programming be in a declarative logic language? Would you watch search trees form and get resolved? Has anyone done that? I'd like to see it.
👍 1
t
A guy I know started a REPL for CUE (cuelang.org | cuetorials.com) a declarative, logical language. New kid on the block It might be in here: https://github.com/philipdexter/pycue
@Jason Morris that's an interesting idea, to visualize the evaluator 🤔
k
@Jason Morris I am thinking about similar issues right now, working on a term rewriting language which is 100% declarative as well. For now, all I am aiming for is having all data updated live whenever a dependency changes. I'll see if that works out fine, and sufficient.
j
@Konrad Hinsen That sounds interesting! Are you aware of e-graphs ? They would certainly come in handy when dealing with declarative and responsive term-rewriting.
👍 1
k
Yes, e-graphs are great for my application!
i
A fun reminder for this topic: Recall that in almost every practical coding grammar, you’re always a single keystroke away from an epsilon transition to an undefined state, which you must do something about 😉 There’s definitely some value in identifying which pieces of the lightning box are “discretely live” and which are “result live”
👍 2
w
@Jason Morris a friend had a live logic language that was exactly about watching search trees. The qualitative difference from a REPL is that you start a query then let it run in the background without caring so much about when or how many results it would produce. Immediately, you fall into using it for streamed data processing. Live notebooks become a sort of special case where you just show each expression's most recently enumerated result. I'm keen to explore the possibilities here more.
🍰 1
j
I'm having trouble understanding what "watching search trees" would visually entail. What do you actually see?
And is it something I could see demonstrated?
w
Haven't been in touch with him recently. Don't know if he's still working on it. He wouldn't show the tree just answers appearing below the query. Let me try to conjure an image. You're at a terminal window. Run a command. See a bunch of log nonsense scroll by as it executes. Hit return. Now you have a command prompt. You can run a second command. The first one is still going, still printing. It's like you opened opened a second terminal window below the first. That's what it was like to a first approximation.
👍🏻 1
j
Here are some of the things I'm talking about. Hopefully the examples in the thread will make it more clear. https://twitter.com/jackrusher/status/1558000856263540736
❤️ 3
g
@Jack Rusher made a good point about convenient debugging. I indulged in riffing on this idea: too long to paste here, so I’ve written it in Obsidian instead https://publish.obsidian.md/programmingsimplicity/2022-08-12-Convenient+Programming+IDEs
👍 1
j
A few points: • Restarts are not hot-reloading, but you can change the runtime environment during the restart. (A "doesn't change the environment" example: unreachable network causes an error, but it's transient. You can "retry" the function call after the network comes back up, everything proceeds as usual. Your code doesn't need to know about this for it to work.) • "Kill a process, replace it with another process with exactly the same FDs for stdin, stdout and stderr" -> one of the key points here is that all the program state remains the same as you modify the data and functions within the running process. This is particularly useful, for example, you have a large data ingestion step at the beginning of your program, or there's some UI situation that it would take you a bunch of steps to get back to in order to test your change, or, or, or... • Glad you mention LispWorks 🙂
👍 3
k
Being able to change code while keeping program state was one of the features that made Python popular with scientists in the 1990s. You could generate or preprocess data and only then start to write the code to explore it in detail. Compared to Common Lisp or Smalltalk, Python's live-coding experience was (and still is) very limited, but it was there, it was free, and it was easy to integrate with the Fortran and C libraries that people depended on.
🤔 1
j
@Jack Rusher Interesting point about program state, but wouldn't it but make sense to speak of "program state remaining the same", in most cases? If one removes some "variables" (= slots for primitives, could be in data structures), then it could make sense in some cases, but adding variables comes with the problem of them not having values from the "previous activation" (the state of the old program). So I suppose you practically always have to do some kind of "migration phase", don't you?
j
@Jan Ruzicka I think auto-correct has done you wrong: "adding variables Congress with the problem", can you clarify what you're asking here?
👍 1
j
@Jack Rusher Corrected to "comes". Basically, I think that effectively, in some cases the programmer must perform a sort of "schema migration" lest the program remains ill-formed (and thus not runnable / error-free). This concerns data in certain situations; analogous things can be said about other parts of any interface (functions, types). I'm not familiar with how LISP resolves this issue; could you describe how you go about this?
w
Emphasis on in some cases. With what I do, most changes that I would make live don't change the core data in a significant way. For example adding methods to format or otherwise pull out information from my models. The other cases are either (1) changing the process that produces state in the first place. In that case, you will want to rerun from scratch. Or (2) updating something long lived. In that case, you'll going to need to migrate one way or another. And then there's the bad case (3) you did something dumb and you broke your image (or more likely) just the UI, like opening up a million windows. In that case, you want to be able to kill with fire. Though I don't know, I cannot but imagine that isolation is better now. Back when I was using Lisp and Smalltalk, desktop operating systems often had the same problem. Hell, earlier this week the window server on laptop decided to take holiday, so reboot was the quickest fix.
i
I think the way people kinda want all this live stuff to work is like the diversion of the flow of water. Bits are flowing from
main()
to some unknown
end()
call. There are tributaries and side branches and loops in the river, but it flows in one direction. People want to be able to stick a hypothetical dam and controls in any spot of the river, and watch the changes occur. The analogy fails because water is slow, and bits are fast. But it’s the immediacy that matters - the instantaneous physics of the interaction. Note, you can actually break the river permanently if you dam up the right spots..
e.g.
, break the running state of the application,
e.g. x 2
, @wtaysom;s comment above. And that’s where all the “live state saving” stuff kicks in. If you dam the wrong outlet and blow something on the other side of the lake, you can press and button and poof your back in time. I see this best day to day, honestly, really well in SwiftUI. You write a line of code, it takes a second to recompile it, and displays the result in a virtual preview. Did you write a line that doesn’t compile and can’t produce a preview? No problem - the “magic state” is the IDE, and all you have to do is modify the code. That should evolve to the rest of the system, and it sort of does. If I tickle some part of the model in a layer elsewhere, the act of recompiling will produce the new preview. It’s implicit, but it happens.
j
@Jan Ruzicka it's mostly a non-problem, tbh. One common thing in these sorts of environments is just interactively changing the shape of the data by evaluating a line or two of code interactively. If it's the kind of thing one might need to do again later, the code gets saved off to the side in a comment. In this way, the admin interface to your program is the code itself until you actually want a separate tool for whatever reason.
g
@personaldynamicmedia
I’m not sure I fully understand how you classified each of those languages, especially Scheme and Smalltalk,
@jackrusher
The two lists of languages don’t make any sense to me. Can you expand on how you think Smalltalk is more of a compiler appeasement language relative to Self?
observation: Classes are but optimizations of Prototypes. further... observation: Inheritance is backwards to what we want. A unit of code should be understandable in a stand-alone manner. A “child” should not affect a “parent’s” unit of code. Instancing a “child” from a “parent” class should - in no way - alter the behaviour of the parent class. A child must not override methods of a parent. theory: delegation, not overriding. observation: Inheritance (of any kind, prototype inheritance or class-based inheritance) is simply DRY (Don’t Repeat Yourself). A good editor should do inheritance automatically without putting the burden on Programmers (see, also, Lisp macros, Paul Bassett’s [Framing Software Reuse](https://www.amazon.ca/Framing-Software-Reuse-Lessons-World/dp/013327859X )) observation DRY is of 2 kinds: (1) semantic (Design) reuse, and, (2) code reuse observation Smalltalkers tell me that inheritance for (2) code reuse is bad. observation Harel StateCharts conquer the “state explosion problem” by using delegation and encapsulation. Harel StateCharts use delegation instead of inheritance. My take on the original StateChart paper observation Sector Lisp - I thought that I hated FP, but Sector Lisp convinced me that FP can be beautiful and small. Why is Sector Lisp many, many orders of magnitude smaller than other Programming Languages and Operating Systems? theory: Sector Lisp stays within the “sweet spot” of the FP paradigm and doesn’t try to be an all-in-one solution to every programming problem. observation Sector Lisp’s Garbage Collector (invented by Tunney) is 40 bytes. SIC: bytes, not K, not M, not G, not T. My take on Sector Lisp GC theory Sector Lisp’s smallness cannot be due only to Assembler Parlour Tricks. Something deeper must be involved. theory We need languages that plumb “build and forget” solutions. Plumbing ≣ composition. UNIX pipelines are an example of this. Surely, we can do better. observation Functions employ LIFOs, UNIX pipelines employ FIFOs. observation Functions calling functions cannot be pipelines (LIFO vs FIFO)
r
@guitarvydas Your misgivings about inheritance are shared by Brad Cox in Planning the software industrial revolution -
The confusion of implementation and specification is particularly prominent in that most fashionable of object-oriented features: inheritance. As used in object-oriented language circles, inheritance is the Blanchard lathe of software -- a powerful and important tool for creating new classes from existing ones, but not nearly as useful for specifying static properties such as how they fit into their environment, and useless for describing dynamic properties such as what these classes do.
Rather than laboriously building each new class by hand, inheritance copies functionality from a network of existing classes to create a new class that is, until the programmer begins overriding or adding methods, correct by construction. Such hierarchies show how a class's internals were constructed. They say nothing, or worse, mislead, as to the class's specification -- the static and dynamic properties that the class offers to its consumers.
Moreover, the difference was elaborated as theory in Inheritance is not subtyping and practically in the Strongtalk type system. It's quite a shame that the fate of Strongtalk was ultimately to improve JVM performance - though it did a great job at that! By these criteria, Java isn't a well formed type system given it allows and encourages the use of classes as types in method arguments and return types, a critical design flaw that makes its otherwise wild success all the more tragic for OOP.
👍 2
😢 1
k
My impression is that specifications are underrated in general. I'd love to see attention shifting from type systems to more general specifications of software characteristics.