5

试图弄清楚 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!
4

1 回答 1

11

你离解决方案不远了。

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

我们可以使用R.aphere,它有一个签名(这里专门用于IOIO (a -> b) -> IO a -> IO -> b。这个签名表明,如果我们有一个IO实例包含一个函数,该函数接受某种类型a并返回某种类型b,以及另一个IO包含某种类型的实例a,我们可以生成一个IO包含某种类型的实例b

在我们开始之前,我们可以通过将两者结合使用来对您的R.zipthen的使用进行轻微的更改using 。R.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()

在此示例中,我们IO (a -> b)通过调用创建了一个 here实例,该实例R.map(R.zipWith(renameDirect), files)将部分应用R.zipWith(renameDirect)存储在files. 然后将其R.apnames值一起提供,这将产生一个新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))
于 2016-10-06T00:28:22.600 回答