带有 ramda 和 ramda-fantasy 的 Monadic IO

Monadic IO with ramda and ramda-fantasy

试图弄清楚 IO monad 是如何工作的。

我使用下面的代码读取 filenames.txt 并使用结果重命名目录 testfiles 中的文件。这显然是未完成的,所以我没有实际重命名我登录到控制台的任何内容。 :)

我的问题是:

  1. 我打了两次runIO,但感觉应该只打 曾经,到底是?
  2. 我想使用 renameIO 而不是 renaneDirect 但不能 找到正确的语法。

也欢迎任何其他建议,我是 FP 新手!

    var R = require('ramda');
    var IO = require('ramda-fantasy').IO
    var fs = require('fs');

    const safeReadDirSync = dir => IO(() => fs.readdirSync(dir));
    const safeReadFileSync = file => IO(() => fs.readFileSync(file, 'utf-8'));

    const renameIO = (file, name) => IO(() => console.log('Renaming file ' + file + ' to ' + name + '\n'));
    const renameDirect = (file, name) => console.log('Renaming file ' + file + ' to ' + name + '\n');

    safeReadFileSync("filenames.txt") // read future file names from text file
            .map(R.split('\n')) // split into array
            .map(R.zip(safeReadDirSync('./testfiles/').runIO())) // zip with current file names from dir
            .map(R.map(R.apply(renameDirect))) // rename
            .runIO(); // go!

你离解决方案不远了。

为了避免第二次调用 runIO,您可以利用 Ramda Fantasy 中的 IO 类型实现 fantasyland 规范中的 Apply 接口这一事实。这允许您提升一个函数(如您的 renameDirect)以接受 IO 类型的参数并将该函数应用于 IO 实例中包含的值。

我们可以在这里使用 R.ap,它的签名(这里特化为 IO)为 IO (a -> b) -> IO a -> IO -> b。此签名表明,如果我们有一个 IO 实例,其中包含一个采用某种类型 a 和 returns 某种类型 b 的函数,以及另一个 IO 实例包含某种类型 a,我们可以生成一个包含某种类型 b.

IO 实例

在开始之前,我们可以对 R.zipR.apply(renameDirect) 的使用稍作更改,方法是使用 R.zipWith(renameDirect).

将两者结合起来

现在您的示例看起来像:

var R = require('ramda')
var IO = require('ramda-fantasy').IO
var fs = require('fs')

const safeReadDirSync = dir => IO(() => fs.readdirSync(dir));
const safeReadFileSync = file => IO(() => fs.readFileSync(file, 'utf-8'))
const renameDirect = (file, name) => console.log('Renaming file ' + file + ' to ' + name + '\n')

const filesIO = R.map(R.split('\n'), safeReadFileSync('filenames.txt'))
const testfilesDirIO = safeReadDirSync('./testfiles/')

const renameDirectIO = (files, names) =>
  R.ap(R.map(R.zipWith(renameDirect), files), names)

renameDirectIO(testfilesDirIO, filesIO).runIO()

在这个例子中,我们通过调用 R.map(R.zipWith(renameDirect), files) 创建了 IO (a -> b) 的实例,它将部分应用 R.zipWith(renameDirect) 和存储在 files 中的值。然后将其与 names 值一起提供给 R.ap,这将生成一个新的 IO 实例,其中包含等效于 IO(() => R.zipWith(renameDirect, value.runIO(), names.runIO())

的有效结果

现在因为必须调用 R.map 来部分应用 R.ap 的第一个参数往往有点笨拙,所以还有另一个辅助函数 R.lift 可以用于这个目的,它负责提升给定函数的工作以生成一个现在接受 Apply 个实例的新函数。

所以在上面的例子中:

const renameDirectIO = (files, names) =>
  R.ap(R.map(R.zipWith(renameDirect), files), names)

可以简化为:

const renameDirectIO = R.lift(R.zipWith(renameDirect))