Confused about CLJS limitations on node

Sorry if this is a silly question, but I’m lost in the various options online, and need some guidance. I’m trying to use ClojureScript along with Max/MSP, which now has the ability to host a nodescript with some convenient integrations. I managed to get this working at a basic level, compiling some cljs to js and gettting it running, using shadow-cljs to compile the cljs to js, and having max watch the script and reload it.

My goal for this is to be able to take advantage of the live coding possible in a clojure repl, so I’m hoping to be able to eval code sent in to this object as text, ideally even reloading part of the script as one can do at the repl. What I’m confused by is what the limitations are of cljs vs regular clojure. As in, can I do things like dynamically evaluate new code and hot reload, and take advantage of the full facilities of macros, in a pure cljs environment? Is this what Lumo and Planck allow? In a perfect world, I’d be able to make a nodescript that runs a little electron gui, but also gives me the full dynamic lisp experience. Any pointers or help is much appreciated!

iain

I don’t have much experience on Clojure. In my impressions, people use Clojure mostly replace code by evaling (require ... :reload) or maybe doing that by send code via nREPL.

In ClojureScript it’s different. We watch files and replace whole file when it’s saved to disk. Since most ClojureScript apps are web pages, it’s just a fluent way replace content of the page. And that’s similar for a Node.js script. There are REPLs for ClojureScript devtools too. But normally I don’t use it. Chrome provides a JavaScript REPL by default and I’m more familiar with that.

Not very sure about macros. But shadow-cljs compiles macros. I may not recommend debugging macros in shadow-cljs because error messages in macros throw by shadow-cljs is not as readable as in Clojure. I would start a clj script for try macros.

I don’t quite understand what you are asking and I also have no idea what “Max/MSP” is or what kind of node integration it provides. Not all JS engines are created equal and sometimes they are severly restricted in what they are allowed to do.

To get a working REPL the script must be allowed to open a websocket connection to the shadow-cljs instance (running on localhost). This is usually taken care of on startup, the startup however might be influenced by whatever the Max/MSP stuff does.

This is almost certainly wrong. shadow-cljs must be in charge of reloading as anything else doing the reloading will get in its way and destroys the state shadow-cljs is trying to maintain.

CLJS REPLs are a bit more constrained than CLJ but for the most part they work the same.

You can use macros but you have to follow a few rules and you cannot write then in a CLJS REPL. They are written in CLJ. I blogged about CLJS macros recently, maybe that helps.

It would help if you provided code examples of what you tried that maybe didn’t work. A build config or an example repo is always useful too.

Thanks Thomas, that’s helpful, pretty sure now what I want to do will not be possible with shadow-cljs. Max/MSP (now just “Max”) is the the world’s most widely used platform for academic computer music, and recently added some ability to work with node scripts as max objects. I was trying to figure out how well I could use that with CLJS. On another forum it was mentioned to me that I might get better results with Lumo, so part of my reason for posting here was to find out if embedding something like Lumo in max might work.

Would help if you describe a bit what you are trying to do.

My goal for this is to be able to take advantage of the live coding possible in a clojure repl, so I’m hoping to be able to eval code sent in to this object as text, ideally even reloading part of the script as one can do at the repl.

That is standard REPL functionality, all CLJS REPLs can do that.

You might be looking for pure self-hosted CLJS if you intent to build your own live-coding UI. If you want to use one of the standard editors then shadow-cljs will probably be easier.

Don’t know how easy it would be to “embed” Lumo though. I think its main purpose is to be used as-is and a standalone script.

What’s not clear (and I don’t know how to tell based on your description without going and downloading Max and using it) is how the JS code gets executed by Max. You say shadow-cljs can’t do what you want, but it’s not clear from what info you’ve given if that’s true or not.

In order to get REPL functionality, you fundamentally need some JS process running that shadow-cljs can load the REPL code in and listen for new evaluations. When you shadow-cljs watch <build id>, shadow-cljs automatically includes this REPL code in the JS output. So the next step is to have that run inside of a Node.js process, continuously. After that, it’s getting a REPL client connected which you can do with shadow-cljs cljs-repl <build id>. Connecting your editor to it for more complex REPL interactions depends on the editor you are using.

After reading Max’s docs for Node for Max, what I’ve deduced is:

  • N4M provides a Node.js module called max-api
  • Max runs your scripts inside of a side-car Node.js process that is controlled by Max
  • Max does not do any compiling of it’s own

