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:
runIO
twice, but it feels like it only should be called
once, in the end?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!
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.
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:
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.
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))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With