8

Così ho iniziato a guardare Ramda/Folktale. Sto riscontrando un problema nel tentativo di mappare su una serie di attività provenienti da una directory. Sto cercando di analizzare il contenuto del file.Mapping su una serie di attività in Javascript

var fs = require('fs'); 
var util = require('util'); 
var R = require('ramda'); 
var Task = require('data.task'); 

var compose = R.compose; 
var map = R.map; 
var chain = R.chain; 


function parseFile(data) { 
     console.log("Name: " + data.match(/\$name:(.*)/)[1]); 
     console.log("Description: " + data.match(/\$description:(.*)/)[1]); 
     console.log("Example path: " + data.match(/\$example:(.*)/)[1]); 
} 

// String => Task [String] 
function readDirectories(path) { 
    return new Task(function(reject, resolve) { 
     fs.readdir(path, function(err, files) { 
      err ? reject(err) : resolve(files); 
     }) 
    }) 
} 

// String => Task String 
function readFile(file) { 
    return new Task(function(reject, resolve) { 
     fs.readFile('./src/less/' + file, 'utf8', function(err, data) { 
      err ? reject(err) : resolve(data); 
     }) 
    }) 
} 

var app = compose(chain(readFile), readDirectories); 

app('./src/less').fork(
    function(error) { throw error }, 
    function(data) { util.log(data) } 
); 

Sto leggendo i file in una directory e restituendo un'attività. Quando questo si risolve dovrebbe andare nella funzione readFile (che restituisce una nuova attività). Una volta letto il file, voglio solo analizzare alcuni bit da lì.

Con la seguente:

var app = compose(chain(readFile), readDirectories); 

Si entra nella funzione ReadFile ma 'file' è un array di file in modo che gli errori.

Con:

var app = compose(chain(map(readFile)), readDirectories); 

Non abbiamo mai entrare in fs.readfile(), ma 'file' è il nome del file vero e proprio.

Sono abbastanza perplesso su questo e la documentazione è sconcertante. Qualsiasi suggerimento benvenuto.

Grazie

risposta

9
'use strict'; 

const fs = require('fs'); 

const Task = require('data.task'); 
const R = require('ramda'); 


// parseFile :: String -> { name :: String 
//       , description :: String 
//       , example :: String } 
const parseFile = data => ({ 
    name:   R.nth(1, R.match(/[$]name:(.*)/, data)), 
    description: R.nth(1, R.match(/[$]description:(.*)/, data)), 
    example:  R.nth(1, R.match(/[$]example:(.*)/, data)), 
}); 

// readDirectories :: String -> Task (Array String) 
const readDirectories = path => 
    new Task((reject, resolve) => { 
    fs.readdir(path, (err, filenames) => { 
     err == null ? resolve(filenames) : reject(err); 
    }) 
    }); 

// readFile :: String -> Task String 
const readFile = filename => 
    new Task(function(reject, resolve) { 
    fs.readFile('./src/less/' + filename, 'utf8', (err, data) => { 
     err == null ? resolve(data) : reject(err); 
    }) 
    }); 

// dirs :: Task (Array String) 
const dirs = readDirectories('./src/less'); 

// files :: Task (Array (Task String)) 
const files = R.map(R.map(readFile), dirs); 

// sequenced :: Task (Task (Array String)) 
const sequenced = R.map(R.sequence(Task.of), files); 

// unnested :: Task (Array String) 
const unnested = R.unnest(sequenced); 

// parsed :: Task (Array { name :: String 
//       , description :: String 
//       , example :: String }) 
const parsed = R.map(R.map(parseFile), unnested); 

parsed.fork(err => { 
       process.stderr.write(err.message); 
       process.exit(1); 
      }, 
      data => { 
       process.stdout.write(R.toString(data)); 
       process.exit(0); 
      }); 

ho scritto ciascuna delle trasformazioni su una riga separata in modo da poter includere tipo firme che rendono le mappe nidificate più facile da capire. Questi potrebbero naturalmente essere combinati in una pipeline tramite R.pipe.

I passi più interessanti stanno usando R.sequence per trasformare Array (Task String) in Task (Array String), e l'utilizzo di R.unnest per trasformare Task (Task (Array String)) in Task (Array String).

Suggerisco di dare un'occhiata a plaid/async-problem se non l'hai già fatto.

+1

Grazie. Questo è davvero molto interessante. È un tale cambiamento nel pensare di averlo fatto. Torna al libro di Dr Boolean. – SpaceBeers

+0

Non riesco a trovare permuti nella documentazione di ramda. Cosa mi manca? – akaphenom

+0

anche (dato che non riesco a trovare la documentazione) in che modo la commutazione è diversa dalla funzione di sequenza "control.monads"? – akaphenom

4

Come suggerito da David, commute è utile per convertire un elenco di alcuni applicativi (come Task) in un singolo applicativo contenente l'elenco di valori.

var app = compose(chain(map(readFile)), readDirectories);

Non abbiamo mai entrare in fs.readfile(), ma 'file' è il nome del file vero e proprio.

La strettamente correlati commuteMap può aiutare anche qui come si prenderà cura del map fase separata, significa che il codice di cui sopra dovrebbe anche essere in grado di essere rappresentato come:

var app = compose(chain(commuteMap(readFile, Task.of)), readDirectories); 
+0

È grandioso. Grazie. – SpaceBeers

0

Ho avuto un problema simile di leggere tutti i file in una directory e ha iniziato con pipeP di Ramda:

'use strict'; 

const fs = require('fs'); 
const promisify = require("es6-promisify"); 
const _ = require('ramda'); 

const path = './src/less/'; 
const log = function(x){ console.dir(x); return x }; 
const map = _.map; 
const take = _.take; 

const readdir = promisify(fs.readdir); 
const readFile = _.curry(fs.readFile); 
const readTextFile = readFile(_.__, 'utf8'); 
const readTextFileP = promisify(readTextFile); 

var app = _.pipeP(
    readdir, 
    take(2), // for debugging, so don’t waste time reading all the files 
    map(_.concat(path)), 
    map(readTextFileP), 
    promiseAll, 
    map(take(50)), 
    log 
); 

function promiseAll(arr) { 
    return Promise.all(arr) 
} 

app(path); 

Promise.all sembra essere necessaria quando si leggono i file come pipeP si aspetta un valore o ap romise, ma sta ricevendo una serie di promesse per leggere i file. Quello che mi imbarazza è il motivo per cui ho dovuto fare una funzione per restituire lo Promise.all invece di inserirlo.

L'utilizzo di task/fork è intrigante perché la gestione degli errori è incorporata. Vorrebbe che pipeP avesse un blocco catch, perché senza di esso è necessario iniettare forse, il che è difficile per un principiante come me farlo correttamente.