Messaging¶
Yes - another chapter called “messaging”, because this is Erlang after all.
It is very common for legacy APIs to send arbitary messages back to the invoking process as a means of communication, convenient, useful, handy… not immediately practical in Purescript however.
Consider the following code, where in Erlang we subscribe to some API that immediately starts sending us some sort of erlang tuple/record.
%% Subscribe to the legacy API
{ ok, Ref } = legacy_api:start()
%% And start receiving messages from it
receive
#legacy_message{} -> ... ... ... ...
If we were to write a straight wrapper for this API in Purescript, it’d look very simple indeed
First, the purescript foreign import, which merely invokes the function and returns the ref
module LegacyApi where
foreign import start :: Effect Handle
Which, in the Erlang is unpacked as thus
-module(legacyApi@foreign).
start() ->
fun() ->
{ ok, Ref } = legacy_api:start(),
Ref
end
end.
Now we have a problem - if we try and use this in Purescript, our message receiving code has to operate on the Foreign data type because it has no idea what an Erlang record is.
A further call into the LegacyApi wrapper could unpack this of course so this doesn’t present an immediate problem.
do
_subscription <- LegacyApi.start
msg :: Foreign <- receive
case LegacyApi.interpretForeign msg of
LegacyApi.ThisHappened -> ....
This might be okay, but it means if we want to receive any other kind of message we are out of luck unless we pack them into Foreign as well, and ask various mappers to attempt to unpack these foreigns in sequence until one works and oh boy this is not enjoyable in the slightest.
do
_subscription <- LegacyApi.start
_subscription2 <- LegacyApi2.start
msg :: Foreign <- receive
let ourMsg :: Msg
ourMsg = case LegacyApi.interpretForeign msg of
Just r -> Just $ LegacyMsg r
Nothing -> LegacyMsg2 <$> LegacyApi2.interpretForeign msg
This can somewhat get out of hand as we interact with more APIs and isn’t a terribly forthright way of doing business, what we really want to write is
do
msg <- receive
and that be the end of it
The choices¶
It’d be nice to be able to unpack these Foreigns into sensible types before we see them in our process, and to do this we have the following options
- Routing - intercept the messages with a proxy process and lift them into more appropriate types before sending them to the owning process
- Untagged Unions - describe the messages with an ADT and have them matched inline into more appropriate types
Most of the time you’ll want Routing as processes are cheap and this is easy, but if writing a wrapper around a native Erlang library, untagged unions might be more useful.