Bindings for Testcontainers

Hello everyone,

I’m pretty new to Purescript and to functional programming in general.

I’ve been struggling for the past couple of months trying to wrap my head around all the refactoring I need to do in my head in order to become proficient with FP and in the meantime I decided to test my skills and to learn by creating some small projects: one of this used some pretty simple bindings for Testcontainers, which - for those who do not know it - is an excellent library that allows to start and control docker containers on the fly, really useful for integration tests on backends. I decided to extrapolate it in a separate library and clean the code a little bit, and published it on my Github profile: here.

It’s still in a very alpha version, but some basic functionalities are working and I’m using it in a different project to implement some basic integration tests.

Now, the main reason why I’m writing here is because I’d love for someone with more experience than me to take a look at my code and tell me where I could improve or if there are some well-known FP patterns that I missed or should use (the only thing I’m aware about in this project is that I could have used a StateT monad at some point but I decided to write my version of it just for learning, I will probably refactor that part!)

In any case: feel free to use it and contribute if you find it interesting!

3 Likes

Just a few thoughts:

For your FFI, you don’t need "use strict"; anymore since we output ES modules as of purs 0.15.x

For your FFI, you might want to use EffectFnX/FnX rather than the curried functions you are using now. A bit more boilerplate (I’d suggest saving some template/snippet for making this easier to type), but it’ll reduce the number of closures created for each call.

Code like this:

foo = 
  let
    binding = "foo"
  in
    use binding

can be rewritten to

foo = do
  let binding = "foo"
  use binding

Maybe refactor this pattern?

  resFiber <- forkAff $ startContainer cnt
  liftEffect $ Console.log "Started container, waiting for launch..."
  res <- joinFiber resFiber

into something like this:

noteFiber msg aff = do
  resFiber <- forkAff aff
  liftEffect $ Console.log msg
  joinFiber resFiber

Lastly, your library has a Main module with a main entry point. That will prevent you from publishing this as a library. Since you’re using the new spago anyways, maybe you should make a bin subpackage that contains just that part?

2 Likes

Hey, thanks for your reply!

Also, thank you for your Jordan's Reference, it is one of the best resources for learning PureScript :slight_smile: and one of the few that made it into my browser’s bookmarks :smiley:

I didn’t know that, fixed it, thank you!

Isn’t this true only if we are in a monadic context?
For example, this works

doSomething :: forall m. MonadEffect m => Int -> Int -> m Int
doSomething x y = do
  let sum = x + y
  pure sum

But this should not work:

doSomething :: Int -> Int -> Int
doSomething x y = do
  let sum = x + y
  pure sum

Yes, that was a mistake on my side, I was using it to test locally while waiting to get acquainted with purescript-spec in order to write some real unit tests… now that I have tests I’ve removed the Main.purs file and hopefully one day I will be able to publish it in the pursuit library :slight_smile:

Thanks again for your review, I appreciated it a lot!

Common misconception! In both Haskell and PureScript, the do syntax doesn’t universally require a Monad instance; it only requires a Monad instance if you bind something with <-. Preferring to use do for tidier let bindings is a minority position, I think, but it absolutely works.

1 Like

do is just syntax sugar. When one uses binding <- computation, a do block will desugar this to bind computation \binding -> restOfDoBlock. But if there is no <- going on, then it effectively desugars to

let
  letBinding1 = ...
  letBinding2 = ...
  ...
  letBindingN = ...

in
  finalLineInDoBlock

For example, see this snippet: Try PureScript!

As long as the final line in the do block is a valid expression and all other lines are let bindings, it all works out.

1 Like