unisonweb / unison

A friendly programming language from the future
https://unison-lang.org
Other
5.75k stars 269 forks source link

Unison for front-end development #1783

Open pchiusano opened 3 years ago

pchiusano commented 3 years ago

It would be pretty nice to be able to use Unison for front-end development, especially if backend services are also written in Unison. Communication is seamless - just relocate a computation to the backend and have all the networking and serialization be done by Unison! No more sending JSON blobs around and issuing HTTP requests manually. No need to arbitrarily fragment your codebase into front-end and back-end code - you can share code between the two no problem, and you don't need some elaborate build setup to make it happen either.

The basic idea is introduce a new builtin ability Browser. It will be opaque, much like the existing IO type, and we can fill it in over time with low-level browser functions in the Browser Object Model, for instance:

-- in browser namespace

builtin ability Browser
builtin type Window
builtin type Document
builtin type Element

window : {Browser} Window
Window.document : Window ->{Browser} Document
Document.getElementById : Text -> Document ->{Browser} Element

Nothing creative here, just low level imperative functions for accessing the browser - we can add to it over time. It's expected that people will write higher level libraries on top of this.

We use Browser rather than IO because the environments are different! The UCM run command doesn't have access to the Browser ability! So within UCM you can write programs that use Browser, but you can't execute them.

So how do you compile programs to JS for execution? The simplest thing is maybe a builtin function:

Browser.compileToJavascript : '{Browser} () -> Text

This would produce a single-page app. You'd take the result of that and save it to a file myprogram.js. I could imagine a few other compilation modes, like if you want to produce a JS library rather than an SPA.

You probably want some FFI so people can invoke JS functions. I think the general approach outlined in #1404 makes sense: foreign functions are represented as operations of an ability. And then there has to be some way of telling the compilation function how those foreign operations should be translated to JS code.

Speculating here -

Browser.compileToJavascript : FFI g -> '{Browser, g} () -> Text

Open question - what is FFI g? It has to provide some way of converting all the operations of g into JS.

Implementation ideas

Idea 1: MCode interpreter in JS

Putting aside the FFI for a minute, I wonder if we could write a simple MCode interpreter in Javascript (or any language that compiles to JS). We don't need to be able to interpret all the IO primitives, just the pure builtins and the Browser builtins.

Then the compileToJavascript is just producing a self-contained JS file that has some representation of the MCode needed for the program, and then the little embedded interpreter for MCode. Calls to Browser builtins turn into regular JS calls. This won't be super efficient but is probably fine for a lot of front-end code.

For FFI, it seems like you'd use the FFI argument to compileToJavascript to produce bindings for the builtin functions. MCode would have to support foreign calls somehow?

If MCode is too late in the process, it may make sense to hook in at an earlier stage of the compiler, so perhaps the compileToJavascript is more like a UCM command rather than a function you can call at runtime (but it would be cool if you could call it at runtime).

Idea 2: variant of @jaredly Rust runtime

This is really Idea 1, but using a Rust runtime rather than writing the interpreter directly in JS.

I think I'd want to start with a Rust runtime that basically is an MCode interpreter. So it shouldn't be responsible for things like reading definitions from a codebase or doing arbitrary IO, just interpreting the IR for pure Unison code that can have embedded Browser ability requests.

@jaredly this is a little different than what you've implemented - it would be a lot easier since it's responsible for less. And we could recycle a lot of your existing work if we went this route. WDYT?

Idea 3: compile the entire Haskell runtime to JS

This feels kinda icky and I kinda doubt it will work very well. The Haskell runtime uses Haskell's IO type heavily and it just generally has access to a bunch of stuff (like file I/O, networking) that the browser shouldn't have access to.

Idea 4: a proper compiler

This would try to produce actual JS code rather than something that is interpreted in JS. A difficulty here is we can't just reuse the JS call stack due to the stuff we need to do to support abilities (and also, like proper tail calls), so there'd have to be something fancy done here. Level of effort is probably very high.

/cc @dolio

jaredly commented 3 years ago

