main = putStrLn "hello, world" -- putStrLn :: String -> IO() -- IO actions are performed when they are the result of `main` main = do putStrLn "Hello, what's your name?" :: IO () name <- getLine putStrLn ("Hey " ++ name ++ "!") :: IO () main = do putStrLn "Enter a number:" strResult <- getLine let numResult=(read strResult) :: Int putStrLn (show (numResult * 10)) -- main is always oftype main :: IO a, but we normally don't specify the type explicitly -- Well, I/O actions will be performed if we type them out in GHCi... -- In do-blocks, we can use `let` without `in`: main = do putStrLn "Name?" name <- getLine let bigName = map toUpper name putStrLn $ "hey, " ++ bigName ++ "." -- Example that continually reads line and prints out reversed words: reverseWords :: String -> String reverseWords = unwords . map reverse . words main = do line <- getLine if null line then return () else do putStrLn $ reverseWords line main -- putStr - returns IO that prints string to terminal, no \n -- putChar - returns IO that prints char to terminal -- print - for some value deriving Show, do putStrLn . show -- getChar - IO that reads character of input - because of buffering, read won't happen until user hits return -- when from Control.Monad - if value is True, return the IO passed in. Else, return (): import Control.Monad main = do c <- getChar when (c /= ' ') $ do putChar c main -- sequence - list of IO and returns IO that performs them in order - sequence :: [IO a] -> IO [a] -- mapM maps a function over the list then sequences it, mapM_ does the same and throws out the result -- forever - takes an IO and returns an IO that repeats the original action forever - from Control.Monad - e.g. import Control.Monad import Data.Char main = forever $ do putStr "Input: " l <- getLine putStrLn $ map toUpper l -- Files & streams -- getContents reads from stdin in a lazy manner getContents :: IO String import Data.Char main = do contents <- getContents putStr (map toUpper contents) -- This reads from contents as needed, rather than all at once. -- Now, a program that takes input and prints lines shorter than 10 chars main = do contents <- getContents putStr (shortLinesOnly contents) shortLinesOnly :: String -> String shortLinesOnly input = unlines . (filter (\x -> (length x) < 10)) . lines input -- We can use `interact` to read contents, map them String -> String, then print the result: main = interact $ unlines . (filter ((<10) . length)) . lines -- File IO -- Here's an example reading a file: import System.IO main = do handle <- openFile "file.txt" ReadMode contents <- hGetContents handle putStr contents hClose handle -- Alternatively, we can use `withFile` to automatically close the Handle: main = do withFile "file.txt" ReadMode (\handle -> do contents <- hGetContents handle putStr contents) -- We could implement withFile ourselves: withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a withFile path mode fn = do handle <- openFile path mode result <- fn handle hClose handle return result -- hGetLine, hPutStr, hPutStrLn, hGetChar all exist for file IO -- readFile :: FilePath -> IO String can simplify the above - reads contents of file to IO String action -- writeFile :: FilePath -> String -> IO () - overwrites contents of file -- appendFile :: FilePath -> String -> IO () - appends to contents of file -- We can change the buffering mode using hSetBuffering and the BufferMode type -- Can also use hFlush to flush a handle (IO ()) -- Program for removing line from todo.txt: import System.IO import System.Directory import Data.List main = do handle <- openFile "todo.txt" ReadMode (tempName, tempHandle) <- openTempFile "." "temp" contents <- hGetContents handle let todoTasks = lines contents numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks putStrLn "These are your TODO items:" putStr $ unlines numberedTasks putStrLn "Which one do you want to delete?" numberString <- getLine let number = read numberString newTodoItems = delete (todoTasks !! number) todoTasks hPutStr tempHandle $ unlines newTodoItems hClose handle hClose tempHandle removeFile "todo.txt" renameFile tempName "todo.txt" -- We can use getArgs :: IO [String] and getProgName :: IO String from System.Environment to get CLI args -- Let's build a simple CLI application: import System.Environment import System.Directory import System.IO import Data.List add :: [String] -> IO () add [fileName, todoString] = appendFile fileName (todoString ++ "\n") view :: [String] -> IO () view [fileName] = do contents <- readFile fileName let todoTasks = lines contents numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks putStr $ unlines numberedtasks -- ...similarly for `remove` dispatch :: [(String, [String] -> IO ())] dispatch = [ ("add", add), ("view", view), ("remove", remove), ] main = do (command:args) <- getArgs let (Just action) = lookup command dispatch action args -- Random number generation: import System.Random -- random :: (RandomGen g, Random a) -> g -> (a, g) -- RandomGen - a source of randomness -- Random - typeclass of things that can take on random values -- To manually create a random generator, use mkStdGen :: Int -> StdGen -- So, now we can do this: random (mkStdGen 100) :: (Int, StdGen) -- this casts the a -> Int -- We can use the `randoms` function to get an infinite sequence of random values: take 5 $ randoms (mkStdGen 11) :: [Int] -- We can get random values in a range using randomR and randomRs -- randomR :: (RandomGen g, Random a) :: (a, a) -> g -> (a, g) -- But we may want to randomly initialize StdGen rather than providing a seed -- To do this, we can use `getStdGen :: IO StdGen`: main = do gen <- getStdGen putStr $ take 20 (randomRs ('a','z') gen) -- Bytestrings -- [Char] can be inefficient because Char is not a fixed width -- Instead, we can use bytestrings which are [Word8] mostly -- There are 2 kinds - Data.Bytestring (strict) and Data.Bytestring.Lazy (lazy) -- pack :: [Word8] -> ByteString -- Word8 is 0 thru 255. For example: Data.ByteString.pack [99,97,110] -- == Chunk "can" Empty -- unpack :: ByteString -> [Word8] -- fromChunks takes a list of strict bytestrings and creates a combined lazy bytestring -- cons - adds a Word8 to the beginning of a bytestring, lazily -- cons' - if you are prepending lots of Word8, use the strict version since it's more efficient -- empty - makes an empty ByteString -- Example program copying one file to another, lazily. This already exists, but is a good example import System.Environment import qualified Data.ByteString.Lazy as B main = do (fileName1:fileName2:_) <- getArgs copyFile fileName1 fileName2 copyFile :: FilePath -> FilePath -> IO () copyFile src dst = do contents <- B.readFile src B.writeFile dst contents -- This lazily transfers the file in chunks -- In I/O context, we can actually catch exceptions arising from IO actions using -- `catch` from System.IO.Error: -- catch :: IO a -> (IOError -> IO a) -> IO a import System.Environment import System.IO import System.IO.Error main = toTry `catch` handler toTry :: IO () toTry = do (fileName:_) <- getArgs contents <- readFile fileName putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!" handler :: IOError -> IO () handler e = putStrLn "Whoops, had some trouble!" -- We can use ioError to re-raise the IOError -- There are predicates over IOError to figure out what type of error it was: -- isAlreadyExistsError, isDoesNotExistError, isAlreadyInUseError, isFullError, isEOFError, isIllegalOperation, isPermissionError, isUserError -- isUserError is True when we use `userError` to create an IOError: ioError $ userError "Uh, oh!"