Path 0.6.0 released
Published on June 16, 2017
As a co-maintainer of
path and the author/maintainer of its companion package
path-io, I’m happy to announce new releases of these libraries. In this post I’m going to quickly remind you what the package is about and then explain important fixes and changes in the latest versions.
path package is currently the most popular way to work with typed paths in Haskell. “Typed” means here that file paths are the same
FilePath strings internally (for the ease of interaction with the existing “vanilla” paths and libraries), but only can be created through validating smart constructors which attach some important information to them at the type level.
The main type is
Path b t where
b—“the base location” of the path: absolute
When your paths are indexed by phantom types like this, it becomes much harder to shoot yourself in the foot. For example, here are some types of common functions for working with paths:
(</>) :: Path b Dir -> Path Rel t -> Path b t—append two paths.
filename :: Path b File -> Path Rel File—take file name.
createDir :: MonadIO m => Path b Dir -> m ()—well, create a directory (from
makeAbsolute :: (MonadIO m, MonadThrow m) => path -> m (AbsPath path)—make a path absolute (
AbsPathis a type function that maps type of a path to its absolute version, this is also from
You probably see now that developing with typed paths is less error-prone.
Who uses the packages?
path-io are used in Stack—Haskell’s most popular solution for project management. If that’s not enough, we can also check reverse dependencies of the
For a more lengthy intro, comparison with other similar solutions, etc., see this original announcement by Chris Done.
Changes in Path 0.6.0
After Chris (the author of
path and its primary maintainer) requested to help him maintain some of his packages, we (Simon Jakobi, Wojciech Daniło, Joe Hillenbrand, Tom Sydney Kerckhove, and me) started to work towards a new major version that would close some holes in the package and make it better at handling some edge cases.
Several changes are related to maintainability and are not really visible to users. I’m going to talk about the changes which are visible to end users and some of them are breaking.
The main change is that we now have
"." as a valid relative directory path. This allows to close this hole:
λ> dir <- parseAbsDir "/" λ> dirname dir "/" λ> :t dirname dir dirname dir :: Path Rel Dir
This was a way to construct an incorrectly “tagged” path:
"/" is an absolute path, but yet it has the type
Path Rel Dir.
In Path 0.6.0 the story looks like this:
λ> dir <- parseAbsDir "/" λ> dirname dir "./" λ> :t dirname dir dirname dir :: Path Rel Dir
It must be noted that although we parse
"." as a valid relative path and print it in this form, internally it’s stored as the empty string. This allows us to perform appending of paths via appending of strings without any additional normalization, so the slightly awkward
"." path composes nicely.
In general, here is a table that shows how
"." works in different contexts:
"./" </> "./" = "./"
"./" </> "x/" = "x/"
"x/" </> "./" = "x/"
dirname "x" = "./"
dirname "/" = "./"
dirname "./" = "./"
Another change is related to the
parent function. Previously it only could be used with absolute paths, now you can get
parent of a relative path as well:
λ> rdir <- parseRelDir "x/" λ> parent rdir "./" λ> dir <- parseAbsDir "/" λ> parent dir "/"
"." acts as a relative dual to the absolute root
The old behavior also led to an inconsistency, which I’m going to demonstrate here:
λ> isParentOf $(mkAbsDir "/") $(mkAbsDir "/") False λ> parent $(mkAbsDir "/") "/"
This is resolved by renaming
isProperPrefix (“proper prefix” is a prefix of a thing that is not equal to that thing), and deprecating
stripDir is renamed to
I must say that all these changes were proposed and implemented by a single person—Harendra Kumar. I’d like to thank him for his time and contributions.
Changes in Path IO 1.3.0
The behavior of
copyDirRecur'has been changed to not follow symbolic links, which is more consistent with how Unix utilities work and seem to be a better default behavior. It’s still possible to list directories following symbolic links via a more general
walkDirAccumfunction. The switch also was initiated by Harendra Kumar (discussion).
isSymlinkwhich allows to test whether a path is a symbolic link (this does not depend on a similar function from
directoryand will work with older GHCs as well).
Moved the type functions
AnyPathtype class (previously they were standalone closed type families). To my knowledge this should not break existing code in most cases. Addition of the
AnyPathconstraint may be necessary in some non-trivial cases though.
I believe that the
path ecosystem has recently reached a new level of maturity. If you don’t yet use
path, maybe now is the time to give it a try!