>假设您需要连接到数据库。
因此,您给出一个DbConnection
作为某个假设函数的最后一个参数,类型如下:doDbStuff :: Int -> DbConnection -> Int
也许还有其他函数也依赖于DbConnection
,并且它们都在执行写入操作。因此,它们可以单独运行,也可以作为原子操作(即事务)的一部分运行。
由于人们可能希望使用池来管理DbConnection
,并且函数可能是也可能不是原子操作的一部分,因此这些函数不会实现代码来从池中获取和释放DbConnection
实例。
现在,这些功能是长函数组合的一部分,某些决策可能涉及不需要DbConnection
。也就是说,有可能从池中获取DbConnection
,并且它可能被另一个请求使用,这可能会产生瓶颈。
还有另一种选择,其中不会注入DbConnection
而是像withConnection :: (DbConnection -> a) -> a
这样的高阶函数,因此每个函数都可以接受DbConnection
,使用它,整个withConnection
负责获取和释放连接。这里的缺点是,很难使许多函数作为原子操作的一部分进行协作。
所以。。。
目前,我一直在使用#2方法。顺便说一句,有没有其他选择可以保留两种方法的优点?
JavaScript 中的伪代码:
方法#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)
方法#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))
有一个工具可以解决这种情况,您的解决方案非常接近它!读取器 adt 将允许您在访问某些环境的上下文中编写函数。这是我最喜欢的实现:https://github.com/monet/monet.js/blob/master/docs/READER.md
不幸的是,这种模式可能需要将大量代码包装在读取器类型中 - 但您已经引入了withConnection包装器,这会产生几乎相同数量的额外代码。
下面是一个示例,它从数据库中读取 id 为"123"的文档,覆盖某些属性,并将结果写回数据库。 提供数据库连接将推迟到实际运行程序之前,但您可以编写代码,假设运行代码时数据库连接将存在。
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) })