@pchiusano I think idea 1 is the most promising, and I have something like it working (albeit with my own version of MCode) in the unison.rs codebase. My rust/wasm runtime foundered when I tried to deal with garbage collection, because there just aren't the necessary primitives to do proper cross-boundary garbage tracking, such that holding on to handles to javascript objects results in a lot of garbage. with idea 1, we just rely on javascript's garbage collection to do everything, and it's a lot simpler. Idea 4 seems likely to be faster than idea 1, and it would definitely require having full type annotations on the AST, but I don't think it would be insurmountably hard. I do agree it's probably complicated enough to be a later-on project.

hojberg commented 3 years ago

Exciting! This is going to be really interesting in exploring Unison tooling like documentation/code browsing, quick sharing of code in a jsfiddle way, rich editing experiences etc.

samgqroberts commented 3 years ago

Just wanted to chime in and say I love this idea and hope it goes the distance! Thank you @pchiusano for this writeup, and thank you @jaredly for your work over in https://github.com/jaredly/unison.rs. I've been considering this idea and playing around with that codebase for a couple weeks now and wanted to contribute a few thoughts about what the future could hold on top of this:

  1. I would love to see this support an Elm-like interface. An interface like Elm's Program, and indeed the entire Elm Architecture, would likely fit very naturally in Unison due to the obvious language-level similarities. Unison could benefit a lot from the years of research, design and development that went into forming Elm's language-level types and interfaces. Unless I'm missing something, this Browser ability (and likely some accompanying tooling, see 4 below) would pave the way for the rest of development toward this being library development inside Unison (right?), which is really great.
  2. It would make me simply giddy to further evolve that Elm-like interface to support Server-Side Rendering. In my Typescript + React day job I've been buying more and more into the JAMstack, specifically being delighted with NextJS. I've been very excited to see this brought to Elm with elm-pages, which may further light the way for Unison here. This may also represent a significant new branch to Unison's monetization strategy - I've been using Vercel to deploy and manage my personal NextJS website and so far it's been a wonderful experience. With an Elm-like interface, and/or an elm-pages-like interface, perhaps unison.cloud could also offer a Vercel-like service for building, deploying, monitoring and managing Unison web projects.
  3. It would be peachy indeed if we lived in a world where all data required to produce an artifact on the web could be encoded and worked with solely in Unison. Not just the more natural fits like the view and update functions of the Elm Architecture, but also the traditionally static file assets - images like favicons, web app manifests, robots.txt, anything. I'll mention that elm-pages looks like it takes a step in this direction, which I love to see. This endeavor might be restated as: "The Unison port of Elm's Program type should be broadened to encapsulate any behavior or consideration a web site may need. Program -> <tooling> -> <directory(s) of html / js / css / png / * assets> should be able to produce any web site achievable by other means"
  4. How absolutely neato would it be if the tooling that interpreted the Program type acted similarly to (and therefore brought a similar level of delight as) ucm? This is to say: something the user has a conversation with, that can watch what you're doing and accept direct input simultaneously. Perhaps something like:

    
    Welcome to the Unison Web Project Manager!
    
    list  : print the list of Program terms in your Unison codebase
    serve : serve a Program term to localhost, watch for updates to recompile and refresh
    build : build static assets for a Program terms

list

I found 3 (2 unique) Program terms:

  1. a1bd51c... under name .mysite.main

  2. ba1b742... under name .unisonweb-dot-org.trunk.main

  3. ba1b742... under name .unisonweb-dot-org.update-docs.main

serve 1

Now serving Program under name .mysite.main (#a1bd51c...) at localhost:3000

Detected change to .mysite.main (previously #a1bd51c..., now #c1a2b53...). Updated assets at localhost:3000


This strikes me as an improvement to the standard you find around where the command line is the interface to various custom scripts, some of which may spawn a watch task that you can't meaningfully interact with further. Adding on to this lofty idea, perhaps this "Unison Web Project Manager" could be embedded into `ucm`, under some `ucm` plugin protocol.
clojj commented 3 years ago

Just to add to @samgqroberts first point: possibly having Elm-Architecture for Unison Frontend-Development would be absolutely (and beyond) excellent ! Also... given this georgeous Codebase explorer written in Elm, how could one not want that frontend quality for Unison itself ?

Given the amount of effort Unison demands generally (for reaching Release-Status), maybe there could be some intermediate frontend-solution that does not get in the way for some future Unison-Elm-Architecture (?)