aboutsummaryrefslogtreecommitdiff
path: root/ch09/9_c_3.hs
blob: bf9bdbf1e4f1a207754b627266b76592b87c0a5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
-- Write a combinator library that makes it possible to express the kinds of
-- iterators that foldTree accepts. Does it make the iterators you write any
-- more succinct?

-- From the assignment it's not clear how the combinators for the iterators
-- should work.
--
-- The operators from the previous exercises in this chapter (>, <, ==, &&, ||)
-- cannot be directly transferred here because this iterator returns three
-- values (Done, Skip, Continue) instead just two (True, False). We would have
-- to define three-value logic system for the Iterate type.
--
-- Let's just define a simple (A `andIter` B) combinator for chaining the
-- iterators as:
--   1. Evaluate (A seed info) to resultA
--   2. If the resultA is Skip or Done, then return the result
--   3. Else if the resultA is (Continue resultSeed) and (resultSeed /= seed),
--      then return the resultA
--   4. Return the result of evaluating (B seed info)


{-- From examples/examples/ch09/FoldDir.hs modified according to the assignment
 --
 -- Added missing import of takeBaseName.
 -- Fixed appending correct root path to directory entries when constructing
 -- their full paths.
 --}
import ControlledVisit (Info(..), getInfo, getUsefulContents, isDirectory)

import Control.Monad (liftM)
import Data.Char (toLower)
import System.FilePath ((</>), takeFileName, takeExtension, takeBaseName)

data Iterate seed = Done     { unwrap :: seed }
                  | Skip     { unwrap :: seed }
                  | Continue { unwrap :: seed }
                    deriving (Show)

type Iterator seed = seed -> Info -> Iterate seed


foldTree :: Iterator a -> a -> FilePath -> IO a

foldTree iter initSeed path = do
    endSeed <- fold initSeed path
    return (unwrap endSeed)
  where
    fold seed subpath = getUsefulContents subpath >>= walk seed subpath

    walk seed root (name:names) = do
      let path' = root </> name
      info <- getInfo path'
      case iter seed info of
        done@(Done _) -> return done
        Skip seed'    -> walk seed' root names
        Continue seed'
          | isDirectory info -> do
              next <- fold seed' path'
              case next of
                done@(Done _) -> return done
                seed''        -> walk (unwrap seed'') root names
          | otherwise -> walk seed' root names
    walk seed _ _ = return (Continue seed)

atMostThreePictures :: Iterator [FilePath]

atMostThreePictures paths info
    | length paths == 3
      = Done paths
    | isDirectory info && takeFileName path == ".svn"
      = Skip paths
    | extension `elem` [".jpg", ".png"]
      = Continue (path : paths)
    | otherwise
      = Continue paths
  where extension = map toLower (takeExtension path)
        path = infoPath info
{-- End of code from examples --}


andIter :: (Eq a) => Iterator a -> Iterator a -> Iterator a
andIter a b seed info = case resA of
                          Done _                  -> resA
                          Skip _                  -> resA
                          cont@(Continue newSeed) -> if newSeed /= seed
                                                     then cont
                                                     else resB
  where resA = a seed info
        resB = b seed info

threePaths :: Iterator [FilePath]
threePaths paths info
    | length paths == 3
      = Done paths
    | otherwise
      = Continue paths
  where path = infoPath info

skipSvn :: Iterator [FilePath]
skipSvn paths info
    | isDirectory info && takeFileName path == ".svn"
      = Skip paths
    | otherwise
      = Continue paths
  where path = infoPath info

includePictures :: Iterator [FilePath]
includePictures paths info
    | extension `elem` [".jpg", ".png"]
      = Continue (path : paths)
    | otherwise
      = Continue paths
  where extension = map toLower (takeExtension path)
        path = infoPath info

myThreePictures :: Iterator [FilePath]
myThreePictures = (threePaths `andIter` skipSvn) `andIter` includePictures


-- Contents of the test directory:
-- test-9_c_3/
-- test-9_c_3/e.jpg
-- test-9_c_3/dirA
-- test-9_c_3/dirA/b.jpg
-- test-9_c_3/dirA/a.png
-- test-9_c_3/.svn
-- test-9_c_3/.svn/svn.jpg
-- test-9_c_3/dirB
-- test-9_c_3/dirB/d.png
-- test-9_c_3/dirB/c.jpg

-- ghci> :l 9_c_3.hs
-- [1 of 2] Compiling ControlledVisit  ( ControlledVisit.hs, interpreted )
-- [2 of 2] Compiling Main             ( 9_c_3.hs, interpreted )
-- Ok, two modules loaded.

-- ghci> foldTree atMostThreePictures [] "test-9_c_3/"
-- ["test-9_c_3/dirA/a.png","test-9_c_3/dirA/b.jpg","test-9_c_3/e.jpg"]

-- ghci> foldTree myThreePictures [] "test-9_c_3/"
-- ["test-9_c_3/dirA/a.png","test-9_c_3/dirA/b.jpg","test-9_c_3/e.jpg"]


-- It doesn't make the iterators more succinct.
--
-- The compound iterator (e.g., myThreePictures) is more readable thanks to
-- hiding the individual iterators in well-named functions, but because the
-- individual iterators cannot share common parts (the 'where' clause) the code
-- duplication cancels the positives.