# Script for copying files to the appropriate directory

In the book Real World Haskell, the author writes in the first line of every file that uses, for example the following: file: ch05/PrettyJSON. The chapter slash and the name of a module.

I wanted to create a script which reads the first line and copies the files to the appropriate directory. In the above example we should copy the file to the directory ch05. I wanted to write this in Haskell in order to use it for "real world" applications.

But before starting writing to Haskell I wrote it in Python:

import os
import shutil

current_dir = os.getcwd()
current_files=os.listdir(current_dir)

for x in haskell_files:
with open(x,"r") as f:
try:
except ValueError as e:
continue

try:
os.mkdir(number)
shutil.copy2(x,number)
except OSError as e:
shutil.copy2(x,number)


And then I tried to write the same code in Haskell:

f x = dropWhile (\x-> x/='c') x
g x = takeWhile (\x-> x=='c' || x=='h' || isDigit x) x
z= do
y<-getCurrentDirectory
e<-getDirectoryContents y
let b=findhs e
mapM w b
findhs filepaths = filter (isSuffixOf ".hs") filepaths
w filename= do
let a=(g.f) y
if "ch" isPrefixOf a
then do
createDirectoryIfMissing False a
g<-getCurrentDirectory
writeFile (g ++"/"++ a ++ "/" ++ filename) y
else
return ()
main=z


Haskell code should be more elegant and more compact but it is not. Can you give me suggestions on making the above program more Haskellish?

• Interesting question though. I think it breaks down to: How to code a completely sequential thing in "good" Haskell. – J Fritsch Sep 29 '12 at 9:00
• You 've got the point @JFritsch. Can we write "good" sequential Haskell programs? This is the real question. Because if we want to do "Real World Haskell" I think that we should know how to write sequential code in Haskell. – Dragno Sep 29 '12 at 9:51
• A few links for scripting shell tasks in Haskell: Haskell shell examples, Shelly: Write your shell scripts in Haskell, Practical Haskell Programming: Scripting with Types. – Petr Sep 29 '12 at 14:04

Notes:

• don't use single letter variable names for top level bindings
• leave spaces around syntax like = and <-
• use meaningful variable names for system values (such as file contents)
• inline single uses of tiny functions, such as your findhs e
• give your core algorithm, w, a meaningful name
• don't use long names for local parameters like filepaths. fs is fine.
• use pointfree style for arguments in the final position
• use when instead of if .. then ... else return ()
• use the filepath library for nice file path construction
• your only use of f and g is to compose them. so do that and name the result.
• use section syntax for lambdas as function arguments to HOFs
• indent consistently
• use mapM_ when the result you don't care about.

Resulting in:

import Data.Char
import Data.List
import System.Directory
import System.FilePath

clean = takeWhile (\x -> x == 'c' || x == 'h' || isDigit x)
. dropWhile (/='c')

process path = do
y <- readFile path

let a = clean y

when ("ch" isPrefixOf a) $do createDirectoryIfMissing False a g <- getCurrentDirectory writeFile (g </> a </> path) y main = do y <- getCurrentDirectory e <- getDirectoryContents y mapM_ process$ filter (isSuffixOf ".hs") e

• much nicer. getCurrentDirectory is superfluous though - in the when clause don't need g, and in main, you can replace y with ".". – AndrewC Sep 29 '12 at 15:00
• In the sense of the original python code, the name of target directory has to be computed as take 4 <$> find (isPrefixOf "ch") (tails y) where y is extracted from head . lines <$> readFile path . – David Unric Sep 29 '12 at 15:08
• main can be written as main = getCurrentDirectory >>= getDirectoryContents >>= mapM_ process . filter (isSuffixOf ".hs") – nponeccop Oct 4 '12 at 16:03
• @nponeccop Much more monadic! – recursion.ninja Jul 13 '15 at 18:24

The first thing you note is that although haskellers like one character variable names in a small scope, we do not like one character functions in a global scope.

There is an implicit partial function in your code. The function that gets the chapter name from the file (called getChapter in the code below), which takes strings (the content of the file) and gives a chapter name if it exists in the string. This seems like a good idea to make explicit using the Maybe type so that this fact does not need to be expressed in the control flow of the program. Note that I have split the getting of the chapter from the contents into the pure part (looking at the string) and the impure part (reading the file), this might be overkill but it's nice to remove impure functions whenever possible.

