【问题标题】:Monadic IO with ramda and ramda-fantasy带有 ramda 和 ramda-fantasy 的 Monadic IO
【发布时间】:2017-02-14 13:16:28
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    您离解决方案不远了。

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

    我们可以在这里使用R.ap,它的签名(这里专门针对IO)是IO (a -> b) -> IO a -> IO -> b。这个签名表明,如果我们有一个 IO 实例,其中包含一个函数,该函数采用某种类型 a 并返回某种类型 b,以及另一个包含某种类型 aIO 实例,我们可以生成一个IO 实例包含某种类型的 b

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

    【讨论】:

    • 非常感谢,即使我是 FP 的新手。已经使用 ramda 很长时间了。但是使用 Applicative Functors 和提升,仍然令人困惑。这个答案将帮助我更好地掌握这些概念。
    猜你喜欢
    • 2017-08-13
    • 2021-09-24
    • 1970-01-01
    • 2017-12-02
    • 1970-01-01
    • 2023-01-23
    • 1970-01-01
    • 2019-10-04
    • 1970-01-01
    相关资源
    最近更新 更多