Tips And Tricks

Table of Contents

  • Index
  • Repository
  • 1 Tips and Tricks

    1.1 Overview

    It is a collection of many tips and tricks for Haskell development.

    1.2 Configuration files in Haskell Syntax

    Configuration files can be written in Haskell since it provides a parser (readMaybe) that can read arbitrary data structures defined as instance of show and read type classes.

    Example:

    File: serverConf.hs

    import Text.Read (readMaybe)
    import Text.Printf (printf)
    
    type Password = String
    type Username = String     
    
    data ServerAuth = AuthEmpty
                    | AuthBasic Username Password
                    deriving (Eq, Read, Show)
    
    data ServerConf = ServerConf
          {
          serverHost     :: String      
         ,serverPort     :: Int
         ,serverAssets   :: String
         ,serverSecurity :: ServerAuth
          } deriving (Eq, Read, Show)
    
    
    readConfig1 :: String -> Maybe ServerConf
    readConfig1 = readMaybe 
    
    readConfig2 :: String -> Maybe ServerConf
    readConfig2 str = readMaybe str 
    
    
    readConfigFile :: String -> IO (Maybe ServerConf)
    readConfigFile configFile = readConfig1 <$> readFile configFile -- Or fmap readConfig1 (readFile configFile)
    
    
    runServer :: ServerConf -> IO ()
    runServer config = do printf "Running server at port %d, listening host %s\n" (serverPort config) (serverHost config) 
                          putStrLn $ "Server Authentication = " ++ show (serverSecurity config)
    
    
    runServerConfigFile :: String -> IO ()
    runServerConfigFile configFile = do
      config <- readConfigFile configFile
      case config of
        Just conf -> runServer conf
        Nothing   -> putStrLn "Error: I can't read the server configuration file"
    
    
    
    config1 = ServerConf "0.0.0.0" 8080 "/var/www" AuthEmpty
    
    config2 = ServerConf { serverHost = "0.0.0.0",
                           serverPort = 9090,
                           serverAssets = "/home/arch/www",
                           serverSecurity = AuthBasic "gh0st" "xmkg3p98sfawgav"
                         }
    

    Configuration File: server.conf

    ServerConf {
      serverHost = "127.0.0.1" 
     ,serverPort = 80 
     ,serverAssets = "/var/www"
     ,serverSecurity = AuthBasic "admin" "xjwxfm2pcfg56hga" 
    }
    

    Load the file in the REPL:

    > :load /tmp/serverconf.hs 
    [1 of 1] Compiling Main             ( /tmp/serverconf.hs, interpreted )
    Ok, modules loaded: Main.
    > 
    > :t ServerConf 
    ServerConf :: String -> Int -> String -> ServerAuth -> ServerConf
    > 
    
    
    > config1
    ServerConf {serverHost = "0.0.0.0", serverPort = 8080, serverAssets = "/var/www", serverSecurity = AuthEmpty}
    >
    
    > config2
    ServerConf {serverHost = "0.0.0.0", serverPort = 9090, serverAssets = "/home/arch/www", serverSecurity = AuthBasic "gh0st" "xmkg3p98sfawgav"}
    >
    
    > runServer config1
    Running server at port 8080, listening host 0.0.0.0
    Server Authentication = AuthEmpty
    > 
    > runServer config2
    Running server at port 9090, listening host 0.0.0.0
    Server Authentication = AuthBasic "gh0st" "xmkg3p98sfawgav"
    > 
    >
    
    -- Read configuration file.
    -- 
    > readConfigFile "/tmp/server.conf" 
    Just (ServerConf {serverHost = "127.0.0.1", serverPort = 80, serverAssets = "/var/www", serverSecurity = AuthBasic "admin" "xjwxfm2pcfg56hga"})
    > 
    
    > :t readConfigFile "/tmp/server.conf" 
    readConfigFile "/tmp/server.conf" :: IO (Maybe ServerConf)
    > 
    
    > runServerConfigFile "/tmp/server.conf"
    Running server at port 80, listening host 127.0.0.1
    Server Authentication = AuthBasic "admin" "xjwxfm2pcfg56hga"
    > 
    > runServerConfigFile "/etc/fstab"
    Error: I can't read the server configuration file
    >
    

    1.3 Using undefined to specify function signature before implementation

    Undefined can be used to define function signatures before they are implemented.

    Example:

    File: math1.hs - Before implement the functions.

    add :: Int -> Int -> Int
    add = undefined
    
    sub :: Int -> Int -> Int
    sub x y = x - y        
    
    product :: Num a => [a] -> a
    product = undefined
    

    Load in the Repl:

    > :load /tmp/math1.hs 
    [1 of 1] Compiling Main             ( /tmp/math1.hs, interpreted )
    Ok, modules loaded: Main.
    > 
    
    > :t add
    add :: Int -> Int -> Int
    >
    > :t Main.add
    Main.add :: Int -> Int -> Int
    > 
    
    
    > :t product
    
    <interactive>:1:1:
        Ambiguous occurrence ‘product’
        It could refer to either ‘Main.product’,
                                 defined at /tmp/math1.hs:6:1
                              or ‘Prelude.product’,
                                 imported from ‘Prelude’ at /tmp/math1.hs:1:1
                                 (and originally defined inData.Foldable’)
    > 
    
    > :t Main.product
    Main.product :: Num a => [a] -> a
    > 
    
    > Main.add 3 5
    *** Exception: Prelude.undefined
    > 
    
    > Main.product [1, 2, 3, 4, 5]
    *** Exception: Prelude.undefined
    >
    

    File: math1.hs after implement the functions.

    add :: Int -> Int -> Int
    add = \x y -> x + y
    
    sub :: Int -> Int -> Int
    sub x y = x - y        
    
    product :: Num a => [a] -> a
    product xs = foldr (*) 1 xs
    

    Loading in the repl:

    > :t add
    add :: Int -> Int -> Int
    > 
    > add 3 5
    8
    >
    
    > Main.add 3 5
    8
    > 
    
    
    > :t product 
    
    <interactive>:1:1:
        Ambiguous occurrence ‘product’
        It could refer to either ‘Main.product’,
                                 defined at /tmp/math2.hs:9:1
                              or ‘Prelude.product’,
                                 imported from ‘Prelude’ at /tmp/math2.hs:1:1
                                 (and originally defined inData.Foldable’)
    > 
    > 
    > Main.product [1, 2, 3, 4, 5]
    120
    > :t Main.product
    Main.product :: Num a => [a] -> a
    >
    

    1.4 Using pattern matching for simple command line application

    The pattern match is useful to develop simple command line applications in a declarative and robust way.

    File: calculator.hs

    import System.Environment  (getArgs)
    import System.Exit (exitFailure, exitSuccess)   
    import Text.Read (readMaybe)
    import Control.Monad 
    
    parseNum :: String -> Maybe Double 
    parseNum = readMaybe    
    
    parseNumList :: [String] -> Maybe [Double]
    parseNumList xs = mapM readMaybe xs                
    
    {- | Perform operation on a single command line argument -}                  
    doOperation :: Show b => (a -> b) -> (String -> Maybe a) -> String -> IO ()
    doOperation fn fnParser arg =
        case fnParser arg of
          Just x  -> do putStrLn $ "Result = " ++ show (fn x)
                        exitSuccess
    
          Nothing -> do putStrLn $ "Error: Invalid input '" ++ arg ++ "'"
                        exitFailure 
    
    {- | Perform operation on multiple command line arguments -}
    doListOperation :: Show b => ([a] -> b) -> ([String] -> Maybe [a]) -> [String] -> IO () 
    doListOperation fn fnParser args =
        case fnParser args of
          Just xs -> do putStrLn $ "Result = " ++ show (fn xs)
                        exitSuccess
    
          Nothing -> do putStrLn $ "Error: Invalid input '" ++ show args ++ "'"
                        exitFailure
    
    
    showHelp = putStrLn $ unlines [
                 "Calculator App"
                ,"\nOptions\n"
                ," -help           - Show help"
                ," -pi             - Show pi number"
                ," -exp x          - Compute exponential"
                ," -sin x          - Compute sin"
                ," -echo file      - Display file"
                ," -sum 10 20 30   - Compute sum"
                ," -prod 10 20 30  - Compute product"
    
                ]
    
    
    echoFile file = do
      content <- readFile file
      putStrLn content
    
    parseArgs :: [String] -> IO ()
    parseArgs args =
        case args of
          []                -> showHelp >> exitSuccess
          ["-pi"]           -> putStrLn (show 3.1415) >> exitSuccess                     
          ["-help"]         -> showHelp >> exitSuccess
          ["-exp", x]       -> doOperation exp parseNum x
          ["-sin", x]       -> doOperation sin parseNum x
          ["-cos", x]       -> doOperation cos parseNum x                     
          ["-echo", file]   -> echoFile file                      
          "-sum":args       -> doListOperation sum parseNumList args
          "-prod":args      -> doListOperation product parseNumList args
          _                 -> do putStrLn "Error: Invalid command line switches."
                                  exitFailure
    {-
    Or 
       main :: IO () 
       main = do 
          args <- getArgs 
          parseArgs args 
    
    
    -}                           
    main :: IO () 
    main = getArgs >>= parseArgs
    

    Command line tests:

    Running in as script:

    $ stack exec -- runhaskell /tmp/calculator.hs 
    Calculator App
    
    Options
    
     -help           - Show help
     -pi             - Show pi number
     -exp x          - Compute exponential
     -sin x          - Compute sin
     -echo file      - Display file
     -sum 10 20 30   - Compute sum
     -prod 10 20 30  - Compute product
    
    
    $ stack exec -- runhaskell /tmp/calculator.hs -help
    Calculator App
    
    Options
    
     -help           - Show help
     -pi             - Show pi number
     -exp x          - Compute exponential
     -sin x          - Compute sin
     -echo file      - Display file
     -sum 10 20 30   - Compute sum
     -prod 10 20 30  - Compute product
    
    
    $ stack exec -- runhaskell /tmp/calculator.hs sada
    Error: Invalid command line switches.
    
    
    $ stack exec -- runhaskell /tmp/calculator.hs -exp 1.0
    Result = 2.718281828459045
    
    $ stack exec -- runhaskell /tmp/calculator.hs -exp 2.0
    Result = 7.38905609893065
    
    $ stack exec -- runhaskell /tmp/calculator.hs -sin 3.1415
    Result = 9.265358966049026e-5
    
    $ stack exec -- runhaskell /tmp/calculator.hs -cos x2323
    Error: Invalid input 'x2323'
    
    $ stack exec -- runhaskell /tmp/calculator.hs -cos
    Error: Invalid command line switches.
    
    $ stack exec -- runhaskell /tmp/calculator.hs -sum 1 2 3 4 5
    Result = 15.0
    
    $ stack exec -- runhaskell /tmp/calculator.hs -prod 1 2 3 4 5 
    Result = 120.0
    
    $ stack exec -- runhaskell /tmp/calculator.hs -prod 1 2 3 sads 4 5 
    Error: Invalid input '["1","2","3","sads","4","5"]'
    
    $ stack exec -- runhaskell /tmp/calculator.hs -echo /etc/issue
    Manjaro Linux \r  (\n) (\l)
    

    Compiling:

    $ stack exec -- ghc /tmp/calculator.hs -o /tmp/calculator.bin
    [1 of 1] Compiling Main             ( /tmp/calculator.hs, /tmp/calculator.o )
    Linking /tmp/calculator.bin ...
    
    $ file /tmp/calculator.bin 
    /tmp/calculator.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b3a0da3f4814ef85dd257b9e81651e2d234bb026, not stripped, with debug_info
    
    
    $ /tmp/calculator.bin 
    Calculator App
    
    Options
    
     -help           - Show help
     -pi             - Show pi number
     -exp x          - Compute exponential
     -sin x          - Compute sin
     -echo file      - Display file
     -sum 10 20 30   - Compute sum
     -prod 10 20 30  - Compute product
    
    $ /tmp/calculator.bin -pi
    3.1415
    
    
    $ /tmp/calculator.bin -exp pi
    Error: Invalid input 'pi'
    
    
    $ /tmp/calculator.bin -exp 3.1415
    Result = 23.138548663861286
    
    
    $ /tmp/calculator.bin -sum 1 2 3 4 5 6 7
    Result = 28.0
    
    
    $ /tmp/calculator.bin -sum 1 nothing 3 4 5 6 asdas
    Error: Invalid input '["1","nothing","3","4","5","6","asdas"]'
    
    $ /tmp/calculator.bin -echo /etc/lsb-release
    DISTRIB_ID=ManjaroLinux
    DISTRIB_RELEASE=17.0
    DISTRIB_CODENAME=Gellivara
    DISTRIB_DESCRIPTION="Manjaro Linux"
    

    Testing in REPL:

    > :t parseArgs 
    parseArgs :: [String] -> IO ()
    > 
    > parseArgs ["-help"]
    Calculator App
    
    Options
    
     -help           - Show help
     -pi             - Show pi number
     -exp x          - Compute exponential
     -sin x          - Compute sin
     -echo file      - Display file
     -sum 10 20 30   - Compute sum
     -prod 10 20 30  - Compute product
    
    *** Exception: ExitSuccess
    > 
    > parseArgs ["-help", "qasdas", "seds"]
    Error: Invalid command line switches.
    *** Exception: ExitFailure 1
    > 
    > parseArgs ["-pi"]
    3.1415
    *** Exception: ExitSuccess
    > 
    
    > 
    > parseArgs ["-exp", "1.23"]
    Result = 3.4212295362896734
    *** Exception: ExitSuccess
    > 
    > parseArgs ["-sum", "1.23", "2.323", "4.23"]
    Result = 7.783
    *** Exception: ExitSuccess
    > 
    
    > parseArgs ["-echo", "/etc/lsb-release"]
    DISTRIB_ID=ManjaroLinux
    DISTRIB_RELEASE=17.0
    DISTRIB_CODENAME=Gellivara
    DISTRIB_DESCRIPTION="Manjaro Linux"
    
    >
    

    1.5 Debug Computations using trace

    The module Debug.Trace provides functions to allow debug computations.

    Function   Signature Description
    trace :: String -> a -> a Print the first argument (string) and returns the second.
    traceShow :: Show a => a -> b -> b Print the first value and returns the second.
    traceIO :: String -> IO () Like trace but works in a IO Monad.
           

    Example:

    import Debug.Trace
    
    let x = 10 + 20 in trace ("the value of (10 + 20) is = " ++ show x) x
    > the value of (10 + 20) is = 30
    30
    it
    
    
    :{
    factorial :: Integer -> Integer
    factorial 0 = 1
    factorial n = trace ("n = " ++ show n) n * factorial (n -1)
    :}
    
    
    > factorial 5
    n = 5
    n = 4
    n = 3
    n = 2
    n = 1
    120
    it :: Integer
    >
    
    > factorial 10
    n = 10
    n = 9
    n = 8
    n = 7
    n = 6
    n = 5
    n = 4
    n = 3
    n = 2
    n = 1
    3628800
    it :: Integer
    >
    
    
    
    :{    
    myfoldl :: Show acc => (acc -> x -> acc) -> acc -> [x] -> acc
    myfoldl step acc []      =  acc
    myfoldl step acc (x:xs)  =  myfoldl step (step acc x) xs                       
    :}
    
    
    > myfoldl (\acc x -> 10 * acc +x) 0 [1, 2, 3, 4, 5]
    12345
    >     
    
    
    :{    
    myfoldl2 :: Show acc => (acc -> x -> acc) -> acc -> [x] -> acc
    myfoldl2 step acc []      =  acc
    myfoldl2 step acc (x:xs)  =  let acc' = step acc x
                                 in trace ("acc' = " ++ show acc') $ myfoldl2 step acc' xs                       
    :}
    
    >  myfoldl2 (\acc x -> 10 * acc +x) 0 [1, 2, 3, 4, 5]
    acc' = 1
    acc' = 12
    acc' = 123
    acc' = 1234
    acc' = 12345
    12345
    > 
    
    > let result =  myfoldl2 (\acc x -> 10 * acc +x) 0 [1, 2, 3, 4, 5]
    
    > result
    acc' = 1
    acc' = 12
    acc' = 123
    acc' = 1234
    acc' = 12345
    12345
    
    > result
    acc' = 1
    acc' = 12
    acc' = 123
    acc' = 1234
    acc' = 12345
    12345
    
    > result * 3
    acc' = 1
    acc' = 12
    acc' = 123
    acc' = 1234
    acc' = 12345
    37035
    > 
    
    :{
    traceLabel :: Show a => String -> a -> a 
    traceLabel label value = trace (label ++ show value) value
    :}
    
    
    :{    
    myfoldl3 :: Show acc => (acc -> x -> acc) -> acc -> [x] -> acc
    myfoldl3 step acc []      =  acc
    myfoldl3 step acc (x:xs)  =  myfoldl3 step (traceLabel "acc' = " $ step acc x) xs                       
    :}
    
    
    >  myfoldl3 (\acc x -> 10 * acc +x) 0 [1, 2, 3, 4, 5]
    acc' = 1
    acc' = 12
    acc' = 123
    acc' = 1234
    acc' = 12345
    12345
    >
    

    1.6 Haskell Interactive Shell - GHCI

    1.6.1 GHCI configuration file

    Example - GHCI without configuration file

    $ stack ghci
    Configuring GHCi with the following packages: 
    GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
    Loaded GHCi configuration from /tmp/ghci7713/ghci-script
    Prelude> 
    Prelude> import Control.Monad
    Prelude Control.Monad> 
    
    Prelude Control.Monad> import System.Directory as D
    Prelude Control.Monad D> 
    
    Prelude Control.Monad D> D.getDirectoryContents "/etc/" >>= mapM_ putStrLn
    systemd
    motd
    adobe
    ld.so.cache
    environment
    libreoffice
    rc_keymaps
    sensors3.conf
    gshadow
    acpi
    pkcs11
    modules-load.d
    cron.deny
    shadow-
    gufw
    security
    tmpfiles.d
    ... ...         
    
    Prelude Control.Monad D> 
    Prelude Control.Monad D> :{
    Prelude Control.Monad D| getPassword :: String -> IO () -> IO () -> IO ()         
    Prelude Control.Monad D| getPassword password success error = do
    Prelude Control.Monad D|   passwd <- putStr "Enter the password: " >> getLine 
    Prelude Control.Monad D|   if passwd == password
    Prelude Control.Monad D|   then success
    Prelude Control.Monad D|   else do error
    Prelude Control.Monad D|           getPassword password success error
    Prelude Control.Monad D| :}
    Prelude Control.Monad D>
    

    Example - GHCI with configuration file

    File: ~/.ghci

    import Control.Concurrent (forkIO, threadDelay)
    import System.Directory (getCurrentDirectory)
    
    -- Set prompts
    
    :set prompt "> "
    :set prompt2 "- "
    
    -- Print type after evaluation 
    :set +t 
    
    -- Start at tmp directory 
    :cd /tmp
    
    -- Add language extensions 
    :set -XFlexibleInstances
    
    putStrLn "Wellcome to Haskell"
    

    GHCI Repl:

    $ stack ghci
    Configuring GHCi with the following packages: 
    GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
    Wellcome to Haskell
    it :: ()
    Loaded GHCi configuration from /home/archbox/.ghci
    Loaded GHCi configuration from /tmp/ghci8295/ghci-script
    > 
    
    > import Control.Monad
    > import System.Directory as D
    > 
    > D.getDirectoryContents "/etc/" >>= mapM_ putStrLn
    systemd
    motd
    adobe
    ld.so.cache
    environment
    libreoffice
    rc_keymaps
    sensors3.conf
    gshadow
    
    ... ... ...
    
    -- Paste this function
    {-
    getPassword :: String -> IO () -> IO () -> IO ()         
    getPassword password success error = do
      passwd <- putStr "Enter the password: " >> getLine 
      if passwd == password
      then success
      else do error
              getPassword password success error
    -}
    
    > :{
    - getPassword :: String -> IO () -> IO () -> IO ()         
    - getPassword password success error = do
    -   passwd <- putStr "Enter the password: " >> getLine 
    -   if passwd == password
    -   then success
    -   else do error
    -           getPassword password success error
    - :}
    getPassword :: String -> IO () -> IO () -> IO ()
    >
    
    
    > let fn a b = 10 * a - b
    fn :: Num a => a -> a -> a
    > fn 10 20
    80
    it :: Num a => a
    > it
    80
    it :: Num a => a
    > 
    
    > :t forkIO
    forkIO :: IO () -> IO GHC.Conc.Sync.ThreadId
    
    > :t threadDelay 
    threadDelay :: Int -> IO ()
    > 
    
    
    > forkIO (forever $ threadDelay 1000000 >> putStrLn "Repeat forever with 1 second delay")
    ThreadId 82
    it :: GHC.Conc.Sync.ThreadId
    > Repeat forever with 1 second delay
    Repeat forever with 1 second delay
    Repeat forever with 1 second delay
    Repeat forever with 1 second delay
    Repeat forever with 1 second delay
    Repeat forever with 1 second delay
    Repeat forever with 1 second delay
    Repeat forever with 1 second delay
         .. ... ... ...
    

    1.6.2 Extract contents of IO action

    It is useful to extract the value wrapped by an IO action to small experiments and interactive commands. Note: It is only possible in the REPL.

    Not possible because readFile returns an IO action.

    > putStrLn (readFile "/etc/lsb-release")
    
    <interactive>:5:11: error:
         Couldn't match typeIO String’ with ‘[Char]’
          Expected type: String
            Actual type: IO String
         In the first argument of ‘putStrLn’, namely
            ‘(readFile "/etc/lsb-release")’
          In the expression: putStrLn (readFile "/etc/lsb-release")
          In an equation for ‘it’:
              it = putStrLn (readFile "/etc/lsb-release")
    >
    
    > :t readFile
    readFile :: FilePath -> IO String
    
    > let content = readFile "/etc/lsb-release"
    
    > :t content
    content :: IO String
    > 
    
    
    > putStrLn content
    
    <interactive>:9:10: error:
         Couldn't match typeIO String’ with ‘[Char]’
          Expected type: String
            Actual type: IO String
         In the first argument of ‘putStrLn’, namely ‘content’
          In the expression: putStrLn content
          In an equation for ‘it’: it = putStrLn content
    >
    
    > :t readFile
    readFile :: FilePath -> IO String
    

    Solutions:

    Extract the IO action content. It is possible due to the Haskell REPL be inside an IO action.

    > let contentIOaction = readFile "/etc/lsb-release"
    
    > :t contentIOaction 
    contentIOaction :: IO String
    > 
    
    > content <- contentIOaction 
    >
    
    > :t content
    content :: String
    
    > putStrLn content
    DISTRIB_ID=ManjaroLinux
    DISTRIB_RELEASE=17.0.1
    DISTRIB_CODENAME=Gellivara
    DISTRIB_DESCRIPTION="Manjaro Linux"
    
    > length content
    110
    >
    
    > lines content
    ["DISTRIB_ID=ManjaroLinux","DISTRIB_RELEASE=17.0.1","DISTRIB_CODENAME=Gellivara","DISTRIB_DESCRIPTION=\"Manjaro Linux\""]
    > 
    
    
    {- -- ========================   Or ======================= --}
    
    > content <- readFile "/etc/lsb-release"
    > :t content
    content :: String
    
    
    > putStrLn content
    DISTRIB_ID=ManjaroLinux
    DISTRIB_RELEASE=17.0.1
    DISTRIB_CODENAME=Gellivara
    DISTRIB_DESCRIPTION="Manjaro Linux"
    
    > length content
    110
    >
    

    Pass the IO action content using (>>=) bind or (=<<)

    > readFile "/etc/lsb-release" >>= putStrLn
    DISTRIB_ID=ManjaroLinux
    DISTRIB_RELEASE=17.0.1
    DISTRIB_CODENAME=Gellivara
    DISTRIB_DESCRIPTION="Manjaro Linux"
    
    
    > putStrLn =<< readFile "/etc/lsb-release"
    DISTRIB_ID=ManjaroLinux
    DISTRIB_RELEASE=17.0.1
    DISTRIB_CODENAME=Gellivara
    DISTRIB_DESCRIPTION="Manjaro Linux"
    

    1.6.3 Load interactive commands from file

    The Ghci directive :script is load interactive commands that could by typed in Ghci shell from a file.

    File: src/script_ghci.hs

    content <- readFile "/etc/lsb-release"
    
    let nChars = length content
    
    putStrLn $ "File content = \n" ++ content
    
    putStrLn $ "File size (number of chars) = " ++ show nChars
    
    
    putStrLn "--------------------------------"
    
    :{
    getPassword :: String -> IO () -> IO () -> IO ()         
    getPassword password success error = do
      passwd <- putStr "Enter the password: " >> getLine 
      if passwd == password
      then success
      else do error
              getPassword password success error
    :}
    
    
    :{
    showSecreteFile :: String -> IO ()
    showSecreteFile file = do
      content <- readFile file
      putStrLn "Secrete File Content"
      putStrLn content
    
    :}   
    
    
    :{
    getPassword2 :: String -> Int -> IO ()
    getPassword2 password maxTry = aux (maxTry - 1)
      where
        aux counter = do 
          passwd <- putStr "Enter the password: " >> getLine
          case (counter, passwd) of
            _ | passwd == password
                  -> showSecreteFile "/etc/shells"
    
            _ | counter == 0
                  -> putStrLn $ "File destroyed after " ++ show maxTry ++ " attempts."                 
            _
                  -> do putStrLn $ "Try again. You only have " ++ show counter ++ " attempts."
                        aux (counter - 1)                                      
    
    :}
    
    let printLine = putStrLn "-----------------------------------------------"
    
    printLine                
    putStrLn "Open secrete vault: "
    getPassword "guessthepassword" (putStrLn "Vault opened. Ok.") (putStrLn "Error: Wrong password. Try Again")               
    printLine
    putStrLn "Open secret file: (1)"         
    getPassword2 "xyzVjkmArchp972343asx" 3
    
    printLine             
    putStrLn "Open secret file: (2)"         
    getPassword2 "xyzVjkmArchp972343asx" 3
    

    Running:

    archbox@ghostpc 18:19 ~/Documents/projects/fp-by-example.tutorial/haskell
    $ stack ghci
    Configuring GHCi with the following packages: 
    GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
    Loaded GHCi configuration from /home/archbox/.ghci
    Loaded GHCi configuration from /tmp/ghci7633/ghci-script
    >
    
    > 
    > :script src/script_ghci.hs 
    File content = 
    DISTRIB_ID=ManjaroLinux
    DISTRIB_RELEASE=17.0.1
    DISTRIB_CODENAME=Gellivara
    DISTRIB_DESCRIPTION="Manjaro Linux"
    
    File size (number of chars) = 110
    --------------------------------
    -----------------------------------------------
    Open secrete vault: 
    Enter the password: mxkasdasd
    Error: Wrong password. Try Again
    Enter the password: 32423asdas
    Error: Wrong password. Try Again
    Enter the password: guessht^?
    Error: Wrong password. Try Again
    Enter the password: guessthepassword
    Vault opened. Ok.
    -----------------------------------------------
    Open secret file: (1)
    Enter the password: 234asdqa
    Try again. You only have 2 attempts.
    Enter the password: i dont know
    Try again. You only have 1 attempts.
    Enter the password: try again
    File destroyed after 3 attempts.
    -----------------------------------------------
    Open secret file: (2)
    Enter the password: tell me it
    Try again. You only have 2 attempts.
    Enter the password: archxfmksjpfUif83432
    Try again. You only have 1 attempts.
    Enter the password: xyzVjkmArchp972343asx
    Secrete File Content
    #
    # /etc/shells
    #
    
    /bin/sh
    /bin/bash
    
    # End of file
    /bin/zsh
    /usr/bin/zsh
    

    Author: nobody

    Created: 2018-06-17 Sun 02:37

    Emacs 25.3.1 (Org mode 8.2.10)

    Validate