带有 ramda 和 ramda-fantasy 的 Monadic IO
Monadic IO with ramda and ramda-fantasy
试图弄清楚 IO monad 是如何工作的。
我使用下面的代码读取 filenames.txt
并使用结果重命名目录 testfiles
中的文件。这显然是未完成的,所以我没有实际重命名我登录到控制台的任何内容。 :)
我的问题是:
- 我打了两次
runIO
,但感觉应该只打
曾经,到底是?
- 我想使用
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.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)
创建了 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))
试图弄清楚 IO monad 是如何工作的。
我使用下面的代码读取 filenames.txt
并使用结果重命名目录 testfiles
中的文件。这显然是未完成的,所以我没有实际重命名我登录到控制台的任何内容。 :)
我的问题是:
- 我打了两次
runIO
,但感觉应该只打 曾经,到底是? - 我想使用
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.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)
创建了 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))