Previous entries..
Armed with the tools from the last explorations, it should be relatively trivial to start interacting with OTP in Purescript, and we'll give that a bash directly in this entry to see how well that goes..
Ignoring applications and supervisors for a moment, we can probably write a genserver fairly trivially in Purescript directly using an FFI to call into OTP and shuffle some data types
The low level FFI could look a little like this, where the call is just an effect that results in a genserver starting.
-module(genServer@foreign).
-export([startLinkImpl/3]).
startLinkImpl(ServerName, Module, Args) ->
fun() ->
gen_server:start_link(ServerName, Module, Args, [])
end.
module GenServer where
import Prelude
import Erl.Atom
import Erl.Data.List
import Erl.Data.Tuple
import Effect.Uncurried (mkEffectFn1, EffectFn1)
import Effect
foreign import data StartLinkResult :: Type
foreign import startLinkImpl :: forall args. (Tuple2 Atom Atom) -> Atom -> args -> Effect StartLinkResult
startLink :: forall args. (Tuple2 Atom Atom) -> Atom -> EffectFn1 args StartLinkResult
startLink serverName mod =
mkEffectFn1 \args -> startLinkImpl serverName mod args
Note the mkEffectFn1 allowing us to pass this effectful function into Erlang code, and the parameterised argument type allowing us to have custom arguments for the gen server we're writing.
Allowing us to write a gen server that looks like this:
module TestServer where
import Prelude
import Erl.Atom
import Erl.Data.List
import Erl.Data.Tuple
import GenServer as GenServer
import Effect.Uncurried (mkEffectFn1, EffectFn1)
import Effect.Console (log)
newtype State = State {}
startLink :: EffectFn1 String GenServer.StartLinkResult
startLink = GenServer.startLink (tuple2 (atom "local") (atom "testServer")) (atom "testServer@ps")
init :: EffectFn1 String (Tuple2 Atom State)
init = mkEffectFn1 \args -> do
_ <- log $ "Gen server started with args: " <> args
pure $ tuple2 (atom "ok") (State {})
In this case, we've decided our start args are a string and we'll just log that out on startup, and we return a newtype with a record containing our gen server state from the init function, and of course we can just plug this into a standard Erlang supervision tree and we'll end up with a gen server running which if sent any messages will simply crash :).
init([]) ->
{ok, { {one_for_all, 0, 1}, [ #{ start => { testServer@ps, startLink, [<<"Your args">>] },
type => worker,
id => test_server
}
]} }.
Already we can see that we've not gained an awful lot by writing this thin wrapper allowing us to write gen servers in this way
We can see that manually writing and using 1:1 mappings between Purescript and Erlang code for use across an application is not going to be a sustainable ideal - and yet we will press on and look at a few of the attempts made to do this in the following blog entries before finally trying to do something a little more idiomatic.
2020 © Rob Ashton. ALL Rights Reserved.