>> That is, how leporello handles a codebase that's partially written in freestyle JS and not the subset that leporello specify handles?
Leporello is based on the idea that you can have much more powerful dev experience (time-travel debugging, better debugger UX) if you adhere to functional code. Look at the recent news from Jetbrains [0]. They presented predictive debugger for their .NET IDE. They called it "a game changing look into the future" and it is actually a huge improvement of ergonomics. But with FP, you get predictive debugger for free. It is just a trivial consequence of not having data mutations. So inside the function, each name is binded to single immutable value.
If you have freestyle JS, then Leporello is not a good fit for you. It requires some buy-in from a developer. The good thing is that you still write plain vanilla javascript, without non-standart extensions that require some kind of transpilation.
I don't have any experience with Jsonnet, but from what I can gather, it appears to be a Turing-complete functional programming language. Leporello.js, on the other hand, is a UX concept that can be applied to any functional programming language, including Jsonnet.
In fact, I believe it's possible to write code in TypeScript that would be very similar to Jsonnet. TypeScript provides the capability to create compiler plugins, which can restrict the allowed language subset, effectively making it more limited and suitable for configuration
The main difference between jsonnet and JavaScript/typescript is that it's lazily evaluated.
Evaluation is driven "from the output"; by which I mean that only the code that has an effect on the output is evaluated. When an object is rendered (manifested) all it's non-hidfen fields are evaluated and every expression they contain produces a value which is then manifested and recursively only the fields that are manifested have their expressions evaluated.
This ia particularly important when combined the key operation that jsonnet provides: object overlay/merges. When you overlay an object on another object it may shadow a field of the "super" object causing the expression of the super object to not be evaluated.
Thank you for the suggestion! I am using the Ace editor. I will consider adding a configuration option, but my main goal is to build a VSCode plugin, so all the configuration will be left to the user.
sounds amazing. Your tool is super useful for understanding recursive functions. Only thing I felt that I was missing when using this was a format button to fix the formatting of my code.
Thank you for your feedback! I believe it has some value in teaching programming in the spirit of "Structure and Interpretation of Computer Programs." There is a JavaScript edition of the SICP book. I believe Leporello.js would be a nice environment for students to solve problems from this book.
I agree that enforcing strict purity in Leporello.js might limit its adoption among developers who prefer a more flexible approach. However, this initial focus on a pure functional subset allowed me to concentrate on the core idea – reimagining developer tools for functional programming from the ground up.
Exploring the development of a two-mode debugger that can seamlessly switch between traditional imperative debugging and Leporello.js-style debugging for pure functions in modern multiparadigm languages is indeed an intriguing research avenue.
Leporello.js follows a pragmatic functional approach. You can use IO functions and throw exceptions as you typically would, without the need for an IO monad or an Option monad. However, there is one critical limitation – you cannot mutate your data structures. Mutations are not allowed because they would make time-travel debugging impossible. For instance, the following code is forbidden:
const point = {x: 1, y: 2}
point.x = 2
And instead you should use this:
const point = {x: 1, y: 2}
const another_point = {...point, x : 2}
In Leporello.js, it's vital to avoid mutating data structures; instead, you create new ones. You can watch Rich Hickey's insightful talk [0], where he discusses the advantages of immutability in functional programming.
In many ways, Leporello.js bears a resemblance to Clojure. Clojure served as a source of inspiration for Leporello.js, to the extent that I even contemplated building Leporello.js for Clojure first.
The comma operator also seems forbidden. I guess there's never a need to use it in purely function code, but the code can still qualify as pure even allowing its use?
Currently, Leporello.js relies on my custom JavaScript parser, which, while functional, is not yet complete and exhibits some quirks. My plan is to replace it with a TypeScript parser, thereby providing full support for both JavaScript and TypeScript within Leporello.js. Additionally, I'm going to develop a VSCode extension, which will offer you all the features available in ts-server.
I plan to blog about Leporello.js internals, but now I will try to give you a short answer.
When navigating a call tree, Leporello.js evaluates your functions with tracing enabled, collecting execution information and materializing parts of the call tree in memory. This evaluation is lazy, but if a function invokes an IO operation, the corresponding call tree node is saved eagerly to ensure the function is called only once, avoiding duplicate IO operations.
Handling state mutations is more complex. Leporello.js doesn't inherently know if a function, possibly from a third-party library, will mutate its arguments. Therefore, when using such functions, there's a risk of displaying incorrect data when you navigate and debug your code.
Ideally, a language would color functions based on their purity, allowing smart IDEs to deep clone arguments before invoking impure functions, enabling time-travel debugging. Maybe I will add comment pragmas to Leporello.js. They would allow to have state-mutating functions as first-class citizens in Leporello.js. Syntactically, they can be just ordinary comments:
// leporello: mutation
function sort(arr) {
arr.sort() // in javascript, sort mutates array in-place
}
Currently, this responsibility falls on the programmer. If you use argument-mutating functions, consider wrapping them in a pure functional interface. The viable approach is structuring your program with a 'functional core, imperative shell' architecture. This approach, as seen in Leporello.js itself, involves a main codebase that is pure functional, operating on a single immutable data structure describing Leporello.js's state, while the 'shell' part invokes pure functions and applies effects.
The notable example of such architecture is Redux. Redux architecture fits perfectly for apps build with Leporello.js, as you can see in TODO app example https://app.leporello.tech/?example=todos-preact
Bret Victor's talks, particularly 'Learnable Programming' and especially 'Inventing On Principle' [0] served as the inspiration behind my creation of an IDE called Leporello.js [1]. You can find my interpretation of his example from 'Learnable Programming' here: https://vimeo.com/870708017
YESSS thank you! But... Perestroika frogs??? Anyway I think I played the Toppler version (no Gorby I remember) and it was really fun. I'll start digging for it right now...
My both mother and father developed software running on IBM mainframes for large USSR enterprises. It's interesting that USSR could not purchase mainframes legally so USSR worked around this restriction and finally was able to buy software with the help of KGB agents in Romania who introduced themselves to IBM sales as Romanian citizens. You can read more about it here [1]
After crash of soviet economy my parents became unemployed and never worked as programmers again. My father teached me some C in TurboC IDE. Currently I am full stack web dev.
[1] http://www.csd.uwo.ca/~magi/personal/humour/Computer_Folklor...
Leporello is based on the idea that you can have much more powerful dev experience (time-travel debugging, better debugger UX) if you adhere to functional code. Look at the recent news from Jetbrains [0]. They presented predictive debugger for their .NET IDE. They called it "a game changing look into the future" and it is actually a huge improvement of ergonomics. But with FP, you get predictive debugger for free. It is just a trivial consequence of not having data mutations. So inside the function, each name is binded to single immutable value.
If you have freestyle JS, then Leporello is not a good fit for you. It requires some buy-in from a developer. The good thing is that you still write plain vanilla javascript, without non-standart extensions that require some kind of transpilation.
[0] https://news.ycombinator.com/item?id=36940937