Monadic IO与ramda和ramda幻想
试图找出IO monad是如何工作的。
使用下面的代码,我读取filenames.txt
并使用结果来重命名目录testfiles
中的文件。 这显然是未完成的,所以而不是实际重命名我login到控制台的任何东西。 🙂
我的问题是:
- 我打电话
runIO
两次,但它感觉只是应该只调用一次,到底? - 我想使用
renameIO
而不是renaneDirect
但无法find正确的语法。
任何其他build议也表示赞赏,我是新来的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!
你不是一个解决scheme太远。
为了避免第二次调用runIO
,可以利用runIO
Fantasy中的IO
types实现了fantasyland规范中的Apply
接口。 这允许你renameDirect
一个函数(比如你的renameDirect
)来接受IO
types的参数,并将函数应用到IO
实例中包含的值。
我们可以在这里使用R.ap
,它有一个IO (a -> b) -> IO a -> IO -> b
的签名(这里专用于IO
)。 这个签名表明,如果我们有一个IO
实例,它包含一个接受某个typesa
并返回某个typesb
的函数,以及另一个包含某个typesa
的IO
实例,则可以生成一个包含某种typesb
的IO
实例。
在我们进入之前,我们可以稍微改变一下你使用R.zip
然后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()
在这个例子中,我们通过调用R.map(R.zipWith(renameDirect), files)
在这里创build了一个IO (a -> b)
的实例,它将部分应用R.zipWith(renameDirect)
和存储在files
的值。 然后将R.ap
与names
值一起提供给R.ap
,这将生成一个新的IO
实例,其中包含与IO(() => R.zipWith(renameDirect, value.runIO(), names.runIO())
现在,因为不得不调用R.map
来部分应用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))