2012-01-10 29 views
6

Đầu tiên hãy để tôi xin lỗi về quy mô của vấn đề này nhưng tôi thực sự đang cố gắng suy nghĩ về chức năng và đây là một trong những vấn đề khó khăn hơn mà tôi phải làm việc.Cách viết một tập tin chức năng "scanner"

Tôi muốn nhận một số đề xuất về cách tôi có thể xử lý sự cố mà tôi có một cách hoạt động, đặc biệt trong F #. Tôi đang viết một chương trình để đi qua danh sách các thư mục và sử dụng danh sách các mẫu regex để lọc danh sách các tệp được truy xuất từ ​​các thư mục và sử dụng danh sách thứ hai các mẫu regex để tìm các kết quả phù hợp trong văn bản của các tệp đã truy xuất. Tôi muốn điều này trả về tên tệp, chỉ mục dòng, chỉ mục cột, mẫu và giá trị phù hợp cho từng phần văn bản khớp với mẫu regex đã cho. Ngoài ra, trường hợp ngoại lệ cần phải được ghi lại và có 3 trường hợp ngoại lệ có thể xảy ra: không thể mở thư mục, không thể mở tệp, đọc nội dung từ tệp không thành công. Yêu cầu cuối cùng của điều này là khối lượng của các tập tin "quét" cho các trận đấu có thể là rất lớn vì vậy toàn bộ điều này cần phải được lười biếng. Tôi không quá lo lắng về một giải pháp chức năng "thuần khiết" nhiều như tôi quan tâm đến một giải pháp "tốt" mà đọc tốt và hoạt động tốt. Một thách thức cuối cùng là làm cho nó tương tác với C# bởi vì tôi muốn sử dụng các công cụ winform để đính kèm thuật toán này vào một ui. Đây là nỗ lực đầu tiên của tôi và hy vọng điều này sẽ làm rõ vấn đề:

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)))))))))) 

Cảm ơn mọi đầu vào.

Cập nhật - ở đây là bất kỳ giải pháp cập nhật dựa trên thông tin phản hồi tôi nhận được:

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) } 

Một lần nữa, bất kỳ đầu vào được chào đón.

+2

Bạn đã xem các trình phân tích cú pháp chức năng như Parsec chưa? –

+1

Đây là rất nhiều văn bản. Hãy thử chia nhỏ nó để dễ đọc hơn. – Marcin

+0

Tôi chỉ đơn giản sẽ sử dụng một giao diện và biểu thức đối tượng để tạo một thể hiện và đưa nó vào mã C#. –

Trả lời

8

Tôi nghĩ rằng cách tiếp cận tốt nhất là bắt đầu với giải pháp đơn giản nhất và sau đó mở rộng nó. Cách tiếp cận hiện tại của bạn có vẻ là khá khó đọc đối với tôi vì hai lý do:

  • Mã này sử dụng rất nhiều combinators và bố cục chức năng trong mô hình mà không phải là quá phổ biến trong F #. Một số xử lý có thể được viết dễ dàng hơn bằng cách sử dụng các biểu thức trình tự.

  • Mã được viết dưới dạng một hàm duy nhất, nhưng nó khá phức tạp và dễ đọc hơn nếu được tách thành nhiều hàm.

tôi có lẽ sẽ bắt đầu bằng việc tách mã trong một hàm kiểm tra một tập tin duy nhất (nói fileMatches) và một chức năng mà đi qua các tập tin và gọi fileMatches. Lặp lại chính có thể được viết khá độc đáo bằng cách sử dụng các biểu thức chuỗi F #:

// Checks whether a file name matches a filename pattern 
// and a content matches a content pattern 
let fileMatches fileNamePatterns contentPatterns 
       (fileExHandler, lineExHandler) file = 
    // TODO: This can be imlemented using 
    // File.ReadLines which returns a sequence 


// Iterates over all the files and calls 'fileMatches' 
let scanner specifiedDirectories fileNamePatterns contentPatterns 
      (dirExHandler, fileExHandler, lineExHandler) = seq { 
    // Iterate over all the specified directories 
    for specifiedDir in specifiedDirectories do 
    // Find all files in the directories (and handle exceptions)  
    let files = 
     try Directory.GetFiles(specifiedDir, "*", SearchOption.AllDirectories) 
     with e -> dirExHandler e specifiedDir; [||] 
    // Iterate over all files and report those that match 
    for file in files do 
     if fileMatches fileNamePatterns contentPatterns 
        (fileExHandler, lineExHandler) file then 
     // Matches! Return this file as part of the result. 
     yield file } 

Chức năng này vẫn còn khá phức tạp, vì bạn cần truyền nhiều thông số xung quanh. Gói các thông số trong một kiểu đơn giản hoặc một kỷ lục có thể là một ý tưởng tốt:

type ScannerArguments = 
    { FileNamePatterns:string 
    ContentPatterns:string 
    FileExceptionHandler:exn -> string -> unit 
    LineExceptionHandler:exn -> string -> unit 
    DirectoryExceptionHandler:exn -> string -> unit } 

Sau đó, bạn có thể xác định cả fileMatchesscanner như chức năng mà chỉ mất hai tham số, mà sẽ làm cho mã của bạn rất nhiều dễ đọc hơn. Một cái gì đó như:

// Iterates over all the files and calls 'fileMatches' 
let scanner specifiedDirectories (args:ScannerArguments) = seq { 
    for specifiedDir in specifiedDirectories do 
    let files = 
     try Directory.GetFiles(specifiedDir, "*", SearchOption.AllDirectories) 
     with e -> args.DirectoryEceptionHandler e specifiedDir; [||] 
    for file in files do 
     // No need to propagate all arguments explicitly to other functions 
     if fileMatches args file then yield file } 
Các vấn đề liên quan