Search code examples
scalaimplicits

How to make calling code agnostic of implicit parameter passed under the hood?


I want to be able to surround a block of code with a transaction. The calling code should be as simple as this:

transactional {
  save("something")
}

I thought to make the transactional function like this:

def transactional(block: => Unit): Unit = {
  implicit val conn: Connection = ???

  conn.begin()
  try {
    block
    conn.commit()
  } catch {
    case ex: Exception =>
      conn.rollback()
      throw ex
  } finally {
    conn.close()
  }
}

Now the save method will need to do something with the Connection, but I want to make the calling code agnostic of that (see above). I naively implemented it like this:

def save(operation: String)(implicit conn: Connection): Unit = {
  println(s"saving $operation using $conn")
}

Of course I get a compilation error, that the Connection can not be found. What part am I missing to wire the Connection from the transactional function to the save function?


Solution

  • Change your transactional function something like below (look at the code snippet of transactional). The problem here is connection is available with transactional but it has to reach save function implicitly. So once you get the connection object hand it to the a function which runs inside the transaction and then this code (f) can get access to the connection. Once f gets access to the connection we can make it implicit using the implicit keyword. Now functions like save which take connection implicitly can seamlessly be called inside the transactional.

    Important changes to transactional

    1) Instead of passing the code block (block: => Unit) pass f (f: Connection => Unit)

    2) Inside transactional apply f to connection object and give f access to the connection object.

    def transactional(f: Connection => Unit): Unit = {
      val conn = getConnectionFromDatabase()
      conn.begin()
      try {
        f(conn)
        conn.commit()
      } catch {
        case ex: Exception =>
          conn.rollback()
          throw ex
      } finally {
        conn.close()
      }
    }
    

    Now you can use it like this

    transactional { implicit conn =>
      save("something")
    }
    

    if you save function is like this

    def save(str: String): Connection => Unit = ???
    

    Then you can go without connection

    transactional(save("foo"))