Как написать функциональный файл "сканер"

Сначала позвольте мне извиниться за масштаб этой проблемы, но я действительно пытаюсь мыслить функционально, и это одна из самых сложных проблем, с которыми мне пришлось работать.

Я хотел получить несколько предложений о том, как я могу решить проблему, которая у меня есть, функциональным способом, особенно на F#. Я пишу программу для просмотра списка каталогов и использования списка regex-шаблонов для фильтрации списка файлов, извлеченных из каталогов, и использования второго списка regex-шаблонов для поиска совпадений в тексте извлеченных файлов. Я хочу, чтобы эта штука возвращала имя файла, индекс строки, индекс столбца, шаблон и совпадающее значение для каждого фрагмента текста, который соответствует заданному шаблону regex. Также необходимо регистрировать исключения, и есть 3 возможных сценария исключений: не удается открыть каталог, не удается открыть файл, чтение содержимого из файла не удалось. Последнее требование - объем файлов, которые "сканируются" на предмет совпадений, может быть очень большим, поэтому все это дело должно быть ленивым. Я не слишком беспокоюсь о "чистом" функциональном решении, так как меня интересует "хорошее" решение, которое хорошо читается и хорошо работает. Последняя задача - сделать его совместимым с C#, потому что я хотел бы использовать инструменты winform, чтобы прикрепить этот алгоритм к ui. Вот моя первая попытка, которая, надеюсь, прояснит проблему:

open System.Text.RegularExpressions
open System.IO

type Reader<'t, 'a> = 't -> 'a //=M['a], result varies

let returnM x _ = x 

let map f m = fun t -> t |> m |> f

let apply f m = fun t -> t |> m |> (t |> f)

let bind f m = fun t -> t |> (t |> m |> f)

let Scanner dirs =
    returnM dirs
    |> apply (fun dirExHandler ->
        Seq.collect (fun directory ->
            try
                Directory.GetFiles(directory, "*", SearchOption.AllDirectories)
            with | e ->
                dirExHandler e directory
                Array.empty))
    |> map (fun filenames ->
        returnM filenames
        |> apply (fun (filenamepatterns, lineExHandler, fileExHandler) ->
            Seq.filter (fun filename ->
                 filenamepatterns |> Seq.exists (fun pattern ->
                    let regex = new Regex(pattern)
                    regex.IsMatch(filename)))
            >> Seq.map (fun filename ->
                    let fileinfo = new FileInfo(filename)
                    try
                        use reader = fileinfo.OpenText()
                        Seq.unfold (fun ((reader : StreamReader), index) ->
                            if not reader.EndOfStream then
                                try
                                    let line = reader.ReadLine()
                                    Some((line, index), (reader, index + 1))
                                with | e -> 
                                    lineExHandler e filename index
                                    None
                            else
                                None) (reader, 0)        
                        |> (fun lines -> (filename, lines))
                    with | e -> 
                        fileExHandler e filename
                        (filename, Seq.empty))
            >> (fun files -> 
                returnM files
                |> apply (fun contentpatterns ->
                    Seq.collect (fun file ->
                        let filename, lines = file
                        lines |>
                            Seq.collect (fun line ->
                                let content, index = line
                                contentpatterns
                                |> Seq.collect (fun pattern ->    
                                    let regex = new Regex(pattern)
                                    regex.Matches(content)
                                    |> (Seq.cast<Match>
                                    >> Seq.map (fun contentmatch -> 
                                        (filename, 
                                            index, 
                                            contentmatch.Index, 
                                            pattern, 
                                            contentmatch.Value))))))))))

Спасибо за любой вклад.

Обновлено - вот любое обновленное решение, основанное на полученной обратной связи:

open System.Text.RegularExpressions
open System.IO

type ScannerConfiguration = {
    FileNamePatterns : seq<string>
    ContentPatterns : seq<string>
    FileExceptionHandler : exn -> string -> unit
    LineExceptionHandler : exn -> string -> int -> unit
    DirectoryExceptionHandler : exn -> string -> unit }

let scanner specifiedDirectories (configuration : ScannerConfiguration) = seq {
    let ToCachedRegexList = Seq.map (fun pattern -> new Regex(pattern)) >> Seq.cache

    let contentRegexes = configuration.ContentPatterns |> ToCachedRegexList

    let filenameRegexes = configuration.FileNamePatterns |> ToCachedRegexList

    let getLines exHandler reader = 
        Seq.unfold (fun ((reader : StreamReader), index) ->
            if not reader.EndOfStream then
                try
                    let line = reader.ReadLine()
                    Some((line, index), (reader, index + 1))
                with | e -> exHandler e index; None
            else
                None) (reader, 0)   

    for specifiedDirectory in specifiedDirectories do
        let files =
            try Directory.GetFiles(specifiedDirectory, "*", SearchOption.AllDirectories)
            with e -> configuration.DirectoryExceptionHandler e specifiedDirectory; [||]
        for file in files do
            if filenameRegexes |> Seq.exists (fun (regex : Regex) -> regex.IsMatch(file)) then
                let lines = 
                    let fileinfo = new FileInfo(file)
                    try
                        use reader = fileinfo.OpenText()
                        reader |> getLines (fun e index -> configuration.LineExceptionHandler e file index)
                    with | e -> configuration.FileExceptionHandler e file; Seq.empty
                for line in lines do
                    let content, index = line
                    for contentregex in contentRegexes do
                        for mmatch in content |> contentregex.Matches do
                            yield (file, index, mmatch.Index, contentregex.ToString(), mmatch.Value) }

Опять же, любой вклад приветствуется.

6
задан Brad 10 January 2012 в 18:30
поделиться