About using purescript-run for evented things requiring callbacks

chexxor [4:08 PM]
Oh my, a libuv wrapper in PureScript? That’s neat. Node has that built-in, in a way. It might make sense to write programs which support Node and C backends such that the libuv effects are behind extensible effects, so there can be an interpreter for those effects for each of the backends. So just don’t compile the JS interpreter for the C backend, only the C effect interpreter.

natefaubion [4:16 PM]
Run doesn’t work well with evented things
at least, as is

chexxor [4:43 PM]
Is it possible to make it nicer for evented things while staying within the current design of Run?

natefaubion [4:46 PM]
@chexxor it depends on what you mean. Effects in Run are first-order, which means Effects can’t be parameterized by other effects. Evented things usually require callbacks, and thus doesn’t work well with Run (these things are higher-order). You can have evented things at the edges though which feed into some Run program though.
You can have a version of Run with higher-order effects, which is what I was playing around with this weekend
It does mean you need a different abstraction than Functor though

chexxor [4:55 PM]
@natefaubion What’s an example of an effect which is parametrized by other effects?

natefaubion [4:56 PM]
fork/join

chexxor [4:56 PM]
Seeing a type signature of s/t similar might help me understand.

natefaubion [4:57 PM]
an effect that takes another effect, and has control over how that effect is run and dispatched
it’s not always apparent in the type signature
for example, catch looks like it takes a higher-order effect, and it kind of does
but it’s implemented as AST substitution
and a proper catch effect, rather than an interpreter, would be higher-order

3 Likes

have a problem with this too

I cannot encode

withTransaction
    :: forall a
     . Connection
    -> Aff a
    -> Aff (Either PGError a)

function to use with purescript-run

I have tried

module FeatureTests.FeatureTestSpecUtils.Db where

import Protolude

import Data.Exists (Exists)
import Data.Functor.Variant (FProxy(..))
import Database.PostgreSQL as PostgreSQL
import Run (Run)
import Run as Run

-- | NOT WORKING, Exists is not a functor
-- | data WithTransaction' r next exists = WithTransaction' (Run (db :: DB r | r) exists) (Either PostgreSQL.PGError exists -> next)

data DbF next
  = WithTransaction ???? next

derive instance functorDbF :: Functor DbF

type DB = FProxy DbF

_db = SProxy :: SProxy "db"

withTransaction :: forall r a . Run (db :: DB | r) a -> Run (db :: DB | r) (Either PostgreSQL.PGError a)
withTransaction action = Run.lift _db (WithTransaction ???? ????)

runDb
  :: forall r
   . PostgreSQL.Connection
  -> Run
     ( aff :: Run.AFF
     | DbEffect
     + r
     )
  ~> Run
     ( aff :: Run.AFF
     | r
     )
runDb connection = Run.interpret (Run.on _db handleDb Run.send)
  where
    handleDb = case _ of
      WithTransaction next -> do
        result <- Run.liftAff $ PostgreSQL.withTransaction connection ?????
        pure $ next result

@srghma Run is a first-order, algebraic effect system. Basically, you can’t have effects that directly hold other effects, since this requires a fix-point over the effect type. Sometimes these effects can be made first-order by splitting it into enter/leave (or push/pop)-like effects. So instead of WithTransaction, you might have StartTransaction ... | CommitTransaction ..., and then your interpreter must handle a stack itself in a safe manner.

1 Like

@srghma I worked on this recently in the webrow “framework”.
I think I’ve started with something similar to the model described by @natefaubion above but I encountered problems related to “the native js error handling” mixed with transaction / connection resource management (here are some related tests).

In the current iteration I’ve moved to the new resourcet lib by @robertdp . So instead of an interpretation to the Aff I’m interpreting everything to the intermediate ResourceT layer where all resources are cleaned up.
So finally I have this working version of pg integration with transaction handling embedded in the run app. Additionally we provide a tiny layer of helpers for selda and everything seems to work well during testing and on our dev server (we monitor pg Pool connections and entries from postgreSQL pg_stat_activity table). The final API seems to be quite nice and guards on the type level against transaction nesting - here are pieces of some basic tests for our selda sugar.

  • Please be aware that I’m using devel branches of the postgresql-client and selda here (we can talk on priv about the details of course :wink: ).

  • Please don’t judge the quality of the code pieces above - I’m doing a lot of quite intense prototyping recently.

4 Likes

This seems cool

But I dont understand

but I encountered problems related to “native js error handling” mixed with transaction / connection resource management (here are some related tests)

the Run.liftAff $ liftEffect $ throwError $ Effect.Exception.error "Aff throw before commit" error wasn’t handled?

the Run.liftAff $ liftEffect $ throwError $ Effect.Exception.error "Aff throw before commit" error wasn’t handled?

I’m not sure if it is impossible, I just wasn’t able to implement this kind of error handling and keep my interpreter polymorphic on the effects tail (I want to be able to possibly handle interpretation of multiple “resource managing” effects - I want to have possibility to manage redis transactions, files handlers etc. by using other effects). In other words I wanted to interpret my Run (Pg + Aff + eff) into Run (Aff + eff) and be able to rollback transaction and cleanup connections “somehow”. But in my interpreter I wasn’t able to catch Aff exceptions and "remain in the Run". Unhandled Aff exceptions broke the “interpretation chain” and escaped my cleanup logic.

When I’m using ResourceT as a base layer this is no longer the problem. Additionally I wasn’t forced to touch nearly any other pieces of the app when switching to it and it provides a really generic layer for resource management!

I hope that I’m not talking too much bull*** here and again I’m not proud of the past code snippet linked above :wink:

2 Likes

related https://github.com/natefaubion/purescript-run/issues/32

P.S.

Today I have learned that our Free from purescript-free library is not actually Free from haskell, but rather Freer from https://hackage.haskell.org/package/freer

because the Monad instance of our Free doesnt require f to be functor