Monadic IO with ramda and ramda-fantasy
Asked Answered
P

1

6

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!
Preterit answered 5/10, 2016 at 23:10 Comment(0)
O
11

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))
Olette answered 6/10, 2016 at 0:28 Comment(1)
thanks a lot, even I am new to FP. Have been using ramda for a long time now. But using Applicative Functors, and lifting, still remain confusing. This answer will help me grasp these concepts better.Alfonse

© 2022 - 2024 — McMap. All rights reserved.