Read the Book of Doobie and the Scala with Cats

also Effect Cheatsheet

why is there nothing here

or

Why I (almost) banned Cats and all its libraries and returned to Scala.

Normally I get my head round a tech and make notes.

It turns out I simply disagree with making Scala like Haskell. ‘If then else’, or ‘match case’, or even Option.fold beats Cats Alternative because everyone understands it. Cats uses too many Category Theory terms and not enough Software terms. The classes are named after Category Theory terminology, rather than as a well named library for achieving an aim in software. This is the tail wagging the dog.

The competition in Software languages is too fierce to get derailed by understanding the worth of an Applicative, Semigroupal or other. It is a spell book written in an obscure language.

How to do an if then else in Doobie

My best example on why Cats is obscure rather than readable is below.

I want to call a find function returning an Option[Person], and if its there I want to perform an action, and if its not there I want to do nothing.

// Written long hand, not as terse as I can make it....
def conditionalCall(personId:Option[Long]) : ConnectionIO[Option[AddressDto]] = {
  val ret : ConnectionIO[Option[AddressDto]] = personId match {
    case None => Async[ConnectionIO].liftIO(IO{Option[AddressDto](null)})
    case Some(pid) => AddressDao.updateResident(pid)
  }
  ret
}

or the full code, without transactor, ConnectionIO means you can compose your own transactions which is really useful.

import cats.effect.{Async, IO}
import doobie.ConnectionIO

object ConditionalDoobie extends App {
  println(conditionalCall(None))  // Not a useful println as no transactor
  println(conditionalCall(Some(1)))  // Not a useful println as no transactor

  // The code, the Dao would normally do some doobie sql.
  case class AddressDto(rd:String)
  object AddressDao {
    def updateResident(id:Long) = {
      Async[ConnectionIO].liftIO(IO{Option[AddressDto](AddressDto("a road"))})
    }
  }

  def conditionalCall(personId:Option[Long]) : ConnectionIO[Option[AddressDto]] =
    personId
      .fold{
        println("The None branch")
        Async[ConnectionIO].liftIO(IO{Option[AddressDto](null)})
      }{pid=> {
        println("The Some branch")
        AddressDao.updateResident(pid)
      }}
}

ie this incantation to create a None inside a ConnectionIO

Async[ConnectionIO].liftIO(IO{Option[AddressDto](null)})

I only found that after loads of googling, not by working it out.

Back to the discussion

I understand moving effects into IO and scheduling them off Stack, I understand how Futures are eagerly run, and so on. The class names and the Category Theory convention is really turning me off this stuff, it does not make the code easy to read and maintain.

I should add that in my new world of services and serverless functions I had ditched Akka for http4s and doobie. I’m in a real dilemma about suggesting these again, even though I have now worked through all of the pain.

Scala syntax remains fabulous. Just cats syntax is too obscure for the stated benefit.

Scala is FP, Cats is just a library written by Haskell and/or maths folk who want to work in that domain.