Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to execute code in Real World Haskell?

Tags:

haskell

Firstly - apologies. This is the first Haskell code I'm ever compiling. I'm compiling some code straight out of Real World Haskell Chapter 24. The code uses a MapReduce engine implemented in another source file to count the number of words in a line. Here is the code:

module Main where

import Control.Monad (forM_)
import Data.Int (Int64)
import qualified Data.ByteString.Lazy.Char8 as LB
import System.Environment (getArgs)

import LineChunks (chunkedReadWith)
import MapReduce (mapReduce, rnf)

lineCount :: [LB.ByteString] -> Int64
lineCount = mapReduce rdeepseq (LB.count '\n')
                      rdeepseq sum

main :: IO ()
main = do
  args <- getArgs
  forM_ args $ \path -> do
    numLines <- chunkedReadWith lineCount path
    putStrLn $ path ++ ": " ++ show numLines

This code compiles fine, and I get a LineCount.exe.

Now, how should I actually use it to count lines in a file? I have a file 'test', which contains some test text. But when I do:

LineCount test

on the command line, I get:

Exception: test: hGetBufSome: illegal operation (handle is closed)

What could be wrong?

Here is more of the code in another file:

module LineChunks
    (
      chunkedReadWith
    ) where

import Control.Exception (bracket, finally)
import Control.Monad (forM, liftM)
import Control.Parallel.Strategies (NFData, rdeepseq)
import Data.Int (Int64)
import qualified Data.ByteString.Lazy.Char8 as LB
import GHC.Conc (numCapabilities)
import System.IO

data ChunkSpec = CS {
      chunkOffset :: !Int64
    , chunkLength :: !Int64
    } deriving (Eq, Show)

withChunks :: (NFData a) =>
              (FilePath -> IO [ChunkSpec])
           -> ([LB.ByteString] -> a)
           -> FilePath
           -> IO a
withChunks chunkFunc process path = do
  (chunks, handles) <- chunkedRead chunkFunc path
  let r = process chunks
  (rdeepseq r `seq` return r) `finally` mapM_ hClose handles

chunkedReadWith :: (NFData a) =>
                   ([LB.ByteString] -> a) -> FilePath -> IO a
chunkedReadWith func path =
    withChunks (lineChunks (numCapabilities * 4)) func path
{-- /snippet withChunks --}

{-- snippet chunkedRead --}
chunkedRead :: (FilePath -> IO [ChunkSpec])
            -> FilePath
            -> IO ([LB.ByteString], [Handle])
chunkedRead chunkFunc path = do
  chunks <- chunkFunc path
  liftM unzip . forM chunks $ \spec -> do
    h <- openFile path ReadMode
    hSeek h AbsoluteSeek (fromIntegral (chunkOffset spec))
    chunk <- LB.take (chunkLength spec) `liftM` LB.hGetContents h
    return (chunk, h)
{-- /snippet chunkedRead --}

{-- snippet lineChunks --}
lineChunks :: Int -> FilePath -> IO [ChunkSpec]
lineChunks numChunks path = do
  bracket (openFile path ReadMode) hClose $ \h -> do
    totalSize <- fromIntegral `liftM` hFileSize h
    let chunkSize = totalSize `div` fromIntegral numChunks
        findChunks offset = do
          let newOffset = offset + chunkSize
          hSeek h AbsoluteSeek (fromIntegral newOffset)
          let findNewline off = do
                eof <- hIsEOF h
                if eof
                  then return [CS offset (totalSize - offset)]
                  else do
                    bytes <- LB.hGet h 4096
                    case LB.elemIndex '\n' bytes of
                      Just n -> do
                        chunks@(c:_) <- findChunks (off + n + 1)
                        let coff = chunkOffset c
                        return (CS offset (coff - offset):chunks)
                      Nothing -> findNewline (off + LB.length bytes)
          findNewline newOffset
    findChunks 0
{-- /snippet lineChunks --}

-- Ensure that a series of ChunkSpecs is contiguous and
-- non-overlapping.
prop_contig (CS o l:cs@(CS o' _:_)) | o + l == o' = prop_contig cs
                                    | otherwise = False
prop_contig _ = True
like image 890
Velvet Ghost Avatar asked Jan 30 '26 19:01

Velvet Ghost


1 Answers

Instead of

LineCount < test

use

LineCount test

Explanation: The call to getArgs in main takes the args from the command line. Using "<" would mean reading from stdin.

like image 124
ja. Avatar answered Feb 01 '26 12:02

ja.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!