Câu hỏi Xóa tệp nếu nó tồn tại


Cách tốt nhất để làm điều này trong Haskell là gì?

if exists "foo.txt" then delete "foo.txt"
doSomethingElse

Cho đến nay tôi có:

import System.Directory
main = do
        filename <- getFileNameSomehow
        fileExists <- doesFileExist filename
        if fileExists 
             then removeFile filename
             ???
        doSomethingElse

26
2017-12-14 09:34


gốc


Các doesFileExist chức năng thực sự là một lời mời để điều kiện chủng tộc. Nó không nên tồn tại. - augustss
@augustss: Cách chúng tôi đổi tên thành didFileExistLastTimeIChecked? - C. A. McCann
Tôi đề nghị didFileNotNeverExist. - ehird


Các câu trả lời:


Bạn nên xóa tệp và chỉ cần khôi phục nếu tệp không tồn tại:

import Prelude hiding (catch)
import System.Directory
import Control.Exception
import System.IO.Error hiding (catch)

removeIfExists :: FilePath -> IO ()
removeIfExists fileName = removeFile fileName `catch` handleExists
  where handleExists e
          | isDoesNotExistError e = return ()
          | otherwise = throwIO e

Điều này tránh tình trạng cuộc đua của ai đó xóa tệp giữa mã của bạn để kiểm tra xem nó có tồn tại hay không và xóa nó. Nó có thể không quan trọng trong trường hợp của bạn, nhưng nó là thực hành tốt anyway.

Lưu ý import Prelude hiding (catch) line - điều này là bởi vì Prelude chứa các hàm cũ hơn từ việc xử lý ngoại lệ mà bây giờ không còn được dùng trong Control.Exception, mà còn có một hàm có tên catch; dòng nhập khẩu chỉ đơn giản là ẩn của Prelude catch theo ý kiến Control.Exception'S.

Tuy nhiên, điều đó vẫn còn để lại câu hỏi cơ bản hơn của bạn: làm thế nào để bạn viết các điều kiện trong IO?

Vâng, trong trường hợp này, nó sẽ đủ để chỉ đơn giản là làm

when fileExists $ removeFile filename

(sử dụng Control.Monad.when). Nhưng nó hữu ích ở đây, vì nó thường là trong Haskell, để xem xét các loại.

Cả hai nhánh của một điều kiện phải có cùng loại. Vì vậy, để điền vào

if fileExists
    then removeFile filename
    else ???

chúng ta nên xem xét loại removeFile filename; bất cứ điều gì ??? là, nó phải có cùng loại.

System.Directory.removeFile có loại FilePath -> IO (), vì thế removeFile filename có loại IO (). Vì vậy, những gì chúng tôi muốn là một hành động IO với kết quả của loại () không làm gì cả.

Vâng, mục đích của return là xây dựng một hành động không có hiệu ứng và chỉ trả về một giá trị không đổi và return () có loại phù hợp cho điều này: IO () (hoặc nói chung hơn, (Monad m) => m ()). Vì thế ??? Là return () (mà bạn có thể thấy tôi đã sử dụng trong đoạn mã được cải thiện của mình ở trên, không phải làm gì khi removeFile không thành công vì tệp không tồn tại).

(Nhân tiện, bây giờ bạn có thể thực hiện when với sự giúp đỡ của return (); nó thực sự đơn giản :))

Đừng lo lắng nếu bạn cảm thấy khó khăn để có được vào cách Haskell của sự vật lúc đầu - nó sẽ đến một cách tự nhiên trong thời gian, và khi nó, nó rất bổ ích. :)


45
2017-12-14 09:46



Đây là liên kết đến tài liệu của Control.Exception: hackage.haskell.org/packages/archive/base/latest/doc/html/… - Tôi không thể đặt nó trong bài viết vì tôi không có đủ danh tiếng: / - ehird
Cảm ơn, điều đó thực sự hữu ích! - yogsototh
Không có vấn đề - theo như tài liệu đọc đi, có tuyệt vời Tìm hiểu bạn một Haskell và Thế giới thực Haskell, nhưng tôi tưởng tượng bạn đã biết về những điều này. Tôi có thể đề nghị HoogleTheo mặc định, khả năng tìm kiếm theo kiểu là vô giá đối với lập trình Haskell. - ehird
Tôi đặt liên kết vào văn bản. Câu trả lời hay, sẽ giúp bạn có đủ đại diện để tự mình đặt nhiều hơn hai liên kết :) - Daniel Fischer
Chào mừng bạn đến với SO! Rất vui được gặp bạn ở đây. Bạn hiện có hơn 100 đại diện, vì vậy hầu hết các hạn chế thực sự khó chịu sẽ biến mất. :] - C. A. McCann


(Chú thích: Câu trả lời của ehird làm cho một điểm rất tốt về một điều kiện chủng tộc. Nó nên được lưu ý khi đọc câu trả lời của tôi, mà bỏ qua vấn đề. Cũng lưu ý rằng mã giả bắt buộc được trình bày trong câu hỏi cũng gặp vấn đề tương tự.)

Điều gì định nghĩa tên tập tin? Nó được đưa ra trong chương trình, hoặc được cung cấp bởi người sử dụng? Trong mã giả bắt buộc của bạn, nó là một chuỗi liên tục trong chương trình. Tôi giả sử bạn muốn người dùng cung cấp nó bằng cách chuyển nó làm đối số dòng lệnh đầu tiên cho chương trình.

Sau đó, tôi đề nghị một cái gì đó như thế này:

import Control.Monad    
import System.Directory
import System.Environment

doSomethingElse :: IO ()

main = do
  args <- getArgs
  fileExists <- doesFileExist (head args)
  when fileExists (removeFile (head args))
  doSomethingElse

(Như bạn có thể thấy, tôi đã thêm chữ ký kiểu của doSomethingElse để tránh nhầm lẫn).

Tôi nhập System.Environment cho getArgs chức năng. Trong trường hợp tệp được đề cập chỉ đơn giản là được đưa ra bởi một chuỗi không đổi (chẳng hạn như trong mã giả), chỉ cần loại bỏ tất cả các công cụ args và điền vào chuỗi liên tục bất cứ nơi nào tôi có head args.

Control.Monad được nhập để nhận when chức năng. Lưu ý rằng hàm hữu ích này là không phải một từ khóa (như if), nhưng một chức năng bình thường. Hãy nhìn vào kiểu của nó:

when :: Monad m => Bool -> m () -> m ()

Trong trường hợp của bạn m Là IO, bạn có thể nghĩ when như một hàm cần Bool và một hành động IO và chỉ thực hiện hành động nếu Bool Là True. Tất nhiên bạn có thể giải quyết vấn đề của bạn với ifs, nhưng trong trường hợp của bạn when đọc rõ ràng hơn rất nhiều. IT nhât thi tôi nghi vậy.

Phụ lục: Nếu bạn, như tôi đã làm lúc đầu, có cảm giác rằng when là một số máy móc kỳ diệu và khó khăn, rất hữu ích khi cố tự định nghĩa hàm. Tôi hứa với bạn, nó đã chết đơn giản ...


11
2017-12-14 09:45



Nó cũng có tính cách để suy nghĩ về tại sao nó đơn giản để xác định whenvà những tác động khác có ý nghĩa gì. Các khối mã bắt buộc là các thực thể hạng nhất trong Haskell theo cách mà ít ngôn ngữ có thể khớp. - C. A. McCann