It definitely sounds like shadow-cljs will be easier than using Lumo. Lumo requires you to run the all of your code inside of the lumo process, which will require finagling with the way N4M tries to execute your scripts. shadow-cljs can output a .js file which you can then run in Node.js and register with Max via a patch.

I have no idea how Max is usually used, or if this actually matches reality. I’m basing this off of documentation. But I think the basic steps would be:

  • Create a shadow-cljs project with a :node-script type build
  • Create a Max patch that points to the output of the build
  • Start your shadow-cljs watch: shadow-cljs watch <build-id>
  • Start your node script via Max (? some sort of UI action perhaps?)
  • Start a REPL client: shadow-cljs cljs-repl <build-id>

You should end up with a CLJS REPL where you can redefine your Max handlers/etc.

I haven’t used Node4Max yet, but have worked with Max’s other JavaScript objects extensively. It’s model isn’t something well-suited for ClojureScript, as you’re supposed to set variables in the script instance’s global namespace directly, and as mentioned, Max handles the reloading since it needs to re-figure out inlets/outlets/etc. I can’t imagine Node4Max would be much different.

Hi folks, thanks for the interest in the topic. @mattly, it actually is quite different, though the differences are not super well documented. With node-for-max, Max fires up a separate node process, and the max-api methods (behind the scenes) are just serializing data and sending over sockets (I think it’s sockets, but it’s def serialized network communication). So this is quite different from the way the JS object works, where the JS is being run as part of the Max process.

@lilactown what you describe is what I was trying, but I doing “shadow-cljs watch” seemed to just hang. I will try again later and post what I see, as that is exactly what I want: a cljs repl what is hooked into Max.

One could of course run the cljs (or clj on the jvm) completely separate, and do the serialization manually, but I was hoping to avoid that if I can. I know some previous lispers have done something similar to that pattern

Sounds like there might be an issue with configuration or your files. What you’re saying here isn’t outside the realm of possibility.

It is worth noting, though, that use N4M will only give you the ability to interaction with your Node.js handler code via the REPL. I’m not sure how much you’ll be able to actually interact with Max, as I’m not familiar with what max-api exposes and how it works with calling it synchronously.

Isn’t all state managed by Max anyways? If so, file watches would seem to be as good as live reloading, I mean, in Max it is live, since Max is not restarting, the next input to the patch will have updated output. I haven’t toyed with Max since 5.0 though like 10 years ago, so maybe things have changed.

Does it actually hang or is this maybe just your misunderstanding of how shadow-cljs operates? watch will start a process that will compile your build and then wait for changes and re-compile. So if you receive a Build completed log message it may appear to “hang” while in reality it is just waiting for changes. This process never takes any further input.

Instead you run shadow-cljs cljs-repl <your-build-id> separately (in another terminal) to get your actual REPL. Alternatively you use nREPL in your editor. A socket REPL is also available.

I think figwheel operates differently and shows the REPL prompt together with the “watch”. In shadow-cljs these are separate things so that may be confusing if you are expecting the other behavior.

@thheller you are right, I was expecting the figwheel behaviour. I will try this again in the next couple of days and see if that was the problem.

@didibus and @lilactown, you are correct that there is much less direct interaction with Max through node than in C externals or the JS, but I’m aware of that. The intention is to create a clojurescript (or other lisp) system for working on score data only, with the ability to pipe that data back and forth to Max. It will likely involve some kind of shadow data that is maintained on both ends and synchronized based on events or clock time (ie every 16th note or something). I am also exploring lisping with greater API integration, and I expect that will shake out to be something like S7 or Chiba Scheme running in a C external. For the clojure stuff, limited max API use is okay, I just want to be able to make a clojure gui into my score data and ideally be able to run clojure functions over the data from some repl-like thing. The API for node-for-max is limited to sending and receiving dictionaries and simple messages. Thanks for the tips!

1 Like

If there’s a C API, JNI is another option. Clojure devs have been leveraging that quite successfully for many libs.

You certainly can live-code into MaxMSP from CIDER, via Figwheel - I’ve done it on-stage in front of an audience. I have a demo repo here, with the tweaks needed to target Node rather than the browser:

Obviously, you don’t have a DOM so various JS libraries won’t work, but otherwise it seems pretty usable. Any questions, please ask.

2 Likes

@cassiel, fantastic! I tried for a quite a while to find someone doing this, but for whatever reason didn’t find your repo. I will check it out. thanks so much!