

Lessons from porting integration tests from Ruby to Haskell - funkylexoo
https://blog.pusher.com/porting-the-pusher-integration-tests-to-haskell/

======
creichert
___While cringing at the cliche: monads were the key ingredient._ __

Let 's not turn this into a cliche! Monads are powerful. I find do notation
much easier to read in many cases:

    
    
         do subscribe
            assertSubSucceeded
            startRoundtripTimer
            assertRecievedWebHook "channel_occupied"
            stopRoundtripTimer
            unsubscribe
            assertRecievedWebHook "channel_vacated"
    
    

___If you haven’t come across monads before, don’t worry, you can think of the
monad we are using as “code that does IO”._ __

On top of this, once you have a basic intuition for how monads work, you start
gaining the ability to describe what your program does at the type level. For
example, This block of code requires a read-only environment (Reader), has
logging output (Writer), and accesses websockets (WebSocketT).

 __ _Also, the fact that Haskell is a less mainstream language led to a few
obvious disadvantages: less documentation, not quite as many libraries, poorer
tooling (although having on the fly type errors show up with mod-ghc is
already a massive win over Ruby)._ __

Yes, you essentially need to be prepared to do any or all of the following if
you want to use Haskell commercially:

1) Patch libraries and tooling.

2) Engage communities and maintainers (bug reports, mailing list, etc).

3) Implement functionality (from scratch) which "should be there".

If you can't, or won't, do these things, Haskell will be much more painful to
use in the real world.

~~~
fractalsea
Author here. Thanks for the feedback!

I agree that the do notation does look nice. The on thing that I like about
the >> operator is that it's clear that you are performing a combining two
functions. But to be honest I don't feel strongly either way.

I definitely agree with your point on monads. I tried to keep this post more
accessible, but I hope to go into a more technical post in the future on how
we used monad transformers when writing these tests. We ended up using
WriterT, ReaderT, LoggingT, EitherT for the "test components". Unfortunately
the websocket library we used did not play well with this (it is callback
based).

And I also agree with your final points. Having said that, I had very low
expectations in this area going in, and it actually turned out to be not
nearly as bad as I expected.

~~~
tel
At the risk of being a little pedantic, >> isn't combining two functions, e.g.

    
    
        Just () >> Nothing     ==     Nothing
        Nothing >> Just ()     ==     Nothing
    

It's perhaps viable to think of (m >> f) as modifying/continuing the m
effect/computation using a continuation which is constantly the f effect. But
that's about as close to "combining two functions" I can get it without
specifically picking a monad that is a function.

On the other hand, >=> is _absolutely_ combining two functions. It's exactly
(flip (.)) in a Kleisli category over some monad.

~~~
fractalsea
By "combining" I just meant an operator that takes multiple functions and
returns a new function based on those arguments. I guess these "functions" are
a special case because they are constant monadic functions. Do you mean to say
composition rather than combination when you are about >=>?

~~~
tel
Yeah, I'm happy to abide by this all when the caveats are specialized, but >=>
is definitely and directly what's being appealed to.

I meant combination just so as to generalize the idea of "composition". If
you're willing to call Kleisli composition "composition" generally then I
won't complain :)

------
bos
I know nothing about the Pusher protocol, but the standard approach to
boilerplate encoding/decoding problems these days is to use GHC.Generics.

It doesn't always fit (typically when your data structures don't resemble the
wire encoding), but it kills all the boilerplate when it does.

Since you say that you can magically get away without boilerplate in Ruby in
this case, I would expect that the Generics approach will give exactly the
same result.

~~~
bos
In fact, it turns out the Pusher protocol is all JSON, so you can autogenerate
the code.

Here's a Pusher message.

    
    
      {
        "event": "pusher:error",
        "data": {
          "message": String,
          "code": Integer
        }
      }
    

Here's the corresponding Haskell.

    
    
      {-# LANGUAGE DeriveGeneric #-}
    
      import GHC.Generics
    
      -- a generic wrapper type for all Pusher events
      data Event a = Event {
          eventType :: Text,
          eventData :: a
        } deriving (Generic)
    
      instance ToJSON a   => ToJSON (Event a)
      instance FromJSON a => FromJSON (Event a)
    
    
      data Error = Error {
          message :: Text,
          code :: Integer
        } deriving (Generic)
    
      instance ToJSON   Error
      instance FromJSON Error

------
mikecmpbll
You could've also rewritten that dog-ugly Ruby code in Ruby, but an
interesting look into Haskell none-the-less.

~~~
fractalsea
I don't deny that the test could be written in much the same way in Ruby too,
the key point of the post was that in Haskell you get flexibility comparable
to a highly dynamic language, while also getting a lot safety guarantees.

As for the previous tests, they probably a product of many small changes
rather than design. There was a reason we were re-writing them.

