Several months ago, Stack added support for building and running Haskell modules on-the-fly. This means that you no longer need to include a Cabal file to build and run a singe-module Haskell applications. Here’s a minimal example, HelloWorld.hs.
#!/usr/bin/env stack -- stack --resolver lts-3.12 --install-ghc runghc main = putStrLn "Hello, world"
Given HelloWorld.hs has executable permissions,
./HelloWorld.hs will produce the expected output!
Stack can be configured in the second line of the file, with general form
-- stack [options] runghc [options]. Above, the
--install-ghc switch instructs Stack to download and install the appropriate version of GHC if it’s not already present. Specifying the resolver version is also crucial, as it enables the build to be forever reproducible, regardless of updates to any package dependencies. Happily, no extra work is required to import modules from other packages included in the resolver’s snapshot, although non-resolver packages can be loaded with the
This feature finally allows programmers to leverage the advantages of Haskell in place of traditional scripting languages such as Python. This would be especially useful for ‘glue’ scripts, transforming data representations between programs, given Haskell’s type safely and excellent support for a myriad of data formats.
The Turtle Library
Gabriel Gonzalez’ turtle library attempts to leverage Haskell in place of Bash. I write Bash scripts very occasionally and am prone to forgetting the syntax, so immediately this is an interesting proposition to me. However, does the flexibility of the command line utilities translate usefully to Haskell?
The turtle library serves two purposes. Firstly, it re-exports commonly-used utilities provided by other packages, reducing the number of dependencies in your scripts. Some of these functions are renamed to match their UNIX equivalents. Secondly, it exposes the
Shell type, which encapsulates streaming the results of running some of these utilities. For example,
stdin :: Shell Text streams all lines from the standard input, while
ls :: FilePath -> Shell FilePath streams all immediate children of the given directory.
Shell type is similar to
ListT IO, and shares its monadic behaviour. Binding to a
Shell computation extracts an element from the computation’s output stream, and
mzero terminates the current stream of computation. Text operations are line-oriented, so the monadic instance is all about feeding a line of output into another
Shell computation. Putting this together lets us write really expressive operations, such as the following.
readCurrentDir :: Shell Text readCurrentDir = do file <- ls "." guard $ testfile file -- Ensure 'file' is a regular file! input file -- Read the file
readCurrentDir is a
Shell which streams each line of text of each file in the current directory. Remember, guard will terminate the computation when file isn’t a directory, so its contents will never be read. Without it, we risk an exception being thrown when trying to read a non-regular file.
The library exposes functions for aggregating and filtering the output of a
grep :: Pattern a -> Shell Text -> Shell Text uses a
Pattern to discard non-conforming lines, identical to its namesake. The
Pattern type is a monadic backtracking parser capable of both matching and parsing values from
Text, and is also used in the
sed :: Pattern Text -> Shell Text -> Shell Text utility. The
Fold interface collapses the
Shell to a single result, and
view run a shell to completion in the
Perhaps unsurprisingly, Turtle’s utilities are not as flexible as their command-line equivalents, as no variants exists for different command-line arguments. Of course, it would be possible for the library to expose a ‘Grep’ module with a typed representation of the program’s numerous optional parameters, but this seems overkill when I can execute and stream the results with
inShell. This, then, informs my use of Turtle in the future. I do see myself using it in place of shell scripts, and not for typed representations of UNIX utilities. The library lifts shell output into an environment which I, as a Haskell programmer, can manipulate efficiently and expressively. It serves as the glue whose syntax and semantics I already reason with every day.