partial.lenses: 如何在 L.collect 的光学中使用异步操作?

partial.lenses: how to use asynchronous operations in optics with L.collect?

我从 partial.lenses 图书馆了解到 partial.lenses isn't necessarily the best solution to the following problem and that's besides the point in this question. I was trying to list files from a directory using L.collect。目标只是获得一个扁平化的文件名数组。

问题:我不想使用 fs.readdirSync,而是想在我的光学器件中使用异步的、Promise 返回版本的 Node 的 fs API。

以下是 readdir 的承诺版本:

const util = require('util')
const fs = require('fs')
const readdirAsync = util.promisify(fs.readdir)

下面是实际的实现。我想知道如何用异步版本替换 readdir 函数中的同步 fs.readdirSync

const L = require("partial.lenses")
const fs = require("fs")
const path = require("path")
const _ = require("lodash")

const basePath = path.basename(`${__dirname}/..`)

const isDirectory = dirent => {
  return dirent instanceof fs.Dirent ? dirent.isDirectory() : false
}

const readDir = path => () => {
  return fs.readdirSync(path, { withFileTypes: true })
}

const leafs = nodePath => {
  return L.cond(
    [
      _.isArray,
      L.lazy(() => [
        L.elems,
        L.choose(({ name }) => leafs(path.join(nodePath, name)))
      ])
    ],
    [
      isDirectory,
      L.lazy(() => [
        readDir(nodePath),
        L.elems,
        L.choose(({ name }) => leafs(path.join(nodePath, name)))
      ])
    ],
    [L.identity]
  )
}

const listFiles = async () =>
  L.collect(
    leafs(basePath),
    fs.readdirSync(basePath, { withFileTypes: true })
  )

这是一个有趣的问题,因为局部透镜可以用于 像这样的异步问题,但是库目前只提供了一个 开箱即用的对异步操作的一点点直接支持。在里面 未来图书馆肯定可以扩展以提供更多支持 执行异步操作。

部分镜头构建中的遍历 applicative 操作。经过 使用不同的应用程序进行不同类型的操作,例如收集 元素,计算最少的元素,或计算新版本的 数据结构,可以执行。

许多操作,例如收集元素或计算最小值,都有一个 底层 monoid。任何幺半群,如数组 串联

const ConcatArray = {empty: () => [], concat: (l, r) => l.concat(r)}

可以转换为应用程序。在部分镜头中 L.concatL.concatAs 操作在内部执行此操作。

所以,为了异步收集元素,我们可以使用异步版本 串联的幺半群。我们可以通过创建一个函数来实现 将任何幺半群转换为异步幺半群:

const asyncMonoid = m => ({
  empty: async () => m.empty(),
  concat: async (l, r) => m.concat(await l, await r)
})

我们现在可以定义异步版本 L.collect如下:

const collectAsync = L.concatAs(
  async x => [x],
  asyncMonoid(ConcatArray)
)

我们还需要解决这个问题的一件事是获得 异步操作,以便我们可以使用光学放大它。为此我们 可以定义一个新的 原始光学 等待的函数 在光学组合中向前传递焦点之前的焦点:

const awaitIt = async (x, i, F, xi2yF) => xi2yF(await x, i)

使用上面的 awaitIt optical,我们现在可以定义异步遍历 给定合适的异步 readDirectory 的文件系统中的文件 功能:

const filesUnderEntries = L.lazy(() => [
  awaitIt,
  L.elems,
  L.ifElse(L.get('isDir'), ['path', filesUnderDirectory], 'path')
])

const filesUnderDirectory = [L.reread(readDirectory), filesUnderEntries]

我们可以使用上面的遍历来检查目录结构和select 它下面的文件。我们还可以进一步组合操作来读取这些文件 并检查这些文件中的数据。例如

collectAsync(
  [
    filesUnderDirectory,
    L.when(R.test(/\.my$/)),
    L.reread(readFile),
    awaitIt
  ]
)

定义一个遍历目录树并生成的异步操作 目录树下扩展名为 .my 的文件的内容。

这是一个 playground,其中包含使用伪造文件系统的完整代码和示例 操作.