I'm trying to implement Websocket reconnection in PureScript and am at a complete loss at how to proceed. I've added the reconnection code at the top level due to the use of Aff
; I think this is the correct place but I'm not sure.
I've tried to implement it as I might in Haskell but I can't make it typecheck due to an EscapedSkolem
error in runWs
. I get the impression I can fix this by adding a type signature but I can't for the life of me work out what the signature might be!
So I have three questions:
runWs
(any hints on how I might work this out for myself would be fantastic)?EscapedSkolem
error how would I go about fixing it?And finally, I'm a complete newb when it comes to PureScript so if anything's unclear please point that out and I'll try and clarify.
EDIT: Added the error compiler output and changed the title slightly.
module Main where
import Prelude
import Control.Coroutine (Producer, Consumer, runProcess, consumer, ($$))
import Control.Coroutine.Aff (produce)
import Control.Monad.Aff (Aff, delay)
import Control.Monad.Aff.AVar (AVAR)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Eff.Exception (EXCEPTION)
import Control.Monad.Eff.Ref (REF)
import Control.Monad.Eff.Var (($=), get)
import DOM (DOM)
import DOM.Websocket.Event.CloseEvent (reason)
import Data.Either (Either(..))
import Data.Maybe (Maybe(..))
import Data.Time.Duration (Milliseconds(..))
import Halogen as H
import Halogen.Aff (HalogenEffects, awaitBody, runHalogenAff)
import Halogen.VDom.Driver (runUI)
import Log (Query(..), component)
import WebSocket (Connection(..), URL(..), WEBSOCKET, newWebSocket, runMessage, runURL, runMessageEvent)
wsURI :: URL
wsURI = URL "ws://localhost:6385"
reconnectionDelay :: Milliseconds
reconnectionDelay = Milliseconds 10000.0
main :: forall eff. Eff (HalogenEffects (console :: CONSOLE, err :: EXCEPTION , avar :: AVAR , dom :: DOM , exception :: EXCEPTION , ref :: REF , ws :: WEBSOCKET | eff)) Unit
main = do
runHalogenAff do
body <- awaitBody
driver <- runUI component unit body
---> Replace this: <---
runProcess (wsProducer $$ wsConsumer driver.query)
---> with this: <---
-- runWs driver
-- -------------------------------------------------
-- -------------------------------------------------
--
-- Reconnection function
-- runWs :: ????????
runWs p = go
where
go = do
runProcess (wsProducer $$ wsConsumer p)
delay reconnectionDelay
go
-- -------------------------------------------------
-- -------------------------------------------------
wsProducer :: forall eff. Producer String (Aff (console :: CONSOLE, err :: EXCEPTION , ws :: WEBSOCKET , avar :: AVAR | eff)) Unit
wsProducer = produce \emit -> do
Connection socket <- newWebSocket wsURI []
socket.onopen $= \event -> do
log "onopen: Connection opened"
log <<< runURL =<< get socket.url
socket.onmessage $= \event -> do
emit $ Left $ runMessage (runMessageEvent event)
socket.onclose $= \event -> do
log $ "Socket Closed, returning to runHalogenAff: " <> reason event
emit $ Right unit
socket.onerror $= \event -> do
log "Error."
emit $ Right unit
wsConsumer :: forall eff . (Query ~> Aff (HalogenEffects eff)) -> Consumer String (Aff (HalogenEffects eff)) Unit
wsConsumer driver = consumer \msg -> do
driver $ H.action $ AddMessage msg
pure Nothing
And the compiler output is:
Compiling Main
[1/1 MissingTypeDeclaration] src/Main.purs:54:1
v
54 runWs p = go
55 where
56 go = do
57 runProcess (wsProducer $$ wsConsumer p)
58 delay reconnectionDelay
59 go
^
No type declaration was provided for the top-level declaration of runWs.
It is good practice to provide type declarations as a form of documentation.
The inferred type of runWs was:
forall t110 t120.
(Query a0
-> Aff
( avar :: AVAR
, ref :: REF
, exception :: EXCEPTION
, dom :: DOM
, console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
| t120
)
a0
)
-> Aff
( console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
, avar :: AVAR
, dom :: DOM
, exception :: EXCEPTION
, ref :: REF
| t120
)
t110
where a0 is a rigid type variable
bound at line 57, column 44 - line 57, column 45
[1/1 EscapedSkolem] src/Main.purs:54:1
v
54 runWs p = go
55 where
56 go = do
57 runProcess (wsProducer $$ wsConsumer p)
58 delay reconnectionDelay
59 go
^
The type variable a, bound at
/home/rgh/dev/purescript/translate/sidebar/src/Main.purs line 57, column 44 - line 57, column 45
has escaped its scope, appearing in the type
(Query a2
-> Aff
( avar :: AVAR
, ref :: REF
, exception :: EXCEPTION
, dom :: DOM
, console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
| t120
)
a2
)
-> Aff
( console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
, avar :: AVAR
, dom :: DOM
, exception :: EXCEPTION
, ref :: REF
| t120
)
t110
in the expression \p ->
let
go = ...
in go
in value declaration runWs
Src Lib All
Warnings 1 0 1
Errors 1 0 1
* Failed to rebuild; try to fix the compile errors
Compiler error messages may be hard to decrypt sometimes, but in this case it turns out to be the answer you're looking for. Let's look at your do block here:
do
runHalogenAff do
body <- awaitBody
driver <- runUI component unit body
runWs driver.query -- < assuming you made a small mistake here
I usually start by desugaring, I find it makes it easier to follow the types, but ymmv:
runHalogenAff $
awaitBody >>= \body ->
runUI component unit body >>= \driver ->
runWs driver.query
Looking at the signature of runHalogenAff
, we can see that it accepts an argument of type Aff (HalogenEffects eff) x
, meaning the following term must evaluate to a value of that type. It must be then that runWs
returns a value of that type.
Now let's turn to runWs
. Its argument is a natural transformation f ~> m
which in your example takes your query algebra into the Aff
monad. We can write this down and ask the compiler to figure out the rest for us:
runWs :: (Query ~> Aff _) -> Aff _ Unit
That will build successfully and give you what you can fill these holes with. Here is the final signature:
runWs :: forall eff.
(Query ~> Aff (HalogenEffects
( console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
| eff
)))
-> Aff (HalogenEffects
( console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
| eff
)) Unit
Indeed that is exactly what the compiler output gives you. I am assuming the error message "the type variable a has escaped its scope" is because of the universal quantifier in the definition of a natural transformation.