Monadic IO with ramda and ramda-fantasy

时间:2016-10-05 23:10:17

标签: node.js functional-programming ramda.js ramda-fantasy

试图找出IO monad的工作原理。

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

我的问题是:

  1. 我打电话给runIO两次,但感觉它只应该被调用 一次,到底?
  2. 我想使用renameIO代替renaneDirect,但不能 找到合适的语法。
  3. 还有任何其他建议,我是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!
    

1 个答案:

答案 0 :(得分:11)

你离解决方案不太远。

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

我们可以在这里使用R.apIO具有IO (a -> b) -> IO a -> IO -> b的签名(此处专门针对IO)。此签名表明,如果我们有一个a实例包含一个函数,该函数采用某种类型b并返回某种类型IO,以及包含某种类型的另一个a实例IO,我们可以生成包含某种类型b的{​​{1}}实例。

在我们开始讨论之前,我们可以使用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()

在此示例中,我们通过调用IO (a -> b)在此处创建了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))