1
votes

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:

  • Is this the correct way of implementing reconnection?
  • What is the type of runWs (any hints on how I might work this out for myself would be fantastic)?
  • If adding a type signature doesn't fix the 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
1

1 Answers

1
votes

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.