Rationale

I find Haskell a fine tool for writing little utility programs. It’s most succinct, elegant, and programs tend to just work suprisingly often. Often I’ll just write a single .hs file and the job’s done.

However there are a couple of issues:

This simple library is an attempt to solve these. It is deliberately opinionated in that it provides functions which I find useful, whilst making it harder to access things which I use less often. You might think of it as a compression scheme tailored to give short expressions for things I personally use often.

Practical matters

You can get the code from GitHub1.

Haddock documentation2 is also available.

If the two resources are in sync, that’s purely coincidental: if you actually care about this, grab the code and generate the docs locally.

Examples

Most of the functions are short enough to read quickly, and are documented in the source. So, I present here some (somewhat contrived) examples.

Toy.Generic3

processGeneric

If you’re interating over some input files, generating a separate output file for each input, it can be fiddly to generate good names for the outputs. This tries to help:

Note: it only processes one file, so you’ll typically call it repeatedly.

processArgs

This seems hardly worthwhile for now, but I have some vague plans to add functionality. Then again, YAGNI usually wins!

An example

Here’s a simple utility to cast the contents of text files into upper-case:

import Toy.Generic							
									
import Data.Char							
									
main = processArgs $ processGeneric ucContents (++ "-u") "txt"		
									
ucContents :: FilePath -> FilePath -> IO ()				
ucContents = transformText (map toUpper)				
									
transformText :: (String -> String) ->  FilePath -> FilePath -> IO ()	
transformText tx inf outf = do						
                             orig <- readFile inf			
                             let new = tx orig				
                             writeFile outf new				

transformText should probably be included in a (presently non-existent) Toy.Text module.

You might use it like this:

$ echo abc > abc.txt							
$ echo def > def.text	 						
$ ls *.txt								
abc.txt def.text							
$ stack exec ht-uc-text *.txt *.text					
$ ls *.txt								
abc-u.txt abc.txt   def-u.txt def.text					
$ cat abc-u.txt 							
ABC									

Toy.JuicyPixels4

JuicyPixels5 is a great package for loading and saving images, and I often use it to munge images in command line tools.

Toy.JuicyPixels deals exclusively with PixelRGB8 images because most of the images I encounter are in that format.

loadImage, loadImageThen

As their names suggest these loadImages, but unlike readImage in the JuicyPixels library force the data loaded into PixelRGB8 format.

loadImageThen also handles error conditions by printing an error message: this simplifies life for the user.

Here’s a complete GHCI session showing code to extract the width of an image:

$ stack ghci								
Run from outside a project, using implicit global project config	
...									
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help		
Ok, modules loaded: none.						
ghci> import Codec.Picture						
ghci> import Toy.JuicyPixels 						
ghci> loadImageThen (putStrLn . show . imageWidth) "avatar.png" 	
420									

transformImagePNG, transformImagePNG'

These load an image, transform its contents, then saves the result as a PNG file. The PNG' version uses a fixed basename transformation: adding -x.

Again here’s an example in ghci: swapping red and blue channels:

$ stack ghci								
Run from outside a project, using implicit global project config	
...									
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help		
Ok, modules loaded: none.						
ghci> import Codec.Picture						
ghci> import Toy.JuicyPixels 						
ghci> let px = pixelMap (\(PixelRGB8 r g b) -> PixelRGB8 b g r)
gchi> transformImagePNG' px "bird.jpg"

transformImagesInArgsPNG

As above, but iterate over the command line arguments, this time in an application.

import Toy.JuicyPixels							
import Codec.Picture							
									
main = transformImagesInArgsPNG px (++ "-f")				
									
px :: ImageRGB8 -> ImageRGB8	
px = pixelMap (\(PixelRGB8 r g b) -> PixelRGB8 b g r)         		

transformImage, transformImagesInArgs

These are more general versions which can write any file format. Accordingly the caller must provide code to save the data, and an appropriate suffix. Unsurprisingly the PNG versions are implemented in terms of these. For example:

transformImagePNG = transformImage writePng "png"

describeImage, describeImagesInArgs

Sometimes, it’s enough to extract data from the image and print it out. Rather than taking a String, any Show instance will do.

Here’s a reimplementation of our image width displayer:

ghci> describeImage imageWidth "avatar.png"
avatar.png:
420

Note that as well as the output, it also prints the filename.

iPixelList, pixelList

Return a list of pixels in the image. This isn’t very efficient, but it can be convenient.

ht-pixel-freqs, one of the toy applications in the distribution uses this:

import Toy.JuicyPixels							
import Codec.Picture							
									
import qualified Data.List as L						
									
import Text.Printf							
									
main = describeImagesInArgs countPixels					
									
countPixels :: ImageRGB8 -> String					
countPixels = concatMap pp . freqs . pixelList				
									
freqs :: (Ord a, Eq a) => [a] -> [(a,Int)]				
freqs = map (\ps -> (head ps, length ps)) . L.group . L.sort		
									
pp (PixelRGB8 r g b, n) = printf "%3d %3d %3d: %8d\n" r g b n		

liftRGB, memoizeWord8

A couple of combinators to help with efficient, colour-blind, transforms. The haskell-toys distribution includes a program which flips the bits in each pixel, which I find helpful to reveal hidden low-level structure.

import Toy.JuicyPixels							
import Codec.Picture							
									
import Data.Word							
import Data.Bits							
import qualified Data.List as L						
									
main = transformImagesInArgsPNG flipImage (++ "-f")			
									
flipImage :: ImageRGB8 -> ImageRGB8					
flipImage = pixelMap (liftRGB flipByte)					
									
flipByte :: Word8 -> Word8						
flipByte = memoizeWord8 flipByte'					
									
flipByte' :: Word8 -> Word8						
flipByte' x = L.foldl' setBit 0 $ [ 7 - i | i <- [0..7], testBit x i ]