Монадический IO с рамдой и рамда-фэнтези
Попытка выяснить, как работает монада IO.
Используя код ниже, я прочитал filenames.txt
и использовать результаты для переименования файлов в каталоге testfiles
, Это явно незакончено, поэтому вместо того, чтобы переименовывать что-либо, я вхожу в консоль.:)
Мои вопросы:
- Я звоню
runIO
дважды, но такое ощущение, что его нужно назвать только один раз, в конце концов? - Я хочу использовать
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))