Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Show HN: Leporello.js – interactive functional programming IDE for JavaScript (leporello.tech)
107 points by dmitry-vsl on Oct 3, 2023 | hide | past | favorite | 33 comments
Hi! Leporello.js is an interactive functional programming environment designed for pure functional subset of JavaScript. It executes code instantly as you type and displays results next to it. Leporello.js also features an omnipresent debugger. Just position your cursor on any line or select any expression, and immediately see its value. Leporello.js visualizes a dynamic call tree of your program. Thanks to the data immutability in functional programming, it allows you to navigate the call tree both forward and backward, offering a time-travel-like experience. Leporello.js offers the ability to develop HTML5 applications interactively, enabling you to update your code without losing the application's state.

It records an IO trace of your program, which is then transparently replayed during subsequent program executions. This allows you to instantly reexecute your code after making small tweaks, thereby tightening your feedback loop.

Furthermore, Leporello.js can serve as an interactive notebook. You have the flexibility to utilize any JavaScript libraries to visualize your data directly within your code.

For a more detailed walkthrough, please watch the product video. Currently, Leporello.js is available as a free online application that you can try right in your browser. My goal is to build the Leporello.js standalone Electron app and a VSCode plugin, both with TypeScript support. Additionally, I plan to add Node.js support (currently, Leporello.js is only for HTML5 apps). In the VSCode plugin, Leporello.js will sit on top of the built-in TypeScript/JavaScript mode, utilizing its code analysis information to enhance the default VSCode experience with unique Leporello.js features.

I am building Leporello.js as a single independent developer. Leporello.js is funded solely by donations. Support me on Github Sponsors [0] and be the first to gain access to the Leporello.js Visual Studio Code plugin with TypeScript support.

I'll be delighted to answer any questions you may have.

[0] https://github.com/sponsors/leporello-js



Wow this looks pretty spectacular. At first I was annoyed that I had to play a video to get it, but I then had to admit that there’s no real way to convey this so well with text.

That VS Code plugin sounds particularly amazing. How do you plan to scope things? Notably how would I avoid it accidentally re-running side effects?


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


This looks awesome. Any plans to support Typescript? I can’t stomach vanilla JavaScript these days.

Edit: that vscode extension with typescript support actually seems perfect for me!


The term "self-hosted" is an interesting choice. It's usually used to describe the compiler of a programming language written in that same programming language. This is an IDE written in JavaScript. Cool project, though!


Something like this but for jsonnet could definitely help people grokking how jsonnet works.

I do believe that jsonnet (despite all it's real problems) is an underestimated jewel mostly because of the embryonic level of its tooling. An ide/debugger like this could help.

EDIT: some tangentially related work: https://github.com/kubecfg/ursonnet


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.


Incredible, it's like having perfectly typed language without having to use types at all. Few worries from my side:

- TypeScript gained traction partially because you could migrate an existing codebase to it. Does leporello support gradual migration from style that's not aligned with leporello to the supported subset? That is, how leporello handles a codebase that's partially written in freestyle JS and not the subset that leporello specify handles?

- How does it deal with patterns from popular frameworks or libraries that are borderline frameworks. React, Vue, Express, NestJS etc.

- It feels like leporello has to store a lot of possible branch-outs of the state. How would it handle frameworks code in this case? React on its own probably has a lot of branch-outs inside. Would it devour my RAM?


>> How does it deal with patterns from popular frameworks or libraries that are borderline frameworks. React, Vue, Express, NestJS etc.

React is a great fit for Leporello.js. There is an example of React TODO app that you can write and debug in Leporello. It is showcased in the video. You can play with it yourself if follow the link https://app.leporello.tech/?example=todos-preact

Speaking about backend frameworks, basically you code is a function Request -> Response. How do you organize your code is up to you. You can code it in a functional manner. What is great about Leporello, is that it remembers all the calls your app made to databases, other microservices and external APIs and allows to debug them in a time-travel manner, seeing requests and responses. You can run your code once, and then debug and navigate it forward and backward, seeing runtime values that were generated when your code was executed. It a huge time saving, especially when external resources are slow and may require complex setup or teardown before or after being called.

>> It feels like leporello has to store a lot of possible branch-outs of the state

Could you please clarify, what do you mean by branch-outs of the state?


>> 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.

[0] https://news.ycombinator.com/item?id=36940937


Are you using the monaco editor? If so please enable the autoClosingBrackets:

```js

monaco.editor.create(document.getElementById('container'), {

    value: 'const myVar = hello',

    language: 'javascript',

    autoClosingBrackets: 'always'  // <--- This
});

```

When I highlight "hello world" and press ', I want to see "'hello world'" instead of "'"

All that aside, awesome project


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 dig the core concept, particularly since I've been searching for a more efficient alternative to opening a blank DevTools window for executing various helper scripts. With that in mind, what about introducing a "Loose Mode" option of sorts, akin to a basic js interpreter? Granted, most of the tool's interactive functionalities probably wouldn't work in this simplified mode, but it would, in turn, expand the tool's overall versatility. Enabling you to use previously written scripts and/or experiment with code (such as copying an example snippet from the README) without having to first adapt it to a functional paradigm.


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.


Hope you don’t mind answering a few questions of mine.

What’s the functional subset of JS? Any literature on this subset? I don’t really know JS apart from hacking together a few scripts for fun.

If this is purely functional, does this mean we will need to use monads for IO like in Haskell?


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.

[0] https://www.youtube.com/watch?v=-6BsiVyC1kM


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?

fib(6), fib(7) // scratch:9:6 - unexpected token


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.


Sounds exciting. Looking forward to trying out the extension!


It looks amazing. That said the lack of a license is something you may want to address.


Really awesome , would love to see typescript support.


OP: Bug report- With Brave on Android the "try it" demo constantly zooms way, way in, making this unusable on mobile.


I played around with it for a bit and love some of the concepts. Is the tool aiming to be a business or a passion project?


I would be delighted to turn it into a business, but I may not be able to do it without securing funding.


This looks seriously awesome. Favorited it for later.

As a side note who / what narrates the video? ;)


It is an AI voice generator https://genny.lovo.ai/


it's the same AI girl voice you hear in like 30% of tiktok videos.

And she pronounced 'console' wrong. The noun should be pronounced /ˈkɑːn.soʊl/, not /kənˈsoʊl/.


[flagged]


Sorry downvoters, but the web is objectively in a completely shit state and nearly all of it is on the back of functional programming and ideas.

This was a rotten era. Frankly I’m glad the hype is over and people have worn thin on it. Only one more “functional is the best” cycle till I kick the bucket, thankfully.


Genuine question, what's bad about FP in your opinion? Do you disagree with using any of its parts or the general approach where people try to force a completely pure FP? Is using .map operator already too much? Where do you draw the line?


I draw the line at software being objectively dogshit on the back of people making absurd silver bullet claims on medium.

.map is not strictly functional programming. Replacing your data structures with slow and balls, hardware ignoring garbage because “it’s easier to reason about” while simultaneously producing the shittiest of dogshit software in history is functional programming right now.


Здравствуйте, Дмитрий Евгеньевич!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: