Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Approach to thread resources like connections in functional programming

Let's say you need to connect to a database.

So, you give a DbConnection as the last argument of some hypothetical function with a type like this: doDbStuff :: Int -> DbConnection -> Int

Perhaps there're other functions which also depend on a DbConnection, and all of them are performing write operations. Thus, these might be ran separately or as part of an atomic operation (i.e. a transaction).

Since one may want to manage DbConnection using a pool, and functions may or may not be part of an atomic operation, these doesn't implement code to acquire and release DbConnection instances from and to the pool.

Now, these functions are part of a long function composition on which certain decisions may involve not requiring the DbConnection. That is, there's a chance that a DbConnection may be taken from the pool and it could be used by another request, which might produce bottlenecks.

There's an alternative, where one won't inject DbConnection but a high-order function like withConnection :: (DbConnection -> a) -> a, so each function can take a DbConnection, use it and the whole withConnection takes care of acquiring and releasing connections. The drawback here is that it's harder to make many functions to collaborate as part of an atomic operation.

So...

For now, I've been using #2 approach. BTW, is there any alternative on which one could retain the best of both approaches?

Pseudo-code in JavaScript:

Approach #1

const connectionString = '[whatever]'
const env = { connection: acquire (connectionString) }

const output = composition (arg0) (argN) (env)
// then, release the connection

// f :: a -> b -> { connection: DbConnection }
const f = x => y => ({ connection }) => 
    doDbStuff (x + y) (connection)

Approach #2

const withConnection = f => [stuff to acquire the connection, and aftewards, release it]
const env = { withConnection }

const output = composition (arg0) (argN) (env)

// type FnConnection DbConnection c = c -> a
// f :: a -> a -> { connection: FnConnection }
const f = x => y => ({ withConnection }) => 
    withConnection (doDbStuff (x + y))
like image 514
Matías Fidemraizer Avatar asked Nov 06 '22 22:11

Matías Fidemraizer


1 Answers

There is a tool for this situation and your solution is pretty close to it! The reader adt will allow you to write your functions within the context of having access to some environment. Here is my favorite implementation: https://github.com/monet/monet.js/blob/master/docs/READER.md

Unfortunately this pattern may require large amounts of your code to be wrapped in the reader type - but you are already introducing the withConnection wrapper which results in nearly the same amount of extra code.

Here is an example that reads a document with id of'123' from the db, overrides some properties, and writes the result back into the db. providing the db connection is deffered until actually running your program, but you can write your code assuming the db connection will be present when the code is run.

const { Reader } = require('monet');

const findById = (id) => Reader(({ db }) => db.find({ id }));
const insertDoc = (doc) => Reader(({ db }) => db.insert(doc));
const copyWithDefaults = (doc) => ({
    ...doc,
    name: 'default name',
});

const app =
    findById('123')
        .map(copyWithDefaults)
        .chain(insertDoc)

app.run({ db: aquire(connectionString) })
like image 77
Alfred Young Avatar answered Nov 11 '22 14:11

Alfred Young