Real World Haskell Walk

Table of Contents

  • Index
  • Repository
  • 1 Real World Haskell

    1.1 Overview

    At the time the book Real World Haskell was written (2008) the current Haskell version was 6.8 and many libraries, modules and implementations have changed what makes some codes incompatible with new Haskell versions. In order to solve this problem this section provides modified codes from the book and the ghci repl sessions in Haskell v7.10.2.

    Reference:

    • O'Sullivan, Bryan, John Goerzen, and Donald Bruce Stewart. Real world haskell: Code you can believe in. " O'Reilly Media, Inc.", 2008. Available at: http://book.realworldhaskell.org/read/.

    Install the necessary libraries with stack

    The necessary libraries to run the codes in this page can be installed with the following command outside a project directory (Directory without stack.yaml file):

    $  stack install mtl turtle aeson regex-posix network random
    

    Once the package manager is installed you can run:

    $ cd rwh
    
    $ ls
    ch03/  ch05/  ch07/  ch10/  ch14/  ch16/  ch18/  shell.nix
    ch04/  ch06/  ch08/  ch13/  ch15/  ch17/  ch27/
    
    #
    # Where is ghci ??
    $ stack exec -- which ghci
    /home/arch/.stack/programs/x86_64-linux/ghc-8.0.1/bin/ghci
    
    # Where is ghc ??
    $ stack exec -- which ghc
    /home/arch/.stack/programs/x86_64-linux/ghc-8.0.1/bin/ghc
    
    # Run ghci 
    $ stack ghci
    

    1.2 Chapter 3 - Defining Types, Streamlining Functions

    1.2.2 BookStore.hs

    1.2.2.1 Code
    data BookInfo = Book Int String [String]
                    deriving (Show)
    
    
    
    data MagazineInfo = Magazine Int String [String]
                        deriving (Show)
    
    
    type CustomerID = Int        --- Type synonym 
    type ReviewBody = String 
    
    data BookReview = BookReview BookInfo CustomerID String 
    
    data BetterReviw = BetterReviw BookInfo CustomerID ReviewBody 
    
    type BookRecord = (BookInfo, BookReview) 
    
    myInfo = Book 9780135072455 "Algebra of Programming"
                  ["Richard Bird", "Oege de Moor"]
    
    1.2.2.2 Running
    >>> :load "rwh/ch03/BookStore.hs"
    [1 of 1] Compiling Main             ( rwh/ch03/BookStore.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    >>> myInfo
    Book 9780135072455 "Algebra of Programming" ["Richard Bird","Oege de Moor"]
    >>> 
    >>> :type myInfo
    myInfo :: BookInfo
    >>> 
    
    >>> Book 0 "The Book of Imaginary Beings" ["Jorge Luis Borges"]
    Book 0 "The Book of Imaginary Beings" ["Jorge Luis Borges"]
    >>> 
    
    >>> :type Book 1 "Cosmicomics" ["Italo Calvino"]
    Book 1 "Cosmicomics" ["Italo Calvino"] :: BookInfo
    >>> 
    
    >>> let cities = Book 173 "Use of Weapons" ["Iain M. Banks"]
    
    >>> cities
    Book 173 "Use of Weapons" ["Iain M. Banks"]
    >>>
    

    1.2.3 BookStore2.hs

    1.2.3.1 Code

    rwh/ch03/BookStore2.hs

    type CardHolder = String
    type CardNumber = String
    type Address = [String]
    type CustomerID = Int
    
    data BillingInfo = CreditCard CardNumber CardHolder Address
                     | CashOnDelivery
                     | Invoice CustomerID
                       deriving (Show)
    
    1.2.3.2 Running
    >>> CreditCard "2901650221064486" "Thomas Gradgrind" ["Dickens", "England"]
    CreditCard "2901650221064486" "Thomas Gradgrind" ["Dickens","England"]
    >>> 
    >>> it
    CreditCard "2901650221064486" "Thomas Gradgrind" ["Dickens","England"]
    >>> 
    >>> :type it
    it :: BillingInfo
    >>> 
    
    >>> 
    Invoice 10
    >>> 
    
    >>> CashOnDelivery
    CashOnDelivery
    

    1.2.4 AlgebraicVector.hs

    1.2.4.1 Code

    rwh/ch03/AlgebraicVector.hs

    -- x and y coordinates or lengths.
    data Cartesian2D = Cartesian2D Double Double 
                       deriving (Eq, Show)
    
    --- Angle and distance (magnitude)
    data Polar2D = Polar2D Double Double  
                   deriving (Eq, Show)
    
    1.2.4.2 Running
    >>> Cartesian2D (sqrt 2) (sqrt 2)
    Cartesian2D 1.4142135623730951 1.4142135623730951
    >>> 
    >>> Polar2D (pi / 4) 2
    Polar2D 0.7853981633974483 2.0
    >>> 
    
    --- The (==)  operator requires its arguments 
    --- to have the same type
    ---
    >>> Cartesian2D (sqrt 2) (sqrt 2) == Polar2D (pi / 4) 2
    
    <interactive>:58:34:
        Couldn't match expected typeCartesian2D’
                    with actual typePolar2DIn the second argument of ‘(==)’, namely ‘Polar2D (pi / 4) 2’
        In the expression:
          Cartesian2D (sqrt 2) (sqrt 2) == Polar2D (pi / 4) 2
    >>>
    

    1.2.5 ShapeUnion.hs

    type Vector = (Double, Double)
    
    data  Shape = Circle Vector Double 
                  | Poly [Vector]
    

    1.2.6 add.hs

    1.2.6.1 Code
    sumList (x:xs) = x + sumList xs 
    sumList []     = 0
    
    1.2.6.2 Running
    >>> :load "rwh/ch03/add.hs"
    [1 of 1] Compiling Main             ( rwh/ch03/add.hs, interpreted )
    Ok, modules loaded: Main.
    >>>
    
    >>> sumList []
    0
    >>> sumList [1, 2, 3, 4, 5, 6]
    21
    >>>
    

    1.2.7 BookStore3.hs

    1.2.7.1 Code
    data BookInfo = Book Int String [String]
                    deriving (Show)
    
    
    
    data MagazineInfo = Magazine Int String [String]
                        deriving (Show)
    
    
    type CustomerID = Int        --- Type synonym 
    type ReviewBody = String 
    
    data BookReview = BookReview BookInfo CustomerID String 
    
    data BetterReviw = BetterReviw BookInfo CustomerID ReviewBody 
    
    type BookRecord = (BookInfo, BookReview) 
    
    bookID      (Book id title authors) = id
    bookTitle   (Book id title authors) = title
    bookAuthors (Book id title authors) = authors
    
    myInfo = Book 9780135072455 "Algebra of Programming"
                  ["Richard Bird", "Oege de Moor"]
    
    1.2.7.2 Running
    >>> :load "rwh/ch03/BookStore3.hs"
    [1 of 1] Compiling Main             ( rwh/ch03/BookStore3.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> bookID (Book 3 "Probability Theory" ["E.T.H. Jaynes"])
    3
    >>> bookTitle (Book 3 "Probability Theory" ["E.T.H. Jaynes"])
    "Probability Theory"
    >>> bookAuthors (Book 3 "Probability Theory" ["E.T.H. Jaynes"])
    ["E.T.H. Jaynes"]
    
    >>> :type bookID
    bookID :: BookInfo -> Int
    >>> :type bookTitle
    bookTitle :: BookInfo -> String
    >>> :type bookAuthors
    bookAuthors :: BookInfo -> [String]
    >>>
    

    1.2.8 BookStore4.hs

    1.2.8.1 Code
    data BookInfo = Book Int String [String]
                    deriving (Show)
    
    
    
    data MagazineInfo = Magazine Int String [String]
                        deriving (Show)
    
    
    type CustomerID = Int        --- Type synonym 
    type ReviewBody = String 
    type Address = [String] 
    
    data BookReview = BookReview BookInfo CustomerID String 
    
    data BetterReviw = BetterReviw BookInfo CustomerID ReviewBody 
    
    type BookRecord = (BookInfo, BookReview) 
    
    bookID      (Book id title authors) = id
    bookTitle   (Book id title authors) = title
    bookAuthors (Book id title authors) = authors
    
    
    data Customer = Customer {
          customerID      :: CustomerID
        , customerName    :: String
        , customerAddress :: Address
        } deriving (Show)
    
    myInfo = Book 9780135072455 "Algebra of Programming"
                  ["Richard Bird", "Oege de Moor"]
    
    customer1 = Customer 271828 "J.R. Hacker"
                ["255 Syntax Ct",
                 "Milpitas, CA 95134",
                 "USA"]
    
    customer2 = Customer {
                  customerID = 271828
                , customerAddress = ["1048576 Disk Drive",
                                     "Milpitas, CA 95134",
                                     "USA"]
                , customerName = "Jane Q. Citizen"
                }
    
    1.2.8.2 Running
    >>> :load "rwh/ch03/BookStore4.hs"
    [1 of 1] Compiling Main             ( rwh/ch03/BookStore4.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> customer1
    Customer {customerID = 271828, customerName = "J.R. Hacker", customerAddress = ["255 Syntax Ct","Milpitas, CA 95134","USA"]}
    >>> 
    
    >>> customer2
    Customer {customerID = 271828, customerName = "Jane Q. Citizen", customerAddress = ["1048576 Disk Drive","Milpitas, CA 95134","USA"]}
    >>> 
    
    >>> customerID customer1
    271828
    >>> customerID customer2
    271828
    >>> customerName customer1
    "J.R. Hacker"
    >>> customerAddress customer1
    ["255 Syntax Ct","Milpitas, CA 95134","USA"]
    >>> 
    
    >>> :t customerName
    customerName :: Customer -> String
    >>>  :t customerAddress
    customerAddress :: Customer -> Address
    >>> :t customerID
    customerID :: Customer -> CustomerID
    >>>
    

    1.3 Chapter 4 - Functional Programming

    1.3.2 InteractWith.hs

    1.3.2.1 Code

    Page 71: rwh/ch04/InteractWith.hs

    import System.Environment (getArgs)
    
    interactWith function inputFile outputFile = do
      input <- readFile inputFile
      writeFile outputFile (function input)
    
    main = mainWith myFunction
      where mainWith function = do
              args <- getArgs
              case args of
                [input,output] -> interactWith function input output
                _ -> putStrLn "error: exactly two arguments needed"
    
            -- replace "id" with the name of our function below
            myFunction = id
    
    1.3.2.2 Running
    $ ghc --make InteractWith 
    [1 of 1] Compiling Main             ( InteractWith.hs, InteractWith.o )
    Linking InteractWith ...
    
    $ ./InteractWith /etc/issue 
    error: exactly two arguments needed
    
    $ ./InteractWith /etc/issue /tmp/issue.out
    
    $ cat /tmp/issue.out 
    Arch Linux \r (\l)
    

    1.3.3 FixLines.hs

    1.3.3.1 Code

    Page 73: rwh/ch04/FixLines.hs

    import System.Environment (getArgs)
    
    interactWith function inputFile outputFile = do
      input <- readFile inputFile
      writeFile outputFile (function input)
    
    splitLines [] = []
    splitLines cs =
        let (pre, suf) = break isLineTerminator cs
        in  pre : case suf of 
                    ('\r':'\n':rest) -> splitLines rest
                    ('\r':rest)      -> splitLines rest
                    ('\n':rest)      -> splitLines rest
                    _                -> []
    
    isLineTerminator c = c == '\r' || c == '\n'
    
    fixLines :: String -> String
    fixLines input = unlines (splitLines input)
    
    
    main = mainWith myFunction
      where mainWith function = do
              args <- getArgs
              case args of
                [input,output] -> interactWith function input output
                _ -> putStrLn "error: exactly two arguments needed"
    
            -- replace "id" with the name of our function below
            myFunction = fixLines
    
    1.3.3.2 Running

    Running Interactive:

    >>> :load "rwh/ch04/FixLines.hs"
    [1 of 1] Compiling Main             ( rwh/ch04/FixLines.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> break odd [2, 4, 5, 6, 8]
    ([2,4],[5,6,8])
    
    >>> splitLines "line1\nline2\r\nline3\r\n"
    ["line1","line2","line3"]
    >>>
    

    Running in batch mode:

    $ curl -O http://www.gnu.org/licenses/gpl-3.0.txt
    
    $ file gpl-3.0.txt 
    gpl-3.0.txt: ASCII text
    
    #  dos2unix replacement 
    #
    $ awk 'sub("$", "\r")' gpl-3.0.txt  >  gpl-3.0.dos.txt
    
    $ file gpl-3.0.dos.txt 
    gpl-3.0.dos.txt: ASCII text, with CRLF line terminators
    
    $ ghc --make FixLines
    [1 of 1] Compiling Main             ( FixLines.hs, FixLines.o )
    Linking FixLines ...
    
    $ file FixLines
    FixLines: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically 
    linked, interpreter 
    /nix/store/n2wxp513rr00f6hr2dy0waqahns49dch-glibc-2.21/lib/ld-linux-x86-64.so.2, 
    for GNU/Linux 2.6.32, not stripped
    
    
    $ ./FixLines 
    error: exactly two arguments needed
    
    $ ./FixLines gpl-3.0.dos.txt gpl-3.0.unix.txt
    
    $ file gpl-3.0.unix.txt 
    gpl-3.0.unix.txt: ASCII text
    

    1.3.4 ch4.exercises.hs

    1.3.4.1 Code
    1. Write your own “safe” definitions of the standard partial list

    functions, but make sure they never fail. As a hint, you might want to consider using the following types:

    safeHead :: [a] -> Maybe a
    safeTail :: [a] -> Maybe [a]
    safeLast :: [a] -> Maybe a
    safeInit :: [a] -> Maybe [a]
    
    1. Write a function splitWith that acts similarly to words but takes a predicate and a

    list of any type, and then splits its input list on every element for which the predicate returns False:

    -- file: ch04/ch04.exercises.hs
    splitWith :: (a -> Bool) -> [a] -> [[a]]
    

    Page 84: rwh/ch04/ch04.exercises.hs

    safeHead :: [a] -> Maybe a
    safeHead (head:rest) = Just head
    safeHead []   = Nothing
    
    safeTail :: [a] -> Maybe [a]
    safeTail (head:rest) = Just rest 
    safeTail []          = Nothing 
    
    
    safeLast :: [a] -> Maybe a
    safeLast [x] = Just x
    safeLast (hd:tl) = safeLast tl 
    safeLast []  = Nothing
    
    safeInit :: [a] -> Maybe [a]
    safeInit (x:xs)      = Just $ init (x:xs)
    safeInit []          = Nothing 
    
    {-
    splitWith_aux :: (a -> Bool) -> [a] -> [[a]] -> [[a]]
    splitWith_aux fnp []     acc  = [] 
    splitWith_aux fnp (x:xs) acc  = if fnp x
                                    then  splitWith_aux fnp xs  
    
    -}
    
    1.3.4.2 Running
    >>> :load "rwh/ch04/ch04.exercises.hs"
    [1 of 1] Compiling Main             ( rwh/ch04/ch04.exercises.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> safeHead [1, 2, 3, 4]
    Just 1
    >>> safeHead [1 ..]
    Just 1
    >>> safeHead []
    Nothing
    >>> 
    
    >>> safeTail [1, 2, 3, 4]
    Just [2,3,4]
    >>> safeTail []
    Nothing
    >>> 
    
    >>> safeLast [1, 2, 3, 4]
    Just 4
    >>> safeLast [4]
    Just 4
    >>> safeLast []
    Nothing
    >>> 
    
    >>> safeInit []
    Nothing
    >>> safeInit [1, 2, 3, 4, 5]
    Just [1,2,3,4]
    >>> safeInit [1]
    Just []
    >>>
    

    1.3.5 IntParser.hs

    1.3.5.1 Code

    Page 86: rwh/ch04/IntParse.hs

    import Data.Char (digitToInt)
    
    asInt :: String -> Int 
    asInt xs = loop 0 xs
    
    loop :: Int -> String -> Int 
    loop acc [] = acc
    loop acc (x:xs) = let acc' = acc * 10 + digitToInt x
                      in loop acc' xs
    
    1.3.5.2 Running
    >>>:load rwh/ch04/IntParse.hs 
    [1 of 1] Compiling Main             ( rwh/ch04/IntParse.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> digitToInt '9'
    9
    >>> digitToInt 'a'
    10
    >>> digitToInt 'x'
     *** Exception: Char.digitToInt: not a digit 'x'
    >>> 
    
    >>> asInt "33"
    33
    >>> asInt "100"
    100
    >>> 
    
    >>> :t asInt
    asInt :: String -> Int
    >>> 
    
    >>> asInt "not a number"
     *** Exception: Char.digitToInt: not a digit 'n'
    >>>
    

    1.4 Chapter 5 - Writing a Library: Working with JSON Data

    1.4.2 SimpleJson1.hs

    1.4.2.1 Code

    Page: 112 rwh/ch05/SimpleJSON1.hs

    data JValue = JString String
                | JNumber Double 
                | JBool Bool
                | JNull 
                | JObject [(String, JValue)]
                | JArray  [JValue]
                deriving (Eq, Ord, Show)
    
    getString :: JValue -> Maybe String 
    getString (JString s) = Just s
    getString _           = Nothing
    
    getBool :: JValue -> Maybe Bool 
    getBool (JBool b) = Just b 
    getBool _         = Nothing 
    
    getNumber :: JValue -> Maybe Double
    getNumber (JNumber n) = Just n
    getNumber _           = Nothing
    
    getObject :: JValue -> Maybe [(String, JValue)]
    getObject js = case js of
                   JObject xs -> Just xs
                   _          -> Nothing 
    
    
    getArray :: JValue -> Maybe [JValue]
    getArray js = case js of
                  JArray xs -> Just xs
                  _         -> Nothing 
    
    isNull :: JValue -> Bool 
    isNull JNull = True
    isNull _     = False
    
    1.4.2.2 Running
    >>> :load "rwh/ch05/SimpleJSON1.hs"
    [1 of 1] Compiling Main             ( rwh/ch05/SimpleJSON1.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> JString "New Jersey"
    JString "New Jersey"
    >>> JNumber 3.1415
    JNumber 3.1415
    >>> JBool False
    JBool False
    >>> JBool True
    JBool True
    >>> 
    
    >>> getString (JNumber 3)
    Nothing
    >>> getString (JString "Utah" )
    Just "Utah"
    >>> 
    
    >>> getNumber (JNumber 10.2323)
    Just 10.2323
    >>> getNumber (JString "Texas")
    Nothing
    >>> 
    
    >>> getBool (JString "Alabama")
    Nothing
    >>> getBool (JBool False)
    Just False
    >>> 
    
    >>> getArray (JArray [JString "Alabama", JNumber 102.23, JBool True])
    Just [JString "Alabama",JNumber 102.23,JBool True]
    >>> 
    
    >>> getObject (JObject [("Alabama", JNumber 0), ("Texas", JNumber 2), ("Nevada", JNumber 20), ("New York", JNumber 40)])
    Just [("Alabama",JNumber 0.0),("Texas",JNumber 2.0),("Nevada",JNumber 20.0),("New York",JNumber 40.0)]
    >>> 
    
    >>> isNull JNull
    True
    >>> isNull (JString "California")
    False
    >>>
    

    1.4.3 SimpleJSon2.hs

    1.4.3.1 Code

    Page: 112 - File: rwh/ch05/SimpleJSON2.hs

    module SimpleJSON2
           (
             JValue (..)
             , getString
             , getInt
             , getDouble
             , getObject
             , getArray
             , isNull
           ) where 
    
    data JValue = JString String
                | JNumber Double 
                | JBool Bool
                | JNull 
                | JObject [(String, JValue)]
                | JArray  [JValue]
                deriving (Eq, Ord, Show)
    
    getString :: JValue -> Maybe String 
    getString (JString s) = Just s
    getString _           = Nothing
    
    getBool :: JValue -> Maybe Bool 
    getBool (JBool b) = Just b 
    getBool _         = Nothing 
    
    getNumber :: JValue -> Maybe Double
    getNumber (JNumber n) = Just n
    getNumber _           = Nothing
    
    getDouble = getNumber 
    
    getObject :: JValue -> Maybe [(String, JValue)]
    getObject js = case js of
                   JObject xs -> Just xs
                   _          -> Nothing 
    
    
    getArray :: JValue -> Maybe [JValue]
    getArray js = case js of
                  JArray xs -> Just xs
                  _         -> Nothing 
    
    getInt (JNumber n) = Just (truncate n)
    getInt _           = Nothing 
    
    isNull :: JValue -> Bool 
    isNull JNull = True
    

    File: rwh/ch05/Main.hs

    module Main (main) where
    
    import SimpleJSON2
    
    main = print (JObject [("foo", JNumber 1), ("bar", JBool False)])
    
    1.4.3.2 Running

    Compile the module:

    $ alias ghc=/nix/store/fcwp5nswfq4wm4hc3c9ij8rap9dr9p3q-ghc-7.10.2/bin/ghc
    
    # Generate only object code
    #
    $ ghc -c SimpleJSON2.hs
    
    
    $ file SimpleJSON2.o 
    SimpleJSON2.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
    
    $ file SimpleJSON2.hi
    SimpleJSON2.hi: data
    
    
    # Error:
    #
    $ ghc -o simple Main.hs SimpleJSON2.o
    Linking simple ...
    SimpleJSON2.o:(.data+0x0): multiple definition of `__stginit_SimpleJSON2'
    ./SimpleJSON2.o:(.data+0x0): first defined here
    SimpleJSON2.o:(.data+0x0): multiple definition of `SimpleJSON2_isNull_closure'
    ./SimpleJSON2.o:(.data+0x0): first defined here
    ...
    SimpleJSON2.o: In function `SimpleJSON2_JArray_info':
    (.text+0x24f0): multiple definition of `SimpleJSON2_JArray_static_info'
    ./SimpleJSON2.o:(.text+0x24f0): first defined here
    collect2: error: ld returned 1 exit status
    
    
    # Now it works 
    #
    $ ghc --make -o simple Main.hs
    Linking simple ...
    
    $ file simple 
    simple: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, 
    interpreter /nix/store/n2wxp513rr00f6hr2dy0waqahns49dch-glibc-2.21/lib/ld-linux-x86-64.so.2, 
    for GNU/Linux 2.6.32, not stripped
    
    $ ./simple 
    JObject [("foo",JNumber 1.0),("bar",JBool False)]
    

    1.4.4 PutJSON.hs

    1.4.4.1 Code

    Page: 116 rwh/ch05/PutJson.hs

    module PutJSON where
    
    import Data.List (intercalate)
    import SimpleJSON2
    
    renderJValue :: JValue -> String
    renderJValue (JString s)   = show s
    renderJValue (JNumber n)   = show n
    renderJValue (JBool True)  = "true"
    renderJValue (JBool False) = "false"
    renderJValue JNull         = "null"
    
    renderJValue (JObject o) = "{" ++ pairs o ++ "}"
      where pairs [] = ""
            pairs ps = intercalate ", " (map renderPair ps)
            renderPair (k,v)   = show k ++ ": " ++ renderJValue v
    
    renderJValue (JArray a) = "[" ++ values a ++ "]"
      where values [] = ""
            values vs = intercalate ", " (map renderJValue vs)
    
    -- Good Haskell style involves separating pure code from code that
    -- performs I/O. (Real World Haskell) 
    --
    putJValue :: JValue -> IO ()
    putJValue v = putStrLn (renderJValue v)
    
    1.4.4.2 Running
    
    

    1.4.5 PrettyJSON.hs

    Page: 119 rwh/ch05/PrettyJSON.hs

    renderJValue :: JValue -> Doc
    renderJValue (JBool True)  = text "true"
    renderJValue (JBool False) = text "false"
    renderJValue JNull         = text "null"
    renderJValue (JNumber num) = double num
    renderJValue (JString str) = string str
    

    1.5 Chapter 7 - Classic I/O in Haskell

    1.5.1 Location

    1.5.2 basicio.hs

    1.5.2.1 Code

    Page: 112 rwh/ch07/basicio.hs

    -- file: ch07/basicio.hs
    main = do
      putStrLn "Greetings! What is your name?"
      inpStr <- getLine
      putStrLn $ "Welcome to Haskell, " ++ inpStr ++ "!"
    
    1.5.2.2 Running
    [nix-shell:~/org/wiki/rwh/ch07]$ runhaskell basicio.hs 
    Greetings! What is your name?
    Julius Caesar
    Welcome to Haskell, Julius Caesar!
    

    1.5.3 callingpure.hs

    1.5.3.1 Code
    -- file: ch07/callingpure.hs
    name2reply :: String -> String
    name2reply name =
      "Pleased to meet you, " ++ name ++ ".\n" ++
      "Your name contains " ++ charcount ++ " characters."
      where charcount = show (length name)
    
    main :: IO ()
    main = do
      putStrLn "Greetings once again. What is your name?"
      inpStr <- getLine
      let outStr = name2reply inpStr
      putStrLn outStr
    
    1.5.3.2 Running
    [nix-shell:~/org/wiki/rwh/ch07]$ ghci
    GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
    >>> 
    >>> :load 
    basicio.hs      callingpure.hs
    >>> :load callingpure.hs 
    [1 of 1] Compiling Main             ( callingpure.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    >>> name2reply "john"
    "Pleased to meet you, john.\nYour name contains 4 characters."
    >>> name2reply "julius caesar"
    "Pleased to meet you, julius caesar.\nYour name contains 13 characters."
    >>> 
    
    >>> putStrLn (name2reply "Julis Caesar")
    Pleased to meet you, Julis Caesar.
    Your name contains 12 characters.
    >>> 
    
    >>> :t putStrLn (name2reply "Julis Caesar")
    putStrLn (name2reply "Julis Caesar") :: IO (
    

    1.5.4 toupper-imp.hs

    1.5.4.1 Code
    -- file: ch07/toupper-imp.hs
    import System.IO
    import Data.Char(toUpper)
    
    main :: IO ()
    main = do
      inh <- openFile "/etc/issue" ReadMode
      outh <- openFile "/tmp/issue.out" WriteMode
      mainloop inh outh
      hClose inh
      hClose outh
    
    mainloop :: Handle -> Handle -> IO ()
    mainloop inh outh =
      do ineof <- hIsEOF inh
         if ineof
           then return ()
           else do inpStr <- hGetLine inh
                   hPutStrLn outh (map toUpper inpStr)
                   mainloop inh outh
    
    1.5.4.2 Running
    [nix-shell:~/org/wiki/rwh/ch07]$ runhaskell toupper-imp.hs 
    
    [nix-shell:~/org/wiki/rwh/ch07]$ cat /etc/issue 
    Arch Linux \r (\l)
    
    [nix-shell:~/org/wiki/rwh/ch07]$ cat /tmp/issue.out 
    ARCH LINUX \R (\L)
    

    1.5.5 tempfile.hs

    1.5.5.1 Code

    Page 175 rwh/ch07/tempfile.hs

    The code tempfile.hs failed to run and had to be changed to run in Haskell 7.10.2.

    -- file: ch07/tempfile.hs
    import System.IO
    import System.Directory    (getTemporaryDirectory, removeFile)
    
    --import System.IO.Error     (catch)
    import Control.Exception   (catch, finally, IOException)
    
    -- The main entry point.  Work with a temp file in myAction.
    main :: IO ()
    main = withTempFile "mytemp.txt" myAction
    
    {- The guts of the program.  Called with the path and handle of a temporary
       file.  When this function exits, that file will be closed and deleted
       because myAction was called from withTempFile. -}
    myAction :: FilePath -> Handle -> IO ()
    myAction tempname temph = 
        do -- Start by displaying a greeting on the terminal
           putStrLn "Welcome to tempfile.hs"
           putStrLn $ "I have a temporary file at " ++ tempname
    
           -- Let's see what the initial position is
           pos <- hTell temph
           putStrLn $ "My initial position is " ++ show pos
    
           -- Now, write some data to the temporary file
           let tempdata = show [1..10]
           putStrLn $ "Writing one line containing " ++ 
                      show (length tempdata) ++ " bytes: " ++
                      tempdata
           hPutStrLn temph tempdata
    
           -- Get our new position.  This doesn't actually modify pos
           -- in memory, but makes the name "pos" correspond to a different 
           -- value for the remainder of the "do" block.
           pos <- hTell temph
           putStrLn $ "After writing, my new position is " ++ show pos
    
           -- Seek to the beginning of the file and display it
           putStrLn $ "The file content is: "
           hSeek temph AbsoluteSeek 0
    
           -- hGetContents performs a lazy read of the entire file
           c <- hGetContents temph
    
           -- Copy the file byte-for-byte to stdout, followed by \n
           putStrLn c
    
           -- Let's also display it as a Haskell literal
           putStrLn $ "Which could be expressed as this Haskell literal:"
           print c
    
    
    getTempdir :: IO String 
    getTempdir =   catch getTemporaryDirectory handler
      where
        handler :: IOException -> IO String
        handler = \ _ -> return "." 
    
    
    {- This function takes two parameters: a filename pattern and another
       function.  It will create a temporary file, and pass the name and Handle
       of that file to the given function.
    
       The temporary file is created with openTempFile.  The directory is the one
       indicated by getTemporaryDirectory, or, if the system has no notion of
       a temporary directory, "." is used.  The given pattern is passed to
       openTempFile.
    
       After the given function terminates, even if it terminates due to an
       exception, the Handle is closed and the file is deleted. -}
    withTempFile :: String -> (FilePath -> Handle -> IO a) -> IO a
    withTempFile pattern func =
        do -- The library ref says that getTemporaryDirectory may raise on
           -- exception on systems that have no notion of a temporary directory.
           -- So, we run getTemporaryDirectory under catch.  catch takes
           -- two functions: one to run, and a different one to run if the
           -- first raised an exception.  If getTemporaryDirectory raised an
           -- exception, just use "." (the current working directory).
    
           {- Note: It doesn't work anymore in Haskell 7.10.2 -}
           {- tempdir <- catch (getTemporaryDirectory) (\_ -> return ".") -}
    
           tempdir <- getTempdir
           (tempfile, temph) <- openTempFile tempdir pattern 
    
           -- Call (func tempfile temph) to perform the action on the temporary
           -- file.  finally takes two actions.  The first is the action to run.
           -- The second is an action to run after the first, regardless of
           -- whether the first action raised an exception.  This way, we ensure
           -- the temporary file is always deleted.  The return value from finally
           -- is the first action's return value.
           finally (func tempfile temph) 
                   (do hClose temph
                       removeFile tempfile)
    
    1.5.5.2 Running
    $ > runhaskell tempfile.hs 
    Welcome to tempfile.hs
    I have a temporary file at /run/user/1000/mytemp1804289383846930886.txt
    My initial position is 0
    Writing one line containing 22 bytes: [1,2,3,4,5,6,7,8,9,10]
    After writing, my new position is 23
    The file content is: 
    [1,2,3,4,5,6,7,8,9,10]
    
    Which could be expressed as this Haskell literal:
    "[1,2,3,4,5,6,7,8,9,10]\n"
    

    1.5.6 actions.hs

    1.5.6.1 Code

    IO actions can be passed as values, stored in data structures and passed to another IO actions. They won't do anything until invoked (called from the action main).

    -- file: ch07/actions.hs
    str2action :: String -> IO ()
    str2action input = putStrLn ("Data: " ++ input)
    
    list2actions :: [String] -> [IO ()]
    list2actions = map str2action
    
    numbers :: [Int]
    numbers = [1..10]
    
    strings :: [String]
    strings = map show numbers
    
    actions :: [IO ()]
    actions = list2actions strings
    
    printitall :: IO ()
    printitall = runall actions
    
    -- Take a list of actions, and execute each of them in turn.
    runall :: [IO ()] -> IO ()
    runall [] = return ()
    runall (firstelem:remainingelems) =
      do firstelem
         runall remainingelems
    
    main = do str2action "Start of the program"
              printitall
              str2action "Done!"
    
    1.5.6.2 Running
    $ > runhaskell actions.hs 
    Data: Start of the program
    Data: 1
    Data: 2
    Data: 3
    Data: 4
    Data: 5
    Data: 6
    Data: 7
    Data: 8
    Data: 9
    Data: 10
    Data: Done!
    $ >
    

    1.5.7 actions2.hs

    1.5.7.1 Code
    -- file: ch07/actions2.hs
    str2message :: String -> String
    str2message input = "Data: " ++ input
    
    str2action :: String -> IO ()
    str2action = putStrLn . str2message
    
    numbers :: [Int]
    numbers = [1..10]
    
    main = do str2action "Start of the program"
              mapM_ (str2action . show) numbers
              str2action "Done!"
    
    1.5.7.2 Running
    $ > runhaskell actions2.hs 
    Data: Start of the program
    Data: 1
    Data: 2
    Data: 3
    Data: 4
    Data: 5
    Data: 6
    Data: 7
    Data: 8
    Data: 9
    Data: 10
    Data: Done!
    

    1.5.8 basicio-nodo.hs

    1.5.8.1 Code

    Page 187 rwh/ch07/basicio-nodo.hs

    -- file: ch07/basicio-nodo.hs
    main =
      putStrLn "Greetings! What is your name?" >>
      getLine >>=
      (\inpStr -> putStrLn $ "Welcome to Haskell, " ++ inpStr ++ "!")
    
    1.5.8.2 Running
    $ > runhaskell basicio-nodo.hs 
    Greetings! What is your name?
    Julius Caesar
    Welcome to Haskell, Julius Caesar!
    

    1.5.9 return1.hs

    1.5.9.1 Code

    Page: 187 rwh/ch07/return1.hs

    -- file: ch07/return1.hs
    import Data.Char(toUpper)
    
    isGreen :: IO Bool
    isGreen =
      do putStrLn "Is green your favorite color?"
         inpStr <- getLine
         return ((toUpper . head $ inpStr) == 'Y')
    
    1.5.9.2 Running
    $ > ghci
    GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
    >>> 
    >>> :load return1.hs 
    [1 of 1] Compiling Main             ( return1.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    >>> :t isGreen 
    isGreen :: IO Bool
    >>> 
    >>> isGreen 
    Is green your favorite color?
    Y
    True
    >>> isGreen 
    Is green your favorite color?
    N
    False
    >>>
    

    1.5.10 return2.hs

    1.5.10.1 Code

    Page: 187 rwh/ch07/return2.hs

    -- file: ch07/return2.hs
    import Data.Char(toUpper)
    
    isYes :: String -> Bool
    isYes inpStr = (toUpper . head $ inpStr) == 'Y'
    
    isGreen :: IO Bool
    isGreen =
      do putStrLn "Is green your favorite color?"
         inpStr <- getLine
         return (isYes inpStr)
    
    1.5.10.2 Running
    >>> :load return2.hs 
    [1 of 1] Compiling Main             ( return2.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    >>> isGreen 
    Is green your favorite color?
    Y
    True
    >>> isGreen 
    Is green your favorite color?
    N
    False
    >>>
    

    1.5.11 return3.hs

    1.5.11.1 Code

    Page: 187 rwh/ch07/return3.hs

    -- file: ch07/return3.hs
    returnTest :: IO ()
    returnTest =
      do one <- return 1
         let two = 2
         putStrLn $ show (one + two)
    
    main :: IO ()  
    main = returnTest
    
    1.5.11.2 Running

    Running in batch mode:

    > runhaskell return3.hs 
    3
    >
    

    Running in ghci:

    $ > ghci
    GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
    >>> 
    >>> :load return3.hs 
    [1 of 1] Compiling Main             ( return3.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    >>> :t returnTest 
    returnTest :: IO ()
    >>> 
    >>> return
    return      returnTest
    >>> returnTest 
    3
    >>> :t returnTest 
    returnTest :: IO ()
    >>>
    

    1.6 Chapter 8 - Efficient File Processing, Regular Expressions, and Filename Matching

    1.6.2 ElfMagic.hs

    1.6.2.1 Code

    Page 194. rwh/ch08/ElfMagic.hs

    This function tests if the file is a Unix ELF executable that are recognized by its magic number which is a initial unique set of bytes that identify the file. Unlike Windows, Unix like OS recognizes the file formats by its magic number.

    See also:

    import qualified Data.ByteString.Lazy as L
    
    hasElfMagic :: L.ByteString -> Bool
    hasElfMagic content = L.take 4 content == elfMagic
        where elfMagic = L.pack [0x7f, 0x45, 0x4c, 0x46]
    
    isElfFile :: FilePath -> IO Bool
    isElfFile path = do
      content <- L.readFile path
      return (hasElfMagic content)
    
    1.6.2.2 Running
    GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
    >>> 
    
    >>> :load "ch08/ElfMagic.hs"
    [1 of 1] Compiling Main             ( ch08/ElfMagic.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    >>> :t hasElfMagic 
    hasElfMagic :: L.ByteString -> Bool
    >>> 
    >>> 
    
    
    >>> file <- L.readFile "/bin/sh"
    >>> :t file
    file :: L.ByteString
    >>>
    >>> hasElfMagic file
    True
    
    >>> L.take 10 file
    "\DELELF\STX\SOH\SOH\NUL\NUL\NUL"
    >>> 
    
    
    >>> file <- L.readFile "/etc/issue"
    >>> file
    "Arch Linux \\r (\\l)\n\n"
    >>> hasElfMagic file
    False
    >>> 
    
    >>> import qualified System.Directory as SD
    
    
    >>> :t isElfFile 
    isElfFile :: FilePath -> IO Bool
    >>> 
    >>> isElfFile "/bin/sh"
    True
    >>> isElfFile "/etc/fstab"
    False
    >>> 
    
    >>> SD.setCurrentDirectory "/bin"
    >>> SD.getCurrentDirectory 
    "/usr/bin"
    >>> 
    
    >>> files <- SD.getDirectoryContents "/bin"
    >>> :t files
    files :: [FilePath]
    >>> 
    
    >>> take 4 files
    [".","..","install-info","update-desktop-database"]
    >>> 
    
    >>> let flist = drop 2 files
    >>> take 4 flist
    ["install-info","update-desktop-database","libinput-list-devices","visudo"]
    >>> 
    
    >>> :t isElfFile 
    isElfFile :: FilePath -> IO Bool
    >>> 
    >>> :t filter
    filter :: (a -> Bool) -> [a] -> [a]
    >>> 
    
    >>> filter isElfFile flist
    
    <interactive>:83:8:
        Couldn't match typeIO Bool’ with ‘BoolExpected type: FilePath -> Bool
          Actual type: FilePath -> IO Bool
        In the first argument of ‘filter’, namely ‘isElfFile’
        In the expression: filter isElfFile flist
    >>> 
    
    >>> import Control.Monad (filterM)
    >>> :t filterM
    filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]
    >>> 
    
    >>> filesOnly <- filterM SD.doesFileExist flist
    >>> 
    
    >>> take 4 filesOnly 
    ["install-info","update-desktop-database","libinput-list-devices","visudo"]
    >>> 
    
    
    >>> filterM isElfFile  (take 30 filesOnly ) >>= mapM_ putStrLn 
    install-info
    update-desktop-database
    libinput-list-devices
    visudo
    suexec
    jack_wait
    dirname
    j2k_dump
    json-glib-format
    runlevel
    chacl
    eu-addr2line
    c++
    git
    gcov
    ionice
    lircd
    ...
    

    1.6.3 HighestClose.hs

    1.6.3.1 Code

    Page 196. rwh/ch08/HighestClose.hs

    import qualified Data.ByteString.Lazy.Char8 as L
    
    closing = readPrice . (!!4) . L.split ','
    
    readPrice :: L.ByteString -> Maybe Int
    readPrice str =
        case L.readInt str of
          Nothing             -> Nothing
          Just (dollars,rest) ->
            case L.readInt (L.tail rest) of
              Nothing           -> Nothing
              Just (cents,more) ->
                Just (dollars * 100 + cents)
    
    
    highestClose = maximum . (Nothing:) . map closing . L.lines
    
    highestCloseFrom path = do
        contents <- L.readFile path
        print (highestClose contents)
    

    File: rwh/ch08/prices.csv

    Date,Open,High,Low,Close,Volume,Adj Close
    2008-08-01,20.09,20.12,19.53,19.80,19777000,19.80
    2008-06-30,21.12,21.20,20.60,20.66,17173500,20.66
    2008-05-30,27.07,27.10,26.63,26.76,17754100,26.76
    2008-04-30,27.17,27.78,26.76,27.41,30597400,27.41
    
    1.6.3.2 Running
    >>> :load "rwh/ch08/HighestClose.hs" 
    [1 of 1] Compiling Main             ( rwh/ch08/HighestClose.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> highestCloseFrom "rwh/ch08/prices.csv"
    Just 2741
    >>> 
    
    >>> contents <- L.readFile "rwh/ch08/prices.csv" 
    
    >>> :t contents
    contents :: L.ByteString
    >>> 
    
    -- The output was formatted manually to fit in the 
    -- screen. 
    --
    >>> tail $ L.lines contents 
    ["2008-08-01,20.09,20.12,19.53,19.80,19777000,19.80",
    "2008-06-30,21.12,21.20,20.60,20.66,17173500,20.66",
    "2008-05-30,27.07,27.10,26.63,26.76,17754100,26.76",
    "2008-04-30,27.17,27.78,26.76,27.41,30597400,27.41"]
    >>> 
    
    >>> mapM_ (\ x -> putStrLn (show x)) $ tail $ L.lines contents 
    "2008-08-01,20.09,20.12,19.53,19.80,19777000,19.80"
    "2008-06-30,21.12,21.20,20.60,20.66,17173500,20.66"
    "2008-05-30,27.07,27.10,26.63,26.76,17754100,26.76"
    "2008-04-30,27.17,27.78,26.76,27.41,30597400,27.41"
    >>> 
    
    >>> mapM_ (\ x -> putStrLn (show x)) $ map (L.split ',')  $ tail $ L.lines contents 
    ["2008-08-01","20.09","20.12","19.53","19.80","19777000","19.80"]
    ["2008-06-30","21.12","21.20","20.60","20.66","17173500","20.66"]
    ["2008-05-30","27.07","27.10","26.63","26.76","17754100","26.76"]
    ["2008-04-30","27.17","27.78","26.76","27.41","30597400","27.41"]
    >>> 
    
    -- Adj close 
    --
    >>> map (!!4) $ map (L.split ',')  $ tail $ L.lines contents 
    ["19.80","20.66","26.76","27.41"]
    >>> 
    
    -- Optmization with function composition 
    -- 
    >>> map ( (!!4) . L.split ',')  $ tail $ L.lines contents 
    ["19.80","20.66","26.76","27.41"]
    >>> 
    
    >>> map ( (!!4) . L.split ',')  . tail . L.lines $ contents 
    ["19.80","20.66","26.76","27.41"]
    >>> 
    
    >>> let readLByteStringMaybe  = readMaybe . L.unpack 
    >>> :t readLByteStringMaybe
    readLByteStringMaybe :: Read a => L.ByteString -> Maybe a
    >>> 
    
    >>> map (\x -> readLByteStringMaybe x :: Maybe Double) $ map ( (!!4) . L.split ',')  . tail . L.lines $ contents 
    [Just 19.8,Just 20.66,Just 26.76,Just 27.41]
    >>> 
    
    >>> map ((\x -> readLByteStringMaybe x :: Maybe Double) . (!!4) . L.split ',')  . tail . L.lines $ contents 
    [Just 19.8,Just 20.66,Just 26.76,Just 27.41]
    >>> 
    
    -- If any number fail to be parsed the whole column will fail, 
    -- it will return Nothing 
    --
    >>> sequence $ map ((\x -> readLByteStringMaybe x :: Maybe Double) . (!!4) . L.split ',')  . tail . L.lines $ contents 
    Just [19.8,20.66,26.76,27.41]
    >>> 
    
    
    >>> fmap sum $ sequence $ map ((\x -> readLByteStringMaybe x :: Maybe Double) . (!!4) . L.split ',')  . tail . L.lines $ contents 
    Just 94.63
    >>> 
    
    >>> let parseColumnDouble n =  sequence . map ((\x -> readLByteStringMaybe x :: Maybe Double) . (!!n) . L.split ',')  . tail . L.lines 
    >>> 
    
    >>> :t parseColumnDouble
    parseColumnDouble :: Int -> L.ByteString -> Maybe [Double]
    >>> 
    
    --- Multiline function composition 
    --- 
    --- 
    ---
    :set +m  -- Allow multi line paste in the repl.
    
    :{
    let parseColumnDouble n =
          sequence
          . map ((\x -> readLByteStringMaybe x :: Maybe Double)
                 . (!!n)
                 . L.split ','
                )
                 . tail
                 . L.lines            
    :}
    
    
    >>> parseColumnDouble 0 contents 
    Nothing
    >>> parseColumnDouble 1 contents 
    Just [20.09,21.12,27.07,27.17]
    >>> parseColumnDouble 2 contents 
    Just [20.12,21.2,27.1,27.78]
    >>> parseColumnDouble 3 contents 
    Just [19.53,20.6,26.63,26.76]
    >>> parseColumnDouble 4 contents 
    Just [19.8,20.66,26.76,27.41]
    
    -- Year 
    --
    >>> map (!!0) $ map (L.split ',')  $ tail $ L.lines contents 
    ["2008-08-01","2008-06-30","2008-05-30","2008-04-30"]
    >>> 
    
    
    >>>
    

    1.7 Chapter 10 - Code Case Study: Parsing a Binary Data Format

    1.7.2 PNM.hs

    1.7.2.1 Code

    Page 246 - File: rwh/ch10/PNM.hs

    import qualified Data.ByteString.Lazy.Char8 as L8
    import qualified Data.ByteString.Lazy as L 
    import Data.Char (isSpace)
    
    data Greymap = Greymap {
          greyWidth  :: Int
        , greyHeight :: Int
        , greyMax    :: Int
        , greyData   :: L.ByteString
        } deriving (Eq)
    
    instance Show Greymap where
        show (Greymap w h m _) = "Greymap " ++ show w ++ "x" ++ show h ++
                                 " " ++ show m
    
    parseP5 :: L.ByteString -> Maybe (Greymap, L.ByteString)
    parseP5 s =
      case matchHeader (L8.pack "P5") s of
        Nothing -> Nothing
        Just s1 ->
          case getNat s1 of
            Nothing -> Nothing
            Just (width, s2) ->
              case getNat (L8.dropWhile isSpace s2) of
                Nothing -> Nothing
                Just (height, s3) ->
                  case getNat (L8.dropWhile isSpace s3) of
                    Nothing -> Nothing
                    Just (maxGrey, s4)
                      | maxGrey > 255 -> Nothing
                      | otherwise ->
                          case getBytes 1 s4 of
                            Nothing -> Nothing
                            Just (_, s5) ->
                              case getBytes (width * height) s5 of
                                Nothing -> Nothing
                                Just (bitmap, s6) ->
                                  Just (Greymap width height maxGrey bitmap, s6)
    
    
    matchHeader :: L.ByteString -> L.ByteString -> Maybe L.ByteString
    matchHeader prefix str
        | prefix `L8.isPrefixOf` str
            = Just (L8.dropWhile isSpace (L.drop (L.length prefix) str))
        | otherwise
            = Nothing
    
    
    getNat :: L.ByteString -> Maybe (Int, L.ByteString)
    getNat s = case L8.readInt s of
                 Nothing -> Nothing
                 Just (num,rest)
                     | num <= 0    -> Nothing
                     | otherwise -> Just (fromIntegral num, rest)
    
    getBytes :: Int -> L.ByteString -> Maybe (L.ByteString, L.ByteString)
    getBytes n str = let count           = fromIntegral n
                         both@(prefix,_) = L.splitAt count str
                     in if L.length prefix < count
                        then Nothing
                        else Just both
    
    1.7.2.2 Running
    >>> :load "rwh/ch10/PNM.hs"
    [1 of 1] Compiling Main             ( rwh/ch10/PNM.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> content <- L.readFile "rwh/ch10/bird.pgm"
    >>> 
    >>> L.take 30 content
    "P5\n321 481\n255\n4`oxyrxvuuuuuuu"
    >>> 
    >>> parseP5 content
    Just (Greymap 321x481 255,"")
    >>>
    

    Test files:

    bird.png

    1.7.3 Parse.hs

    1.7.3.1 Code

    Page 240; 250 - File: rwh/ch10/Parse.hs

    import qualified Data.ByteString.Lazy.Char8 as L8
    import qualified Data.ByteString.Lazy as L 
    import Data.Word (Word8)
    import Data.Int (Int64)
    import Data.Char (chr, isDigit, isSpace)
    import Control.Applicative ((<$>))
    
    data ParseState = ParseState {
          string :: L.ByteString
        , offset :: Int64           -- imported from Data.Int
        } deriving (Show)
    
    simpleParse :: ParseState -> (a, ParseState)
    simpleParse = undefined
    
    betterParse :: ParseState -> Either String (a, ParseState)
    betterParse = undefined
    
    newtype Parse a = Parse {
          runParse :: ParseState -> Either String (a, ParseState)
        }
    
    identity :: a -> Parse a
    identity a = Parse (\s -> Right (a, s))
    
    
    parse :: Parse a -> L.ByteString -> Either String a
    parse parser initState
        = case runParse parser (ParseState initState 0) of
            Left err          -> Left err
            Right (result, _) -> Right result
    
    modifyOffset :: ParseState -> Int64 -> ParseState
    modifyOffset initState newOffset =
        initState { offset = newOffset }
    
    getState :: Parse ParseState
    getState = Parse (\s -> Right (s, s))
    
    putState :: ParseState -> Parse ()
    putState s = Parse (\_ -> Right ((), s))
    
    bail :: String -> Parse a
    bail err = Parse $ \s -> Left $
               "byte offset " ++ show (offset s) ++ ": " ++ err
    
    (==>) :: Parse a -> (a -> Parse b) -> Parse b
    firstParser ==> secondParser  =  Parse chainedParser
      where chainedParser initState   =
              case runParse firstParser initState of
                Left errMessage ->
                    Left errMessage
                Right (firstResult, newState) ->
                    runParse (secondParser firstResult) newState
    
    
    parseByte :: Parse Word8
    parseByte =
        getState ==> \initState ->
        case L.uncons (string initState) of
          Nothing ->
              bail "no more input"
          Just (byte,remainder) ->
              putState newState ==> \_ ->
              identity byte
            where newState = initState { string = remainder,
                                         offset = newOffset }
                  newOffset = offset initState + 1
    
    instance Functor Parse where
        fmap f parser = parser ==> \result ->
                        identity (f result)
    
    w2c :: Word8 -> Char
    w2c = chr . fromIntegral
    
    parseChar :: Parse Char
    parseChar = w2c <$> parseByte
    
    peekByte :: Parse (Maybe Word8)
    peekByte = (fmap fst . L.uncons . string) <$> getState
    
    peekChar :: Parse (Maybe Char)
    peekChar = fmap w2c <$> peekByte
    
    
    parseWhile :: (Word8 -> Bool) -> Parse [Word8]
    parseWhile p = (fmap p <$> peekByte) ==> \mp ->
                   if mp == Just True
                   then parseByte ==> \b ->
                        (b:) <$> parseWhile p
                   else identity []
    
    parseWhileVerbose p =
        peekByte ==> \mc ->
        case mc of
          Nothing -> identity []
          Just c | p c ->
                     parseByte ==> \b ->
                     parseWhileVerbose p ==> \bs ->
                     identity (b:bs)
                 | otherwise ->
                     identity []
    
    parseWhileWith :: (Word8 -> a) -> (a -> Bool) -> Parse [a]
    parseWhileWith f p = fmap f <$> parseWhile (p . f)
    
    parseNat :: Parse Int
    parseNat = parseWhileWith w2c isDigit ==> \digits ->
               if null digits
               then bail "no more input"
               else let n = read digits
                    in if n < 0
                       then bail "integer overflow"
                       else identity n
    
    (==>&) :: Parse a -> Parse b -> Parse b
    p ==>& f = p ==> \_ -> f
    
    skipSpaces :: Parse ()
    skipSpaces = parseWhileWith w2c isSpace ==>& identity ()
    
    assert :: Bool -> String -> Parse ()
    assert True  _   = identity ()
    assert False err = bail err
    
    parseBytes :: Int -> Parse L.ByteString
    parseBytes n =
        getState ==> \st ->
        let n' = fromIntegral n
            (h, t) = L.splitAt n' (string st)
            st' = st { offset = offset st + L.length h, string = t }
        in putState st' ==>&
           assert (L.length h == n') "end of input" ==>&
           identity h
    
    1.7.3.2 Running
    >>> :load "rwh/ch10/Parse.hs"
    [1 of 1] Compiling Main             ( rwh/ch10/Parse.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> :t identity
    identity :: a -> Parse a
    >>> 
    >>> :info Parse
    newtype Parse a
      = Parse {runParse :: ParseState -> Either String (a, ParseState)}
        -- Defined at rwh/ch10/Parse.hs:18:1
    >>> 
    >>> 
    
    >>> :type parse (identity 1) undefined
    parse (identity 1) undefined :: Num a => Either String a
    >>> parse (identity 1) undefined
    Right 1
    >>> 
    >>> 
    >>> parse (identity "foo") undefined
    Right "foo"
    >>> 
    
    >>> let before = ParseState (L8.pack "foo") 0
    >>> before
    ParseState {string = "foo", offset = 0}
    >>> 
    
    >>> let after = modifyOffset before 3
    >>> after
    ParseState {string = "foo", offset = 3}
    >>>
    
    >>> :t L8.uncons
    L8.uncons :: L.ByteString -> Maybe (Char, L.ByteString)
    >>> 
    >>> :t L8.pack
    L8.pack :: [Char] -> L.ByteString
    >>> 
    >>> L8.pack "foo"
    "foo"
    >>> :t L8.pack "foo"
    L8.pack "foo" :: L.ByteString
    >>> 
    >>> L8.uncons $ L8.pack "foo"
    Just ('f',"oo")
    >>> L8.uncons $ L8.empty
    Nothing
    >>> 
    
    >>> :t runParse
    runParse :: Parse a -> ParseState -> Either String (a, ParseState)
    >>> 
    
    >>> :t parse
    parse :: Parse a -> L.ByteString -> Either String a
    
    -- 0xff = 255
    --
    >>> parse parseByte (L8.pack "\xff")
    Right 255
    >>> 
    
    >>> parse parseByte (L8.pack "\xa")
    Right 10
    >>> parse parseByte (L8.pack "\xb")
    Right 11
    >>> parse parseByte (L8.pack "")
    Left "byte offset 0: no more input"
    >>> 
    
    -- 0xfa = 16 * 15 + 10  = 250 decimal 
    --
    --
    >>> runParse parseByte  $ ParseState (L8.pack "\xfa") 0
    Right (250,ParseState {string = "", offset = 1})
    >>> 
    
    >>> runParse parseByte  $ ParseState (L8.pack "x9023") 1
    Right (120,ParseState {string = "9023", offset = 2})
    >>> 
    
    >>> runParse parseByte  $ ParseState (L8.pack "") 1
    Left "byte offset 1: no more input"
    >>> 
    
    >>> let input = L8.pack "foo"
    
    >>> :t input
    input :: L.ByteString
    >>> 
    >>> L.head input
    102
    >>> parse parseByte input
    Right 102
    >>> 
    >>> parse (id <$> parseByte) input
    Right 102
    >>> parse ((*2) <$> parseByte) input
    Right 204 
    >>> 
    
    >>> parse parseNat (L8.pack "10023 asdb")
    Right 10023
    >>> parse parseNat (L8.pack "sad10023 asdb")
    Left "byte offset 0: no more input"
    >>> 
    
    >>> parse parseChar (L8.pack "23")
    Right '2'
    >>> parse parseChar (L8.pack "")
    Left "byte offset 0: no more input"
    >>>
    

    1.7.4 TreeMap.sh

    1.7.4.1 Code

    Page: 244 - File: rwh/ch10/TreeMap.hs

    data Tree a =  Node (Tree a) (Tree a)
                  | Leaf a 
                  deriving (Show)
    
    treeLengths (Leaf s) = Leaf (length s)
    treeLengths (Node l r) = Node (treeLengths l) (treeLengths r)
    
    treeMap :: (a -> b) -> Tree a -> Tree b
    treeMap f (Leaf a)   = Leaf (f a)
    treeMap f (Node l r) = Node (treeMap f l) (treeMap f r)
    
    {-
    class Functor f where
        fmap :: (a -> b) -> f a -> f b
    -}
    instance Functor Tree where
        fmap = treeMap
    
    >>> fmap length (Node (Leaf "North Carolina") (Leaf "Puerto Rico"))
    Node (Leaf 14) (Leaf 11)
    >>> 
    
    >>> fmap id (Node (Leaf "a") (Leaf "b"))
    Node (Leaf "a") (Leaf "b")
    >>>
    
    1.7.4.2 Running
    >>> :load "rwh/ch10/TreeMap.hs"
    [1 of 1] Compiling Main             ( rwh/ch10/TreeMap.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> :info Tree
    data Tree a = Node (Tree a) (Tree a) | Leaf a
        -- Defined at rwh/ch10/TreeMap.hs:2:1
    instance Show a => Show (Tree a)
      -- Defined at rwh/ch10/TreeMap.hs:4:25
    >>> 
    
    >>> let tree = Node (Leaf "foo") (Node (Leaf "x") (Leaf "quux"))
    >>> :t tree
    tree :: Tree [Char]
    >>> 
    >>> treeLengths tree
    Node (Leaf 3) (Node (Leaf 1) (Leaf 4))
    >>> 
    >>> treeMap (odd . length) tree
    Node (Leaf True) (Node (Leaf True) (Leaf False))
    >>>
    

    1.8 Chapter 13 - Data Structures

    1.9 Chapter 14 - Monads

    1.9.1 Location

    1.9.2 Maybe.hs

    Page 367 - File: rwh/ch14/Maybe.hs

    -- file: ch14/Maybe.hs 
    
    data Maybe a = Nothing | Just a 
    
    instance Monad Maybe where
    
     -- chain 
     (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
     Just x    >>= fn = fn x
     Nothing   >>= fn = Nothing
    
     -- inject 
     return :: a ->  Maybe a
     return a = Just a 
    
     ---
     (>>) :: Maybe a -> Maybe b -> Maybe b
     Just _   >> mb = mb
     Nothing  >> mb = Nothing
    
     fail _ = Nothing 
    
    
    
    {- Function that executes the Maybe monad. If the computation 
       fails the third parameter is Nothing it returns the value n, 
       on  the other hand if the computation succeeds the third
       parameter is (Just x) it applies the function (a -> b) to the
       value x wrapped in the monad. 
    
    -}
    maybe :: b -> (a -> b ) -> Maybe a -> b
    maybe n _ Nothing  = n 
    maybe _ f (Just x) = f x
    

    1.9.3 MultiplyTo.hs

    1.9.3.1 Code

    Page: 343. File: rwh/ch14/MultiplyTo.hs

    guarded :: Bool -> [a] -> [a]
    guarded True  xs = xs
    guarded False _  = []
    
    multiplyTo :: Int -> [(Int, Int)]
    multiplyTo n = do 
      x <- [1..n]
      y <- [x..n]
      guarded (x * y == n) $
        return (x, y)
    
    1.9.3.2 Running
    >>> :load ch14/MultiplyTo.hs 
    [1 of 1] Compiling Main             ( ch14/MultiplyTo.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    >>> :t multiplyTo 
    multiplyTo :: Int -> [(Int, Int)]
    >>> 
    >>> multiplyTo 8
    [(1,8),(2,4)]
    >>> 
    >>> multiplyTo 100
    [(1,100),(2,50),(4,25),(5,20),(10,10)]
    >>> 
    >>> multiplyTo 891
    [(1,891),(3,297),(9,99),(11,81),(27,33)]
    >>> 
    >>> multiplyTo 1000
    [(1,1000),(2,500),(4,250),(5,200),(8,125),(10,100),(20,50),(25,40)]
    >>>
    

    1.9.4 SimpleState.hs

    1.9.4.1 Code

    Page: 347 - File: rwh/ch14/SimpleState.hs

    -- file: ch14/SimpleState.hs
    
    {-
       This function transforms one state into another yielding
       a result (output). The state monad is also called
       State Transformer Monad. 
    
       s            : Type of state
       a            : Type of state output
    
       s -> (a, s)  : State transformer function 
    
    
                :: SimpleState s a         :: SimpleState s a
    
                 |-------------|            |-------------|
    State 0      |             | State 1    |             | State 2
            ---> | a -> (a, s) | ------>    | a -> (a, s) | ------->            
                 |-------------|            |-------------|  
                      |                                |
                      |                                |
                     \ / Output 1: a                  \ /  Output 2: a
    
     -}
    type SimpleState s a = s -> (a, s)
    
    -- A type can be partially applied. The type constructor is:
    -- SimpleState s
    -- 
    type StringState a = SimpleState String a
    
    returnSt :: a -> SimpleState s a
    returnSt a = \s -> (a, s)
    
    returnAlt :: a -> SimpleState s a
    returnAlt a s = (a, s)
    
    bindSt :: (SimpleState s a) -> (a -> SimpleState s b) -> SimpleState s b
    bindSt m fn = \s -> let (a, s') = m s
                        in (fn a) s'
    
    {-
      A more readable version of bindSt 
    
       -- m == step
       -- k == makeStep
       -- s == oldState 
    -}
    bindAlt :: (SimpleState s a) -> (a -> SimpleState s b) -> SimpleState s b
    bindAlt step makeStep oldState =
      let (result, newState) = step oldState
      in (makeStep result) newState
    
    
    {- Get current state and returens it as result -}
    getSt :: SimpleState s s 
    getSt = \s -> (s, s)
    
    {- Set current state and ignore the current one. -}
    putSt :: s -> SimpleState s ()
    putSt s = \_ -> ((), s)
    
    1.9.4.2 Running
    >>> :load ch14/SimpleState.hs 
    [1 of 1] Compiling Main             ( ch14/SimpleState.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    >>> :set +m
    >>> 
    
    -- It can be pasted in Ghci 
    --
    -- The current state is n, the next state is 
    -- n + 1, the state output is 2 * n 
    --
    
    {-
           |---------|        |----------|       |----------|     |---------|
       --->| stateFn |---->   | stateFn  |-----> | stateFn  |---->| stateFn |--> 
      1    |---------|    2   |----------|   3   |----------| 4   |---------|   5 ...
                |                   |                 |                | 
                |                   |                 |                |
               \ /                 \ /               \ /              \ /
               output = 2          output = 4       output = 6        output = 8
    -}
    
    
    :{
    
    let stateFn :: SimpleState Int Int 
        stateFn n = (2 * n, n + 1)
    :}
    
    >>> :t stateFn 
    stateFn :: SimpleState Int Int
    >>> 
    
    >>> stateFn 1
    (2,2)
    >>> stateFn 2
    (4,3)
    >>> stateFn 3
    (6,4)
    >>> stateFn 4
    (8,5)
    >>> stateFn 5
    (10,6)
    >>> stateFn 6
    (12,7)
    >>> 
    
    
    >>> :t returnSt 10
    returnSt 10 :: Num a => SimpleState s a
    >>> 
    >>> (returnSt 10) 3
    (10,3)
    >>> (returnSt 20) 4
    (20,4)
    >>> (returnSt 12) 5
    (12,5)
    >>> 
    
    >>> getSt 3
    (3,3)
    >>> 
    >>> getSt 10
    (10,10)
    >>> 
    
    >>> (putSt 3) 4
    ((),3)
    >>> (putSt 3) 4
    ((),3)
    >>> (putSt 3) 5
    ((),3)
    >>> (putSt 3) 10
    ((),3)
    >>>
    

    1.9.5 State.hs

    1.9.5.1 Code

    Page: 349 - File: rwh/ch14/State.hs

    {-
      Applies a state transformer to a state and returns a new state
      yielding a result. 
    
      runState :: State s a -> s -> (a, s)
    
     -}
    newtype State s a = State { runState :: s -> (a, s)  }
    
    
    returnState :: a -> State s a
    returnState a = State ( \s -> (a, s) )
    
    bindState :: State s a -> (a -> State s b) -> State s b
    bindState m fn = State $ \s -> let (a, s') = runState m s
                                   in runState (fn a) s'
    
    -- evalState : Returns only the result, throwing away the final state
    --
    evalState :: State s a -> s -> a
    evalState fn s = fst (runState fn s)
    
    -- execState : Throws the result away, returning only the final state
    execState :: State s a -> s -> s
    execState fn s = snd (runState fn s)
    
    
    get :: State s s
    get = State (\s -> (s, s))
    
    put :: s -> State s ()
    put s = State (\ _ -> ((), s))
    
    {- State Monad Evaluation Functions -}
    
    -- runState : Returns both the result and the final state
    
    
    
    {- State Monad Evaluation Functions -}
    
    -- runState : Returns both the result and the final state
    
    
    
    {-
       Applies a function to the result of the
       state transformer (state monad) application
       keeping the current state. 
    
    -}
    instance Functor (State s) where
    
      {- fmap :: (a -> b) -> F a -> F b -}
      --
      -- fmap :: (a -> b) -> State s a -> State s b
      fmap f fns =
        State $ \oldState -> let (output, newState) = runState fns oldState
                             in (f output, newState)
    
    
    
    instance Applicative (State s) where
    
      pure = returnState
    
      --
      -- (<*>) :: State s (a -> b) -> State s a -> State s b
      --
      -- fsa :: State s a
      --
      -- fn :: State s (a -> b)
      --
      -- output :: a 
      -- newState :: s
      --
      -- f_a_to_b :: a -> b
      -- newState' :: s
      --
      fn <*> fsa = State $ \ oldState ->
        let (output, newState) = runState fsa oldState in
        let (f_a_to_b, newState') = runState fn newState in
        (f_a_to_b output, newState')
    
    
    
    
    instance Monad (State s) where
    
      -- return :: a -> State s a 
      --
      return a = State $ \s ->  (a, s)   
    
      -- (>>=) :: State s a -> (a -> State s b) -> State s b
      --
      -- StateFn    :: State s a
      --
      -- stateMaker :: a -> State s b 
      --
      -- result     :: a
      --
      -- newState   :: s 
      --
      --  
      --
      stateFn >>= stateMaker  =
    
        State $ \oldState -> let (result, newState) = runState stateFn oldState
                             in  runState (stateMaker result) newState
    
    1.9.5.2 Running
    >>> :load ch14/State.hs 
    [1 of 1] Compiling Main             ( ch14/State.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    >>> :set +m
    >>> 
    
    >>> let stateFn1 = State (\x -> (2 * x, x + 1)) :: State Int Int
    >>> :t stateFn1 
    stateFn1 :: State Int Int
    >>> 
    
    
    >>> runState stateFn1 0
    (0,1)
    >>> runState stateFn1 1
    (2,2)
    >>> runState stateFn1 2
    (4,3)
    >>> runState stateFn1 3
    (6,4)
    >>> runState stateFn1 4
    (8,5)
    >>> runState stateFn1 5
    (10,6)
    >>> 
    
    >>> runState (fmap (*2) stateFn1) 0
    (0,1)
    >>> runState (fmap (*2) stateFn1) 1
    (4,2)
    >>> runState (fmap (*2) stateFn1) 2
    (8,3)
    >>> runState (fmap (*2) stateFn1) 3
    (12,4)
    >>> runState (fmap (*2) stateFn1) 4
    (16,5)
    >>> runState (fmap (*2) stateFn1) 5
    (20,6)
    >>> 
    
    
    {- stateFn in monadic notation
       This block can be copied in the repl.
    -}
    
    :{
    let stateFn2 :: State Int Int
        stateFn2 = do
          sc <- get
          put (sc + 1)
          return $ 2 * sc
    
    :}
    
    >>> :t stateFn2
    stateFn2 :: State Int Int
    >>> 
    >>> runState stateFn2 0
    (0,1)
    >>> runState stateFn2 1
    (2,2)
    >>> runState stateFn2 2
    (4,3)
    >>> runState stateFn2 3
    (6,4)
    >>> runState stateFn2 4
    (8,5)
    >>> runState stateFn2 5
    (10,6)
    >>> 
    
    {- stateFn3   -}
    
    :{
    let stateFn3 :: State Int Int
        stateFn3 =
          get          >>= \ sc ->
          put (sc + 1) >>= \_   ->
          return (2 * sc)
    
    :}
    
    >>> runState stateFn3 0
    (0,1)
    >>> runState stateFn3 1
    (2,2)
    >>> runState stateFn3 2
    (4,3)
    >>> runState stateFn3 3
    (6,4)
    >>> runState stateFn3 4
    (8,5)
    >>> runState stateFn3 5
    (10,6)
    >>> 
    
    >>> runState (replicateM 10 stateFn3) 0
    ([18,16,14,12,10,8,6,4,2,0],10)
    >>> 
    >>> runState (replicateM 0 stateFn3) 0
    ([],0)
    >>> runState (replicateM 1 stateFn3) 0
    ([0],1)
    >>> runState (replicateM 2 stateFn3) 0
    ([2,0],2)
    >>> runState (replicateM 3 stateFn3) 0
    ([4,2,0],3)
    >>> runState (replicateM 4 stateFn3) 0
    ([6,4,2,0],4)
    >>> runState (replicateM 10 stateFn3) 0
    ([18,16,14,12,10,8,6,4,2,0],10)
    >>> 
    
    
    >>> runState ((\x -> x + 3) <$>  stateFn3) 0
    (3,1)
    >>> runState ((\x -> x + 3) <$>  stateFn3) 1
    (5,2)
    >>> runState ((\x -> x + 3) <$>  stateFn3) 2
    (7,3)
    >>> runState ((\x -> x + 3) <$>  stateFn3) 3
    (9,4)
    >>> runState ((\x -> x + 3) <$>  stateFn3) 4
    (11,5)
    >>>
    

    1.10 FAILED Chapter 27 - Sockets and Syslog

    1.10.2 Requirements

    1.10.3 UDP Syslog Server and Client

    1.10.3.1 Code
    1. syslogclient.hs

      Page 611 - File: rwh/ch27/syslogclient.hs

      -- file: ch27/syslogclient.hs
      import Data.Bits
      import Network.Socket
      import Network.BSD
      import Data.List
      import SyslogTypes
      
      
      data SyslogHandle = 
          SyslogHandle {slSocket :: Socket,
                        slProgram :: String,
                        slAddress :: SockAddr}
      
      openlog :: HostName             -- ^ Remote hostname, or localhost
              -> String               -- ^ Port number or name; 514 is default
              -> String               -- ^ Name to log under
              -> IO SyslogHandle      -- ^ Handle to use for logging
      
      openlog hostname port progname =
          do -- Look up the hostname and port.  Either raises an exception
             -- or returns a nonempty list.  First element in that list
             -- is supposed to be the best option.
             addrinfos <- getAddrInfo Nothing (Just hostname) (Just port)
             let serveraddr = head addrinfos
      
             -- Establish a socket for communication
             sock <- socket (addrFamily serveraddr) Datagram defaultProtocol
      
             -- Save off the socket, program name, and server address in a handle
             return $ SyslogHandle sock progname (addrAddress serveraddr)
      
      
      syslog :: SyslogHandle -> Facility -> Priority -> String -> IO ()
      syslog syslogh fac pri msg =
          sendstr sendmsg
          where code = makeCode fac pri
                sendmsg = "<" ++ show code ++ ">" ++ (slProgram syslogh) ++
                          ": " ++ msg
                -- Send until everything is done
                sendstr :: String -> IO ()
                sendstr [] = return ()
                sendstr omsg = do sent <- sendTo (slSocket syslogh) omsg
                                          (slAddress syslogh)
                                  sendstr (genericDrop sent omsg)
      
      
      closelog :: SyslogHandle -> IO ()
      closelog syslogh = sClose (slSocket syslogh)
      
      
      {- | Convert a facility and a priority into a syslog code -}
      makeCode :: Facility -> Priority -> Int
      makeCode fac pri =
          let faccode = codeOfFac fac
              pricode = fromEnum pri 
              in
                (faccode `shiftL` 3) .|. pricode
      
    2. SystlogTypes.hs

      File: rwh/ch27/SyslogTypes.hs

      module SyslogTypes where
      {- | Priorities define how important a log message is. -}
      
      data Priority = 
                  DEBUG                   -- ^ Debug messages
                | INFO                    -- ^ Information
                | NOTICE                  -- ^ Normal runtime conditions
                | WARNING                 -- ^ General Warnings
                | ERROR                   -- ^ General Errors
                | CRITICAL                -- ^ Severe situations
                | ALERT                   -- ^ Take immediate action
                | EMERGENCY               -- ^ System is unusable
                          deriving (Eq, Ord, Show, Read, Enum)
      
      {- | Facilities are used by the system to determine where messages
      are sent. -}
      
      data Facility = 
                    KERN                      -- ^ Kernel messages
                    | USER                    -- ^ General userland messages
                    | MAIL                    -- ^ E-Mail system
                    | DAEMON                  -- ^ Daemon (server process) messages
                    | AUTH                    -- ^ Authentication or security messages
                    | SYSLOG                  -- ^ Internal syslog messages
                    | LPR                     -- ^ Printer messages
                    | NEWS                    -- ^ Usenet news
                    | UUCP                    -- ^ UUCP messages
                    | CRON                    -- ^ Cron messages
                    | AUTHPRIV                -- ^ Private authentication messages
                    | FTP                     -- ^ FTP messages
                    | LOCAL0                  
                    | LOCAL1
                    | LOCAL2
                    | LOCAL3
                    | LOCAL4
                    | LOCAL5
                    | LOCAL6
                    | LOCAL7
                      deriving (Eq, Show, Read)
      
      facToCode = [ 
                             (KERN, 0),
                             (USER, 1),
                             (MAIL, 2),
                             (DAEMON, 3),
                             (AUTH, 4),
                             (SYSLOG, 5),
                             (LPR, 6),
                             (NEWS, 7),
                             (UUCP, 8),
                             (CRON, 9),
                             (AUTHPRIV, 10),
                             (FTP, 11),
                             (LOCAL0, 16),
                             (LOCAL1, 17),
                             (LOCAL2, 18),
                             (LOCAL3, 19),
                             (LOCAL4, 20),
                             (LOCAL5, 21),
                             (LOCAL6, 22),
                             (LOCAL7, 23)
      
                 ]
      
      
      codeToFac = map (\(x, y) -> (y, x)) facToCode
      
      
      {- | We can't use enum here because the numbering is discontiguous -}
      codeOfFac :: Facility -> Int
      codeOfFac f = case lookup f facToCode of
                      Just x -> x
                      _ -> error $ "Internal error in codeOfFac"
      
      facOfCode :: Int -> Facility
      facOfCode f = case lookup f codeToFac of
                      Just x -> x
                      _ -> error $ "Invalid code in facOfCode"
      
    3. syslogserver.hs

      File: rwh/ch27/syslogserver.hs

      import Data.Bits
      import Network.Socket
      import Network.BSD
      import Data.List
      
      type HandlerFunc = SockAddr -> String -> IO ()
      
      serveLog :: String              -- ^ Port number or name; 514 is default
               -> HandlerFunc         -- ^ Function to handle incoming messages
               -> IO ()
      
      serveLog port handlerfunc = withSocketsDo $
          do -- Look up the port.  Either raises an exception or returns
             -- a nonempty list.  
             addrinfos <- getAddrInfo 
                          (Just (defaultHints {addrFlags = [AI_PASSIVE]}))
                          Nothing (Just port)
             let serveraddr = head addrinfos
             -- Create a socket
             sock <- socket (addrFamily serveraddr) Datagram defaultProtocol
             -- Bind it to the address we're listening to
             bindSocket sock (addrAddress serveraddr)
             -- Loop forever processing incoming data.  Ctrl-C to abort.
             procMessages sock
          where procMessages sock =
                    do -- Receive one UDP packet, maximum length 1024 bytes,
                       -- and save its content into msg and its source
                       -- IP and port into addr
                       (msg, _, addr) <- recvFrom sock 1024
                       -- Handle it
                       handlerfunc addr msg
                       -- And process more messages
                       procMessages sock
      
      -- A simple handler that prints incoming packets
      plainHandler :: HandlerFunc
      plainHandler addr msg = 
          putStrLn $ "From " ++ show addr ++ ": " ++ msg
      
    1.10.3.2 Running:

    This app loaded in GHCI without any errors, however it didn't work the server printed nothing. It was tested in GHC/GHCI Version 7.10.2, Arch Linux 64 bits, Linux version 4.4.3-1-ARCH.

    1.10.4 TCP Syslog Server and Client

    1.10.4.1 Code
    1. syslogtcpserver.hs

      Page 617 - File: rwh/ch27/syslogtcpserver.hs

      import Data.Bits
      import Network.Socket
      import Network.BSD
      import Data.List
      import Control.Concurrent
      import Control.Concurrent.MVar
      import System.IO
      
      
      type HandlerFunc = SockAddr -> String -> IO ()
      
      serveLog :: String              -- ^ Port number or name; 514 is default
               -> HandlerFunc         -- ^ Function to handle incoming messages
               -> IO ()
      serveLog port handlerfunc = withSocketsDo $
          do -- Look up the port.  Either raises an exception or returns
             -- a nonempty list.  
             addrinfos <- getAddrInfo 
                          (Just (defaultHints {addrFlags = [AI_PASSIVE]}))
                          Nothing (Just port)
             let serveraddr = head addrinfos
             -- Create a socket
             sock <- socket (addrFamily serveraddr) Stream defaultProtocol
             -- Bind it to the address we're listening to
             bindSocket sock (addrAddress serveraddr)
             -- Start listening for connection requests.  Maximum queue size
             -- of 5 connection requests waiting to be accepted.
             listen sock 5
             -- Create a lock to use for synchronizing access to the handler
             lock <- newMVar ()
             -- Loop forever waiting for connections.  Ctrl-C to abort.
             procRequests lock sock
          where
                -- | Process incoming connection requests
                procRequests :: MVar () -> Socket -> IO ()
                procRequests lock mastersock = 
                    do (connsock, clientaddr) <- accept mastersock
                       handle lock clientaddr
                          "syslogtcpserver.hs: client connnected"
                       forkIO $ procMessages lock connsock clientaddr
                       procRequests lock mastersock
      
                -- | Process incoming messages
                procMessages :: MVar () -> Socket -> SockAddr -> IO ()
                procMessages lock connsock clientaddr =
                    do connhdl <- socketToHandle connsock ReadMode
                       hSetBuffering connhdl LineBuffering
                       messages <- hGetContents connhdl
                       mapM_ (handle lock clientaddr) (lines messages)
                       hClose connhdl
                       handle lock clientaddr 
                          "syslogtcpserver.hs: client disconnected"
                -- Lock the handler before passing data to it.
                handle :: MVar () -> HandlerFunc
                -- This type is the same as
                -- handle :: MVar () -> SockAddr -> String -> IO ()
                handle lock clientaddr msg =
                    withMVar lock 
                       (\a -> handlerfunc clientaddr msg >> return a)
      
      -- A simple handler that prints incoming packets
      plainHandler :: HandlerFunc
      plainHandler addr msg = 
          putStrLn $ "From " ++ show addr ++ ": " ++ msg
      

      File: rwh/ch27/syslogtcpserver.hs

    2. sylogtcpclient.hs
      -- file: ch27/syslogtcpclient.hs
      import Data.Bits
      import Network.Socket
      import Network.BSD
      import Data.List
      import SyslogTypes
      import System.IO
      
      data SyslogHandle = 
          SyslogHandle {slHandle :: Handle,
                        slProgram :: String}
      
      openlog :: HostName             -- ^ Remote hostname, or localhost
              -> String               -- ^ Port number or name; 514 is default
              -> String               -- ^ Name to log under
              -> IO SyslogHandle      -- ^ Handle to use for logging
      
      openlog hostname port progname =
          do -- Look up the hostname and port.  Either raises an exception
             -- or returns a nonempty list.  First element in that list
             -- is supposed to be the best option.
             addrinfos <- getAddrInfo Nothing (Just hostname) (Just port)
             let serveraddr = head addrinfos
      
             -- Establish a socket for communication
             sock <- socket (addrFamily serveraddr) Stream defaultProtocol
      
             -- Mark the socket for keep-alive handling since it may be idle
             -- for long periods of time
             setSocketOption sock KeepAlive 1
      
             -- Connect to server
             connect sock (addrAddress serveraddr)
      
             -- Make a Handle out of it for convenience
             h <- socketToHandle sock WriteMode
      
             -- We're going to set buffering to BlockBuffering and then
             -- explicitly call hFlush after each message, below, so that
             -- messages get logged immediately
             hSetBuffering h (BlockBuffering Nothing)
      
             -- Save off the socket, program name, and server address in a handle
             return $ SyslogHandle h progname
      
      
      
      syslog :: SyslogHandle -> Facility -> Priority -> String -> IO ()
      syslog syslogh fac pri msg =
          do hPutStrLn (slHandle syslogh) sendmsg
             -- Make sure that we send data immediately
             hFlush (slHandle syslogh)
          where code = makeCode fac pri
                sendmsg = "<" ++ show code ++ ">" ++ (slProgram syslogh) ++
                          ": " ++ msg
      
      closelog :: SyslogHandle -> IO ()
      closelog syslogh = hClose (slHandle syslogh)
      
      {- | Convert a facility and a priority into a syslog code -}
      makeCode :: Facility -> Priority -> Int
      makeCode fac pri =
          let faccode = codeOfFac fac
              pricode = fromEnum pri 
              in
                (faccode `shiftL` 3) .|. pricode
      
    1.10.4.2 Running

    Running: syslogtcpserver with telnet as client:

    >>> :load "syslogtcpserver.hs"
    [1 of 1] Compiling Main             ( syslogtcpserver.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    
    --  Open another terminal window 
    --  and enter: 
    --
    --  $ telnet localhost 10514 
    --
    --  and type the log messages. 
    --
    --
    >>> serveLog "10514" plainHandler
    From 127.0.0.1:49570: syslogtcpserver.hs: client connnected
    From 127.0.0.1:49570: Test message - Fatal kernel error
    From 127.0.0.1:49570: Application server running OK.
    

    Running: syslogtcpserver with syslogclient:

    syslogserver:

    >>> :load "syslogtcpserver.hs"
    [1 of 1] Compiling Main             ( syslogtcpserver.hs, interpreted )
    Ok, modules loaded: Main.
    >>> 
    >>> serveLog "10514" plainHandler
    

    syslogclient:

    >>> 
    >>> :load sylogtcpclient.hs 
    [1 of 2] Compiling SyslogTypes      ( SyslogTypes.hs, interpreted )
    [2 of 2] Compiling Main             ( sylogtcpclient.hs, interpreted )
    Ok, modules loaded: SyslogTypes, Main.
    >>> 
    
    >>> openlog "localhost" "10514" "tcptest"
     *** Exception: connect: does not exist (Connection refused)
    >>> 
    
    >>> sl <- openlog "localhost" "1514" "tcptest"
     *** Exception: connect: does not exist (Connection refused)
    >>>
    

    Author: nobody

    Created: 2018-05-07 Mon 10:11

    Emacs 25.3.1 (Org mode 8.2.10)

    Validate