Skip to content

Instantly share code, notes, and snippets.

@Chadtech
Last active December 3, 2020 15:13
Show Gist options
  • Save Chadtech/c966d30613c588ef2dc45026a1e29731 to your computer and use it in GitHub Desktop.
Save Chadtech/c966d30613c588ef2dc45026a1e29731 to your computer and use it in GitHub Desktop.

Super Preliminary Elm - Web Assembly Research

Back in December 2017, I started working on something I called Elmish-Wasm, which was just an experimental repo to compile something Elm-like to Wasm. I made some progress, but the helpful people I talked and I came to the conclusion that most of what I had so far (Haskell and Regex to interpret .elm files into rudimentary wasm) was something the Elm compiler and the Elm AST already did. Writing that code was a lot of fun, but maybe not the most valuable way to explore Elm and WebAssembly. Before jumping back into this project I would like to record the important facts and questions related to compiling Elm to Wasm.

What is Wasm?

A lot of people talk about Web Assembly as if its C++ that runs in the browser. Thats not the case. This belief must come from that fact that C can currently compile to web assembly. Wasm is human-unreadable bytecode. There is a human-readable version of wasm, called wat. It looks like this..

;; A function that adds two numbers
(module
  (func $addTwo (param i32 i32) (result i32)
    get_local 0                     
    get_local 1          
    i32.add
  )       
  (export "addTwo" (func $addTwo))
)

This code exports a function called addTwo into JavaScript. It adds two numbers together..

Idea: write the Elm Virtual Dom in Wasm

Every animation frame the Elm virtual dom builds a virtual representation of the html page, finds the differences with a virtual representation of the prior html page, and then implements those differences in the dom. If that entire process could happen in Wasm, the virtual dom would become much faster. We would like to minimize the fixed performance cost of crossing into Wasm from JavaScript, which ideally would only happen twice per animation frame: once to show Wasm the new model, and once for Wasm to show JavaScript the changes that need to be made to the existing Html.

A step farther would be to keep the Elm application's model in Wasm entirely, so it doesnt have to cross into Wasm from JavaScript at all.

Question: How can you curry in C or Wasm?

Wasm isnt really like C syntax, and it seems to resemble ML syntax in the way that functions are naturally applied to whatever preceeding byte values happen to be there. Heres what I mean

i32.const 3
i32.const 4
i32.add

This code initializes a value of 3, then initializes a value of 4, and then adds them together. The add function has no "awareness" of what the two preceeding values are, it just adds whatever they may be together. Web assembly functions cannot return functions, so pretty plainly precludes currying.

But there must be some way to curry. One hint might be to not compile Elm functions to Wasm functions directly. For example, consider this snippet..

i32.const 1
i32.add

This snippet is basically waiting for a value to be put above the top line so it can add one to it. Its basically a function, and if it could just be inserted or applied to other snippets of wasm it would be. Dynamically generating wasm snippets sounds impossible or greatly inefficient. But I am probably missing something, since programming languages that do curry do compile to assembly. There must be a way.

Of course we dont have to compile Elm to Wasm. We could compile it to C and then Wasm. But I dont know that C is any more capable of currying than Wasm. Googling "currying in C" shows real results, but I dont take that to mean its fully possible in the narrow context of web assembly.

Whats exciting about Wasm?

Wasm is really fast. Most people understandably dont feel like performance is a priority; but others are trying to get websites to render in as few milliseconds as possible after page load, and others still are making software that actually does struggle to run given the performance constraints of the browser. Performance is good, some times necessary, but at least cant hurt.

But its not the raw performance thats really interesting. Whats a lot more interesting is the potential features high performance code could unlock. Today if you wanted a super high resolution computer generated video or a complicated 3D rendering, its probably out of reach of your users CPU. If the performance is there then its not out of reach. Today you can write JavaScript that compiles to a mobile app, but its an inferior option because the performance isnt there. More performance means JavaScript is a more valuable and more universal development tool on more platforms.

My personal hope, is that WebAssembly can bring all of Web Development back down to the very basics of computing, whereupon web development can build itself back up, free of the old ways of html and css. The performance doesnt matter, so much as the performance makes it viable to build new web fundamentals from scratch. WebAssembly is a way to escape the legacy of the internet.

How can we take advantage of these benefits?

Whenever you bridge between two programming languages, theres always a huge performance penalty per crossing. Minimizing this penalty means maximizing the amount of run time that happens on the wasm side of JavaScript to Wasm connection. Excluding this fixed performance penalty, the best estimate I have come to, is that Wasm code takes 3% as long to run as JavaScript code.

@PixelPartner
Copy link

How about analysing how curried swift is translated to wat

  func curried(x: Int) -> (String) -> Float {
    return {(y: String) -> Float in
      return Float(x) + Float(y)!
    }
  }

Also swift has enums with parameters that seem very similar to Elm's union types.
Would also be interesting to see this as wat.

For now the idea to generally transpile Elm to swift instead of JS is not yet a valid option, as the swift compiler is only supported on Ubuntu and macOS. But I like to dream on.

@PixelPartner
Copy link

There's the WIP project NectarJS that compiles JS to executables on many targets including ASM.js and WebAssembly.
Sample compile runs are limited to 300 char input, so no chance to test my elm.js

@Chadtech
Copy link
Author

Hey @PixelPartner

Sorry I just saw your messages now.

That has got me thinking, ASM.js must be able compile curried functions right? Thats a great lead! Thanks.

@janwirth
Copy link

There might be interesting information in this thread:
WebAssembly/design#774 (comment)

@janwirth
Copy link

Skimming this article I think it shows a way to implement curried fns in wasm.
https://crypto.stanford.edu/~blynn/lambda/wasm.html

@PixelPartner
Copy link

How about analysing how curried swift is translated to wat

  func curried(x: Int) -> (String) -> Float {
    return {(y: String) -> Float in
      return Float(x) + Float(y)!
    }
  }

Also swift has enums with parameters that seem very similar to Elm's union types.
Would also be interesting to see this as wat.

For now the idea to generally transpile Elm to swift instead of JS is not yet a valid option, as the swift compiler is only supported on Ubuntu and macOS. But I like to dream on.

Swift is now also supported on Windows platforms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment