This is a proposal to add better-files
to the Scala Platform.
Introduction
better-files
is a dependency-free pragmatic thin Scala wrapper around Java NIO. It is an alternative to the IO interface present in the Scala standard library and defines idiomatic helpers to handle IO in a sane and elegant way. It is licensed under the MIT license.
Features
- Java interoperability
- Easy to use
- Efficient NIO wrappers
- Zero external dependencies (aside from JVM-bundled dependencies)
Merits
better-files
has got a lot of traffic lately:
- Gave couple of well received talks on it at Scala Days and Scala by the Bay in 2016
-
2000 Maven downloads. Met a lot of people who use the library at their job/personal use at conferences.
- 700+ stars on Github
- Fairly small and self-contained: <500 LOC and no external dependencies
- Search on sbt files in Github has >200 hits
Example usage
better-files
allow users to work with IO in different styles. Here, I only describe the most common. For more information on the supported use cases and explanations on the syntax, have a look at the README.
File instantiation
import better.files._
import java.io.{File => JFile}
val f = File("/User/johndoe/Documents") // using constructor
val f1: File = file"/User/johndoe/Documents" // using string interpolator
val f2: File = "/User/johndoe/Documents".toFile // convert a string path to a file
val f3: File = new JFile("/User/johndoe/Documents").toScala // convert a Java file to Scala
val f4: File = root/"User"/"johndoe"/"Documents" // using root helper to start from root
val f5: File = `~` / "Documents" // also equivalent to `home / "Documents"`
val f6: File = "/User"/"johndoe"/"Documents" // using file separator DSL
val f7: File = home/"Documents"/"presentations"/`..` // Use `..` to navigate up to parent
File read/write
val file = root/"tmp"/"test.txt"
file.overwrite("hello")
file.appendLine().append("world")
assert(file.contentAsString == "hello\nworld")
Streams and codecs
Various ways to slurp a file without loading the contents into memory:
val bytes : Iterator[Byte] = file.bytes
val chars : Iterator[Char] = file.chars
val lines : Iterator[String] = file.lines
val source : scala.io.BufferedSource = file.newBufferedSource // needs to be closed, unlike the above APIs which auto closes when iterator ends
Java interoperability
We can go from better-files
wrappers to Java wrapper and the other way around at any time.
val file: File = tmp / "hello.txt"
val javaFile : java.io.File = file.toJava
val uri : java.net.uri = file.uri
val reader : java.io.BufferedReader = file.newBufferedReader
val outputstream : java.io.OutputStream = file.newOutputStream
val writer : java.io.BufferedWriter = file.newBufferedWriter
val inputstream : java.io.InputStream = file.newInputStream
val path : java.nio.file.Path = file.path
val fs : java.nio.file.FileSystem = file.fileSystem
val channel : java.nio.channel.FileChannel = file.newFileChannel
val ram : java.io.RandomAccessFile = file.newRandomAccess
val fr : java.io.FileReader = file.newFileReader
val fw : java.io.FileWriter = file.newFileWriter(append = true)
val printer : java.io.PrintWriter = file.newPrintWriter
Better pattern matching
better-files
defines extractor objects that help pattern matching on files and avoiding if-else expressions.
/**
* @return true if file is a directory with no children or a file with no contents
*/
def isEmpty(file: File): Boolean = file match {
case File.Type.SymbolicLink(to) => isEmpty(to) // this must be first case statement if you want to handle symlinks specially; else will follow link
case File.Type.Directory(files) => files.isEmpty
case File.Type.RegularFile(content) => content.isEmpty
case _ => file.notExists // a file may not be one of the above e.g. UNIX pipes, sockets, devices etc
}
// or as extractors on LHS:
val File.Type.Directory(researchDocs) = home/"Downloads"/"research"
File system operations
Utilities like ls, cp, rm, mv, ln, md5, diff, touch, cat are easy to use. See this.
Implementation
The implementation has no extra dependencies aside from the NIO module bundled into the JDK8.
Alternatives
- There are other good libraries out there e.g. Apache Commons IO, Guava but they are in Java
- In Scala world, only Li Haoyi’s ammonite-ops comes close.
- This is the biggest: By no means, this is side-effect free purely functional library. There are purer idioms we can use (IOMonads) that better-files does not touch. better-files is simply a wrapper around Java NIO and happily does side-effects and throws Exceptions that Java throws. This may not be palatable to many Scala programmers.
Bibliography
Previous discussion: Scala IO fix-up/overhaul · Issue #19 · scala/slip · GitHub