kylef / PathKit

Effortless path operations in Swift

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Split Path into DirectoryPath and FilePath

kareman opened this issue · comments

Hi, nice framework, it's the best replacement for NSURL in Swift I've seen.

Have you considered splitting Path into DirectoryPath and FilePath? Path has a lot of methods which are only valid for either directories or files, but not both. By separating the functionality into 2 types we can catch more bugs at compile time. Then we could turn Path into a protocol for the functionality that is common to both directories and files.

This is an interesting idea, but there is no way to guarantee your path will still exist and if it is a directory or regular file for the life-span of your instance of the Path.

Example:

let myDirectory: DirectoryPath = Path("x").directory()

// Somewhere in a different process, application or thread:
// `rm x`, `echo "content" > x`, etc

try myDirectory.children()  // fails because x is no longer a directory but a regular file

Therefore there isn't really any added safety. Splitting them into protocols may make sense, we could have DirectoryPath RegularFilePath which Path conforms to. RegularFilePath can include the methods related to reading and writing. DirectoryPath can contain the methods for viewing the children and mkdir, chdir.

/cc @mrackwitz do yo have any opinions with this?

The protocols would allow you to indicate that your function may accept or return a particular path of a specific type.

Example:

func parseJSON(file: RegularFilePath) -> AnyObject {}

But of course, this doesn't really force given value to be a regular file path since it the underlying file on disk may change.

Sure there is always the possibility items in the file system will change and make your paths invalid, but that problem will always be there. What I meant by catching more bugs at compile time is that it would be impossible to perform file operations on a path the programmer has designated as a directory path:

let myDirectory = DirectoryPath("x")

// won't even compile
myDirectory.write("Hello World!")

But using protocols instead should work too, though instantiating paths would be a bit more cumbersome:

let myDirectory = Path("x") as DirectoryPath
// vs
let myDirectory = DirectoryPath("x")

I was thinking about similar ideas using protocols and default implementations.
You could also extend that to nearly all checks which need to be done.

protocol ExistingPath : Path {
    func delete() {  }
}

protocol ReadablePath : ExistingPath, RegularFilePath {
    func read() -> NSData {  }
}

This could be used with Swift's optional unwrapping:

if let existing = file.existing {
    existing.delete()
}

But it could also enhance the above example to something like:

func parseJSON(file: ReadablePath) -> AnyObject {}

But where would you draw the line?

I'm not convinced that Swift is really the right language to do something like that. A language with dependent types would naturally hosts concepts like that. But it would be still really fallacious safety as anything could change anytime on disk as @kylef already explains.

note: I make no claims about improving filesystem access safety. Just trying to catch more programmer errors at compile time.

@mrackwitz I've been thinking along the same lines. I think of a path as an address to a filesystem item which may or may not currently exist. So in order to use a path to access a file or directory you should first have to check if it exists. Like in the if let existing = file.existing example above. But where indeed do we draw the line?

How about at 4: DirectoryPath and FilePath, Directory and File, where the last 2 are reference types ( objects) referring to actual items in the filesystem. If those items are no longer accessible when we try to perform operations on them we will find out in the errors that are thrown.