Монадический IO с рамдой и рамда-фэнтези

Попытка выяснить, как работает монада IO.

Используя код ниже, я прочитал 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!

1 ответ

Решение

Вы не слишком далеко от решения.

Слишком избежать второго звонка runIO Вы можете использовать тот факт, что IO типа в Рамда Фэнтези реализует Apply Интерфейс от Fantasyland Spec. Это позволяет вам поднять функцию (например, ваш renameDirect) принять аргументы IO введите и примените функцию к значениям, содержащимся в IO экземпляров.

Мы можем использовать R.ap здесь, который имеет подпись (специализируется здесь IO) из IO (a -> b) -> IO a -> IO -> b, Эта подпись предполагает, что если у нас есть IO экземпляр, который содержит функцию, которая принимает некоторый тип a и возвращает какой-то тип b вместе с другим IO экземпляр, который содержит некоторый тип a мы можем произвести IO экземпляр, содержащий некоторый тип b,

Прежде чем мы углубимся в это, мы можем внести небольшие изменения в ваше использование 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()

В этом примере мы создали экземпляр IO (a -> b) здесь по телефону R.map(R.zipWith(renameDirect), files) который будет частично применяться R.zipWith(renameDirect) со значением, хранящимся в files, Затем это дается R.ap вместе с names значение, которое будет производить новый 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))
Другие вопросы по тегам