Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monadic IO with ramda and ramda-fantasy

Trying to figure out how the IO monad works.

Using the code below I read filenames.txt and use the results to rename files in the directory testfiles. This is obviously unfinished, so instead of actually renaming anything I log to console. :)

My questions are:

  1. I call runIO twice, but it feels like it only should be called once, in the end?
  2. I'm want to use renameIO instead of renaneDirect but can't find the proper syntax.

Any other suggestions are also appreciated, I'm new to 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!
like image 525
rickythefox Avatar asked Oct 05 '16 23:10

rickythefox


People also ask

Is there a ramda-fantasy for JavaScript?

Fantasy Land compatible types for easy integration with Ramda. Ramda-Fantasy is no longer developed. There are a number of excellent libraries providing algebraic datatypes in JavaScript.

How long will Ramda-fantasy be available on npm?

The existing npm releases of Ramda-Fantasy will remain available indefinitely. We recommend a number of alternative libraries such as Sanctuary, Folktale, Fluture, and Fantasy-Land. Specifically, we suggest these replacements:

What happened to Ramda-fantasy?

Ramda-Fantasy is no longer developed. There are a number of excellent libraries providing algebraic datatypes in JavaScript. The existing npm releases of Ramda-Fantasy will remain available indefinitely. We recommend a number of alternative libraries such as Sanctuary, Folktale, Fluture, and Fantasy-Land.


1 Answers

You're not too far off a solution.

Too avoid the second call to runIO you can make use of the fact that the IO type in Ramda Fantasy implements the Apply interface from the fantasyland spec. This allows you to lift a function (like your renameDirect) to accept arguments of the IO type and apply the function to the values contained within the IO instances.

We can make use of R.ap here, which has a signature (specialised here to IO) of IO (a -> b) -> IO a -> IO -> b. This signature suggests that if we have an IO instance that contains a function that takes some type a and returns some type b, along with another IO instance that contains some type a, we can produce an IO instance containing some type of b.

Before we get into that, we can make a slight change to your use of R.zip then R.apply(renameDirect) by combining the two using R.zipWith(renameDirect).

Now your example can now look like:

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()

In this example we've created an instance of IO (a -> b) here by calling R.map(R.zipWith(renameDirect), files) which will partially apply R.zipWith(renameDirect) with the value stored in files. This is then given to R.ap along with the names value, which will produce a new IO instance containing the effective result of something equivalent to IO(() => R.zipWith(renameDirect, value.runIO(), names.runIO())

Now because having to call R.map to partially apply against the first argument to R.ap tends to be a bit clunky, there is another helper function R.lift that can be useful for this purpose, which takes care of the job of lifting a given function to produce a new function that now accepts Apply instances.

So in the above example:

const renameDirectIO = (files, names) =>
  R.ap(R.map(R.zipWith(renameDirect), files), names)

Can be simplified to:

const renameDirectIO = R.lift(R.zipWith(renameDirect))
like image 83
Scott Christopher Avatar answered Oct 15 '22 12:10

Scott Christopher