type Chapter = String

getChapter :: String -> Maybe Chapter
getChapter ('c':'h':xs) = Just ('c':'h': takewhile isDigit xs)
getChapter (_:xs) = getChapter xs
getChapter [] = Nothing

main :: IO ()
main = do
dir <- getCurrentDirectory
files <- getDirectoryContents dir
mapM_ process (filter (isPrefixOf ".hs") files)

process :: FilePath -> IO ()
process fileName = do
chapter <- liftM getChapter (readFile fileName)
when (isJust chapter)
(copyToChapter fileName (fromJust chapter))

copyToChapter :: FilePath -> Chapter -> IO ()
copyToChapter fileName chapter = do
createDirectoryIfMissing False chapter
copyFile fileName (chapter ++ "/" ++ fileName)

• Your getChapter is slightly broken, as it takes only the first digit of the chapter. Also, you waste a lot of time reading the whole file, if the chapter is not specified. – Karolis Juodelė Sep 30 '12 at 11:56
• @KarolisJuodelė I fixed the single digit thing. About wasting a lot of time: The original solution does that too (dropWhile (/='c')) so I just thought it was part of the specification. – HaskellElephant Sep 30 '12 at 19:12
getChapter :: String -> IO (Maybe String)
getChapter name = if ".hs" isSuffixOf name then do
ln <- withFile name ReadMode hGetLine
return $fmap (take 4)$ find ("ch" isPrefixOf) $tails ln -- note, this is what your Pythod code does else return Nothing copyToChapter :: String -> String -> IO () copyToChapter file chapter = do createDirectoryIfMissing False chapter copyFile file (chapter ++ "/" ++ file) process :: String -> IO () process file = do chapter <- getChapter file maybe (return ()) (copyToChapter file) chapter main = do dir <- getCurrentDirectory files <- getDirectoryContents dir mapM process files  This should be an improvement. Note that this is actually longer. Of course, you could write getChapter name = if ".hs" isSuffixOf name then withFile name ReadMode hGetLine >>= return . fmap (take 4) . find ("ch" isPrefixOf) . tails else return Nothing copyToChapter file chapter = createDirectoryIfMissing False chapter >> copyFile file (chapter ++ "/" ++ file) process file = getChapter file >>= maybe (return ()) (copyToChapter file) main = getCurrentDirectory >>= getDirectoryContents >>= mapM process  if you like few lines of code. I'd advise against it though. In general, you should not expect Haskell to be better at procedural tasks than procedural languages. • hould not expect Haskell to be better at procedural tasks than procedural languages. -- but note that because of monads, structuring procedural programs is a lot easier as code blocks are first class. error handling may also be simplified thanks to monads. Abstraction capaabilities always help. – Don Stewart Sep 29 '12 at 14:18 Other way you could write your Shell-like scripts in Haskell is using Shelly library. It provides functionality specifically suited for such tasks, kind of DSL, which brings you more brevity. Its GitHub page contains a bunch of usefull links with examples. E.g. Don's program could be rewritten to: {-# LANGUAGE OverloadedStrings #-} import Data.Text.Lazy as LT import Data.Text.Lazy.IO as LTIO import Prelude as P hiding (FilePath) import Data.Char import Shelly clean = LT.takeWhile (\x -> x == 'c' || x == 'h' || isDigit x) . LT.dropWhile (/='c') readFileP = liftIO . LTIO.readFile . unpack . toTextIgnore process path = shelly$ do
y <- readFileP path
let a = clean y

when ("ch" LT.isPrefixOf a) $do let p = fromText a mkdir_p p cp path p main = shelly$ do
e <- ls "."
mapM_ process \$ P.filter (hasExt "hs") e


The only flaw here is a bit bloated readFileP. I did not find a better way (which might be due to my beginner level). Possibly such function could become a part of Shelly library itself.

So far I found combination of Haskell and Shelly quite good as "a scripting language for every day task" and start to rewrite my scripts utilizing Shelly. That is a nice entrance gateway to Haskell language itself for me.