First, some of my own background, which might help explain my thought processes: I am relatively new to Elm, but I have been developing with JavaScript, Haskell and Elixir for a long time.
So, I am in a very interesting situation. I am in the progress of building a so-called ‘Decentralized Application’, which is a fancy term of stating that the application will be a HTML+CSS+(Something compiled to)JavaScript-application that interfaces with the (in this case) Ethereum Blockchain.
This application might end up running in different contexts: It will probably run inside a web-browser(-like) environment, but the method that it will have to use to interact with the Blockchain Environment is through the so-called Web3 Provider.
Now, depending on what context the application is executed in, one of the following things needs to happen:
- If executed in the Mist Etereum wallet, use the provider that Mist injects into its browser window (
web3 = new Web3(window.web3.currentProvider)
). - If executed in a web-browser when MetaMask is installed, use the provider that MetaMask injects into the browser window (
web3 = new Web3(window.web3.currentProvider)
) - If executed in a web-browser when no external tools are installed, Web3 needs to be set up with a
web3 = new Web3(web3.providers.HttpProvider(some_url))
that links to an RPC-exposing Ethereum Client endpoint.
All of this is only tangentially related to Elm, of course, but it might give some background about this problem, at least showing that it is infeasible to re-write this logic in Elm because in certain contexts we need to depend on injected JavaScript calls.
Side note: There is the elm-ethereum package, which is able to create Tasks by using the Http-library directly, but it does not support other wallets except when the user themselves sets up a lot of ports.
So, now I am looking into ways to make Elm interact with it.
I know of the following approaches:
- Use a Cmd Port to send requests to JavaScript, and send the responses back using a Subscription Port.
- Create a special ‘program’ wrapper (Akin to Html.program, Navigation.program and similar) that uses (1) under the hood but hides most of the implementation details.
- Create a Native/Kernel module.
Here’s my thoughts to each of these methods so far.
Ports
It’s impossible to create a library out of ports + JavaScript, because there is a lot of plumbing that needs to be put in place in the application programmer’s code to ensure that things work.
Also, it is very common for multiple (asynchronous!) calls to Web3 to need to happen after one-another before we have arrived at the final interesting result. Ports create Commands, and Commands do not seem that composable.
Custom Program
This has the same advantages and disatvantages as the former, except that some of the plumbing can be hidden because we can wrap the port back-and-forth in our own program wrapper.
However, the result will still be calls that are not that composable.
Native/Kernel code
It seems like writing native code would be the only way to create e.g. Tasks, which seem a lot more composable than Commands.
However, writing Native code has been discouraged for a long time and in 0.19 this will no longer be possible at all.
Neither of these approaches seem satisfying.
What am I missing?
Help is greatly appreciated!