2 % (c) The University of Glasgow 2006
3 % (c) The University of Glasgow 1992-2002
5 \section[Util]{Highly random utility functions}
9 debugIsOn, isWindowsHost,
11 -- general list processing
12 zipEqual, zipWithEqual, zipWith3Equal, zipWith4Equal,
13 zipLazy, stretchZipWith,
15 mapAndUnzip, mapAndUnzip3,
16 nOfThem, filterOut, partitionWith, splitEithers,
19 lengthExceeds, lengthIs, lengthAtLeast,
20 listLengthCmp, atLength, equalLength, compareLength,
22 isSingleton, only, singleton,
33 -- transitive closures
39 takeList, dropList, splitAtList, split,
43 thenCmp, cmpList, maybePrefixMatch,
57 getCmd, toCmdArgs, toArgs,
59 -- Floating point stuff
63 createDirectoryHierarchy,
65 modificationTimeIfExists,
67 later, handleDyn, handle,
74 Direction(..), reslash,
77 #include "HsVersions.h"
81 import Control.Exception ( Exception(..), finally, catchDyn, throw )
82 import qualified Control.Exception as Exception
83 import Data.Dynamic ( Typeable )
84 import Data.IORef ( IORef, newIORef )
85 import System.IO.Unsafe ( unsafePerformIO )
86 import Data.IORef ( readIORef, writeIORef )
87 import Data.List hiding (group)
89 import qualified Data.List as List ( elem )
91 import qualified Data.List as List ( notElem )
95 import Control.Monad ( unless )
96 import SYSTEM_IO_ERROR as IO ( catch, isDoesNotExistError )
97 import System.Directory ( doesDirectoryExist, createDirectory,
99 import System.FilePath hiding ( searchPathSeparator )
100 import Data.Char ( isUpper, isAlphaNum, isSpace, ord, isDigit )
101 import Data.Ratio ( (%) )
102 import System.Time ( ClockTime )
107 %************************************************************************
109 \subsection{Is DEBUG on, are we on Windows?}
111 %************************************************************************
121 isWindowsHost :: Bool
122 #ifdef mingw32_HOST_OS
125 isWindowsHost = False
129 %************************************************************************
131 \subsection{A for loop}
133 %************************************************************************
136 -- Compose a function with itself n times. (nth rather than twice)
137 nTimes :: Int -> (a -> a) -> (a -> a)
140 nTimes n f = f . nTimes (n-1) f
143 %************************************************************************
145 \subsection[Utils-lists]{General list processing}
147 %************************************************************************
150 filterOut :: (a->Bool) -> [a] -> [a]
151 -- Like filter, only reverses the sense of the test
153 filterOut p (x:xs) | p x = filterOut p xs
154 | otherwise = x : filterOut p xs
156 partitionWith :: (a -> Either b c) -> [a] -> ([b], [c])
157 partitionWith _ [] = ([],[])
158 partitionWith f (x:xs) = case f x of
160 Right c -> (bs, c:cs)
161 where (bs,cs) = partitionWith f xs
163 splitEithers :: [Either a b] -> ([a], [b])
164 splitEithers [] = ([],[])
165 splitEithers (e : es) = case e of
167 Right y -> (xs, y:ys)
168 where (xs,ys) = splitEithers es
171 A paranoid @zip@ (and some @zipWith@ friends) that checks the lists
172 are of equal length. Alastair Reid thinks this should only happen if
173 DEBUGging on; hey, why not?
176 zipEqual :: String -> [a] -> [b] -> [(a,b)]
177 zipWithEqual :: String -> (a->b->c) -> [a]->[b]->[c]
178 zipWith3Equal :: String -> (a->b->c->d) -> [a]->[b]->[c]->[d]
179 zipWith4Equal :: String -> (a->b->c->d->e) -> [a]->[b]->[c]->[d]->[e]
183 zipWithEqual _ = zipWith
184 zipWith3Equal _ = zipWith3
185 zipWith4Equal _ = zipWith4
187 zipEqual _ [] [] = []
188 zipEqual msg (a:as) (b:bs) = (a,b) : zipEqual msg as bs
189 zipEqual msg _ _ = panic ("zipEqual: unequal lists:"++msg)
191 zipWithEqual msg z (a:as) (b:bs)= z a b : zipWithEqual msg z as bs
192 zipWithEqual _ _ [] [] = []
193 zipWithEqual msg _ _ _ = panic ("zipWithEqual: unequal lists:"++msg)
195 zipWith3Equal msg z (a:as) (b:bs) (c:cs)
196 = z a b c : zipWith3Equal msg z as bs cs
197 zipWith3Equal _ _ [] [] [] = []
198 zipWith3Equal msg _ _ _ _ = panic ("zipWith3Equal: unequal lists:"++msg)
200 zipWith4Equal msg z (a:as) (b:bs) (c:cs) (d:ds)
201 = z a b c d : zipWith4Equal msg z as bs cs ds
202 zipWith4Equal _ _ [] [] [] [] = []
203 zipWith4Equal msg _ _ _ _ _ = panic ("zipWith4Equal: unequal lists:"++msg)
208 -- zipLazy is lazy in the second list (observe the ~)
210 zipLazy :: [a] -> [b] -> [(a,b)]
212 -- We want to write this, but with GHC 6.4 we get a warning, so it
214 -- zipLazy (x:xs) ~(y:ys) = (x,y) : zipLazy xs ys
215 -- so we write this instead:
216 zipLazy (x:xs) zs = let y : ys = zs
217 in (x,y) : zipLazy xs ys
222 stretchZipWith :: (a -> Bool) -> b -> (a->b->c) -> [a] -> [b] -> [c]
223 -- (stretchZipWith p z f xs ys) stretches ys by inserting z in
224 -- the places where p returns *True*
226 stretchZipWith _ _ _ [] _ = []
227 stretchZipWith p z f (x:xs) ys
228 | p x = f x z : stretchZipWith p z f xs ys
229 | otherwise = case ys of
231 (y:ys) -> f x y : stretchZipWith p z f xs ys
236 mapFst :: (a->c) -> [(a,b)] -> [(c,b)]
237 mapSnd :: (b->c) -> [(a,b)] -> [(a,c)]
239 mapFst f xys = [(f x, y) | (x,y) <- xys]
240 mapSnd f xys = [(x, f y) | (x,y) <- xys]
242 mapAndUnzip :: (a -> (b, c)) -> [a] -> ([b], [c])
244 mapAndUnzip _ [] = ([], [])
247 (rs1, rs2) = mapAndUnzip f xs
251 mapAndUnzip3 :: (a -> (b, c, d)) -> [a] -> ([b], [c], [d])
253 mapAndUnzip3 _ [] = ([], [], [])
254 mapAndUnzip3 f (x:xs)
255 = let (r1, r2, r3) = f x
256 (rs1, rs2, rs3) = mapAndUnzip3 f xs
258 (r1:rs1, r2:rs2, r3:rs3)
262 nOfThem :: Int -> a -> [a]
263 nOfThem n thing = replicate n thing
265 -- 'atLength atLen atEnd ls n' unravels list 'ls' to position 'n';
268 -- atLength atLenPred atEndPred ls n
269 -- | n < 0 = atLenPred n
270 -- | length ls < n = atEndPred (n - length ls)
271 -- | otherwise = atLenPred (drop n ls)
273 atLength :: ([a] -> b)
278 atLength atLenPred atEndPred ls n
279 | n < 0 = atEndPred n
280 | otherwise = go n ls
282 go n [] = atEndPred n
283 go 0 ls = atLenPred ls
284 go n (_:xs) = go (n-1) xs
287 lengthExceeds :: [a] -> Int -> Bool
288 -- (lengthExceeds xs n) = (length xs > n)
289 lengthExceeds = atLength notNull (const False)
291 lengthAtLeast :: [a] -> Int -> Bool
292 lengthAtLeast = atLength notNull (== 0)
294 lengthIs :: [a] -> Int -> Bool
295 lengthIs = atLength null (==0)
297 listLengthCmp :: [a] -> Int -> Ordering
298 listLengthCmp = atLength atLen atEnd
302 | x > 0 = LT -- not yet seen 'n' elts, so list length is < n.
308 equalLength :: [a] -> [b] -> Bool
309 equalLength [] [] = True
310 equalLength (_:xs) (_:ys) = equalLength xs ys
311 equalLength _ _ = False
313 compareLength :: [a] -> [b] -> Ordering
314 compareLength [] [] = EQ
315 compareLength (_:xs) (_:ys) = compareLength xs ys
316 compareLength [] _ = LT
317 compareLength _ [] = GT
319 ----------------------------
320 singleton :: a -> [a]
323 isSingleton :: [a] -> Bool
324 isSingleton [_] = True
325 isSingleton _ = False
327 notNull :: [a] -> Bool
337 only _ = panic "Util: only"
340 Debugging/specialising versions of \tr{elem} and \tr{notElem}
343 isIn, isn'tIn :: Eq a => String -> a -> [a] -> Bool
346 isIn _msg x ys = elem__ x ys
347 isn'tIn _msg x ys = notElem__ x ys
349 --these are here to be SPECIALIZEd (automagically)
350 elem__ :: Eq a => a -> [a] -> Bool
352 elem__ x (y:ys) = x == y || elem__ x ys
354 notElem__ :: Eq a => a -> [a] -> Bool
355 notElem__ _ [] = True
356 notElem__ x (y:ys) = x /= y && notElem__ x ys
360 = elem (_ILIT(0)) x ys
364 | i ># _ILIT(100) = trace ("Over-long elem in " ++ msg)
365 (x `List.elem` (y:ys))
366 | otherwise = x == y || elem (i +# _ILIT(1)) x ys
369 = notElem (_ILIT(0)) x ys
371 notElem _ _ [] = True
373 | i ># _ILIT(100) = trace ("Over-long notElem in " ++ msg)
374 (x `List.notElem` (y:ys))
375 | otherwise = x /= y && notElem (i +# _ILIT(1)) x ys
379 foldl1' was added in GHC 6.4
382 #if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ < 604
383 foldl1' :: (a -> a -> a) -> [a] -> a
384 foldl1' f (x:xs) = foldl' f x xs
385 foldl1' _ [] = panic "foldl1'"
389 %************************************************************************
391 \subsubsection[Utils-Carsten-mergesort]{A mergesort from Carsten}
393 %************************************************************************
396 Date: Mon, 3 May 93 20:45:23 +0200
397 From: Carsten Kehler Holst <kehler@cs.chalmers.se>
398 To: partain@dcs.gla.ac.uk
399 Subject: natural merge sort beats quick sort [ and it is prettier ]
401 Here is a piece of Haskell code that I'm rather fond of. See it as an
402 attempt to get rid of the ridiculous quick-sort routine. group is
403 quite useful by itself I think it was John's idea originally though I
404 believe the lazy version is due to me [surprisingly complicated].
405 gamma [used to be called] is called gamma because I got inspired by
406 the Gamma calculus. It is not very close to the calculus but does
407 behave less sequentially than both foldr and foldl. One could imagine
408 a version of gamma that took a unit element as well thereby avoiding
409 the problem with empty lists.
411 I've tried this code against
413 1) insertion sort - as provided by haskell
414 2) the normal implementation of quick sort
415 3) a deforested version of quick sort due to Jan Sparud
416 4) a super-optimized-quick-sort of Lennart's
418 If the list is partially sorted both merge sort and in particular
419 natural merge sort wins. If the list is random [ average length of
420 rising subsequences = approx 2 ] mergesort still wins and natural
421 merge sort is marginally beaten by Lennart's soqs. The space
422 consumption of merge sort is a bit worse than Lennart's quick sort
423 approx a factor of 2. And a lot worse if Sparud's bug-fix [see his
424 fpca article ] isn't used because of group.
431 group :: (a -> a -> Bool) -> [a] -> [[a]]
432 -- Given a <= function, group finds maximal contiguous up-runs
433 -- or down-runs in the input list.
434 -- It's stable, in the sense that it never re-orders equal elements
436 -- Date: Mon, 12 Feb 1996 15:09:41 +0000
437 -- From: Andy Gill <andy@dcs.gla.ac.uk>
438 -- Here is a `better' definition of group.
441 group p (x:xs) = group' xs x x (x :)
443 group' [] _ _ s = [s []]
444 group' (x:xs) x_min x_max s
445 | x_max `p` x = group' xs x_min x (s . (x :))
446 | not (x_min `p` x) = group' xs x x_max ((x :) . s)
447 | otherwise = s [] : group' xs x x (x :)
448 -- NB: the 'not' is essential for stablity
449 -- x `p` x_min would reverse equal elements
451 generalMerge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
452 generalMerge _ xs [] = xs
453 generalMerge _ [] ys = ys
454 generalMerge p (x:xs) (y:ys) | x `p` y = x : generalMerge p xs (y:ys)
455 | otherwise = y : generalMerge p (x:xs) ys
457 -- gamma is now called balancedFold
459 balancedFold :: (a -> a -> a) -> [a] -> a
460 balancedFold _ [] = error "can't reduce an empty list using balancedFold"
461 balancedFold _ [x] = x
462 balancedFold f l = balancedFold f (balancedFold' f l)
464 balancedFold' :: (a -> a -> a) -> [a] -> [a]
465 balancedFold' f (x:y:xs) = f x y : balancedFold' f xs
466 balancedFold' _ xs = xs
468 generalNaturalMergeSort :: (a -> a -> Bool) -> [a] -> [a]
469 generalNaturalMergeSort _ [] = []
470 generalNaturalMergeSort p xs = (balancedFold (generalMerge p) . group p) xs
473 generalMergeSort p [] = []
474 generalMergeSort p xs = (balancedFold (generalMerge p) . map (: [])) xs
476 mergeSort, naturalMergeSort :: Ord a => [a] -> [a]
478 mergeSort = generalMergeSort (<=)
479 naturalMergeSort = generalNaturalMergeSort (<=)
481 mergeSortLe le = generalMergeSort le
484 sortLe :: (a->a->Bool) -> [a] -> [a]
485 sortLe le = generalNaturalMergeSort le
487 sortWith :: Ord b => (a->b) -> [a] -> [a]
488 sortWith get_key xs = sortLe le xs
490 x `le` y = get_key x < get_key y
492 on :: (a -> a -> Ordering) -> (b -> a) -> b -> b -> Ordering
493 on cmp sel = \x y -> sel x `cmp` sel y
497 %************************************************************************
499 \subsection[Utils-transitive-closure]{Transitive closure}
501 %************************************************************************
503 This algorithm for transitive closure is straightforward, albeit quadratic.
506 transitiveClosure :: (a -> [a]) -- Successor function
507 -> (a -> a -> Bool) -- Equality predicate
509 -> [a] -- The transitive closure
511 transitiveClosure succ eq xs
515 go done (x:xs) | x `is_in` done = go done xs
516 | otherwise = go (x:done) (succ x ++ xs)
519 x `is_in` (y:ys) | eq x y = True
520 | otherwise = x `is_in` ys
523 %************************************************************************
525 \subsection[Utils-accum]{Accumulating}
527 %************************************************************************
529 A combination of foldl with zip. It works with equal length lists.
532 foldl2 :: (acc -> a -> b -> acc) -> acc -> [a] -> [b] -> acc
534 foldl2 k z (a:as) (b:bs) = foldl2 k (k z a b) as bs
535 foldl2 _ _ _ _ = panic "Util: foldl2"
537 all2 :: (a -> b -> Bool) -> [a] -> [b] -> Bool
538 -- True if the lists are the same length, and
539 -- all corresponding elements satisfy the predicate
541 all2 p (x:xs) (y:ys) = p x y && all2 p xs ys
545 Count the number of times a predicate is true
548 count :: (a -> Bool) -> [a] -> Int
550 count p (x:xs) | p x = 1 + count p xs
551 | otherwise = count p xs
554 @splitAt@, @take@, and @drop@ but with length of another
555 list giving the break-off point:
558 takeList :: [b] -> [a] -> [a]
563 (y:ys) -> y : takeList xs ys
565 dropList :: [b] -> [a] -> [a]
567 dropList _ xs@[] = xs
568 dropList (_:xs) (_:ys) = dropList xs ys
571 splitAtList :: [b] -> [a] -> ([a], [a])
572 splitAtList [] xs = ([], xs)
573 splitAtList _ xs@[] = (xs, xs)
574 splitAtList (_:xs) (y:ys) = (y:ys', ys'')
576 (ys', ys'') = splitAtList xs ys
578 snocView :: [a] -> Maybe ([a],a)
579 -- Split off the last element
580 snocView [] = Nothing
581 snocView xs = go [] xs
583 -- Invariant: second arg is non-empty
584 go acc [x] = Just (reverse acc, x)
585 go acc (x:xs) = go (x:acc) xs
586 go _ [] = panic "Util: snocView"
588 split :: Char -> String -> [String]
589 split c s = case rest of
591 _:rest -> chunk : split c rest
592 where (chunk, rest) = break (==c) s
596 %************************************************************************
598 \subsection[Utils-comparison]{Comparisons}
600 %************************************************************************
603 isEqual :: Ordering -> Bool
604 -- Often used in (isEqual (a `compare` b))
609 thenCmp :: Ordering -> Ordering -> Ordering
610 {-# INLINE thenCmp #-}
611 thenCmp EQ ordering = ordering
612 thenCmp ordering _ = ordering
614 eqListBy :: (a->a->Bool) -> [a] -> [a] -> Bool
615 eqListBy _ [] [] = True
616 eqListBy eq (x:xs) (y:ys) = eq x y && eqListBy eq xs ys
617 eqListBy _ _ _ = False
619 cmpList :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
620 -- `cmpList' uses a user-specified comparer
625 cmpList cmp (a:as) (b:bs)
626 = case cmp a b of { EQ -> cmpList cmp as bs; xxx -> xxx }
630 -- This (with a more general type) is Data.List.stripPrefix from GHC 6.8.
631 -- This definition can be removed once we require at least 6.8 to build.
632 maybePrefixMatch :: String -> String -> Maybe String
633 maybePrefixMatch [] rest = Just rest
634 maybePrefixMatch (_:_) [] = Nothing
635 maybePrefixMatch (p:pat) (r:rest)
636 | p == r = maybePrefixMatch pat rest
637 | otherwise = Nothing
639 removeSpaces :: String -> String
640 removeSpaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
643 %************************************************************************
645 \subsection[Utils-pairs]{Pairs}
647 %************************************************************************
650 unzipWith :: (a -> b -> c) -> [(a, b)] -> [c]
651 unzipWith f pairs = map ( \ (a, b) -> f a b ) pairs
655 seqList :: [a] -> b -> b
657 seqList (x:xs) b = x `seq` seqList xs b
663 global :: a -> IORef a
664 global a = unsafePerformIO (newIORef a)
668 consIORef :: IORef [a] -> a -> IO ()
671 writeIORef var (x:xs)
677 looksLikeModuleName :: String -> Bool
678 looksLikeModuleName [] = False
679 looksLikeModuleName (c:cs) = isUpper c && go cs
681 go ('.':cs) = looksLikeModuleName cs
682 go (c:cs) = (isAlphaNum c || c == '_') && go cs
685 Akin to @Prelude.words@, but acts like the Bourne shell, treating
686 quoted strings as Haskell Strings, and also parses Haskell [String]
690 getCmd :: String -> Either String -- Error
691 (String, String) -- (Cmd, Rest)
692 getCmd s = case break isSpace $ dropWhile isSpace s of
693 ([], _) -> Left ("Couldn't find command in " ++ show s)
696 toCmdArgs :: String -> Either String -- Error
697 (String, [String]) -- (Cmd, Args)
698 toCmdArgs s = case getCmd s of
700 Right (cmd, s') -> case toArgs s' of
702 Right args -> Right (cmd, args)
704 toArgs :: String -> Either String -- Error
707 = case dropWhile isSpace str of
708 s@('[':_) -> case reads s of
710 | all isSpace spaces ->
713 Left ("Couldn't read " ++ show str ++ "as [String]")
716 toArgs' s = case dropWhile isSpace s of
718 ('"' : _) -> case reads s of
720 -- rest must either be [] or start with a space
721 | all isSpace (take 1 rest) ->
724 Right args -> Right (arg : args)
726 Left ("Couldn't read " ++ show s ++ "as String")
727 s' -> case break isSpace s' of
728 (arg, s'') -> case toArgs' s'' of
730 Right args -> Right (arg : args)
733 -- -----------------------------------------------------------------------------
737 readRational__ :: ReadS Rational -- NB: doesn't handle leading "-"
738 readRational__ r = do
741 return ((n%1)*10^^(k-d), t)
744 (ds,s) <- lexDecDigits r
745 (ds',t) <- lexDotDigits s
746 return (read (ds++ds'), length ds', t)
748 readExp (e:s) | e `elem` "eE" = readExp' s
749 readExp s = return (0,s)
751 readExp' ('+':s) = readDec s
752 readExp' ('-':s) = do (k,t) <- readDec s
754 readExp' s = readDec s
757 (ds,r) <- nonnull isDigit s
758 return (foldl1 (\n d -> n * 10 + d) [ ord d - ord '0' | d <- ds ],
761 lexDecDigits = nonnull isDigit
763 lexDotDigits ('.':s) = return (span isDigit s)
764 lexDotDigits s = return ("",s)
766 nonnull p s = do (cs@(_:_),t) <- return (span p s)
769 readRational :: String -> Rational -- NB: *does* handle a leading "-"
772 '-' : xs -> - (read_me xs)
776 = case (do { (x,"") <- readRational__ s ; return x }) of
778 [] -> error ("readRational: no parse:" ++ top_s)
779 _ -> error ("readRational: ambiguous parse:" ++ top_s)
782 -----------------------------------------------------------------------------
783 -- Create a hierarchy of directories
785 createDirectoryHierarchy :: FilePath -> IO ()
786 createDirectoryHierarchy dir | isDrive dir = return () -- XXX Hack
787 createDirectoryHierarchy dir = do
788 b <- doesDirectoryExist dir
789 unless b $ do createDirectoryHierarchy (takeDirectory dir)
792 -----------------------------------------------------------------------------
793 -- Verify that the 'dirname' portion of a FilePath exists.
795 doesDirNameExist :: FilePath -> IO Bool
796 doesDirNameExist fpath = case takeDirectory fpath of
797 "" -> return True -- XXX Hack
798 _ -> doesDirectoryExist (takeDirectory fpath)
800 -- -----------------------------------------------------------------------------
803 later :: IO b -> IO a -> IO a
806 handleDyn :: Typeable ex => (ex -> IO a) -> IO a -> IO a
807 handleDyn = flip catchDyn
809 handle :: (Exception -> IO a) -> IO a -> IO a
810 handle h f = f `Exception.catch` \e -> case e of
811 ExitException _ -> throw e
814 -- --------------------------------------------------------------
815 -- check existence & modification time at the same time
817 modificationTimeIfExists :: FilePath -> IO (Maybe ClockTime)
818 modificationTimeIfExists f = do
819 (do t <- getModificationTime f; return (Just t))
820 `IO.catch` \e -> if isDoesNotExistError e
824 -- split a string at the last character where 'pred' is True,
825 -- returning a pair of strings. The first component holds the string
826 -- up (but not including) the last character for which 'pred' returned
827 -- True, the second whatever comes after (but also not including the
830 -- If 'pred' returns False for all characters in the string, the original
831 -- string is returned in the first component (and the second one is just
833 splitLongestPrefix :: String -> (Char -> Bool) -> (String,String)
834 splitLongestPrefix str pred
835 | null r_pre = (str, [])
836 | otherwise = (reverse (tail r_pre), reverse r_suf)
837 -- 'tail' drops the char satisfying 'pred'
838 where (r_suf, r_pre) = break pred (reverse str)
840 escapeSpaces :: String -> String
841 escapeSpaces = foldr (\c s -> if isSpace c then '\\':c:s else c:s) ""
845 --------------------------------------------------------------
847 --------------------------------------------------------------
849 -- | The function splits the given string to substrings
850 -- using the 'searchPathSeparator'.
851 parseSearchPath :: String -> [FilePath]
852 parseSearchPath path = split path
854 split :: String -> [String]
858 _:rest -> chunk : split rest
862 #ifdef mingw32_HOST_OS
863 ('\"':xs@(_:_)) | last xs == '\"' -> init xs
867 (chunk', rest') = break (==searchPathSeparator) s
869 -- | A platform-specific character used to separate search path strings in
870 -- environment variables. The separator is a colon (\":\") on Unix and
871 -- Macintosh, and a semicolon (\";\") on the Windows operating system.
872 searchPathSeparator :: Char
873 #if mingw32_HOST_OS || mingw32_TARGET_OS
874 searchPathSeparator = ';'
876 searchPathSeparator = ':'
879 data Direction = Forwards | Backwards
881 reslash :: Direction -> FilePath -> FilePath
883 where f ('/' : xs) = slash : f xs
884 f ('\\' : xs) = slash : f xs
885 f (x : xs) = x : f xs