diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/03-starting-out.hs b/03-starting-out.hs old mode 100644 new mode 100755 diff --git a/04-types-and-typeclasses.hs b/04-types-and-typeclasses.hs old mode 100644 new mode 100755 diff --git a/05-syntax-in-functions.hs b/05-syntax-in-functions.hs old mode 100644 new mode 100755 diff --git a/06-recursion.hs b/06-recursion.hs old mode 100644 new mode 100755 diff --git a/07-higher-order-functions.hs b/07-higher-order-functions.hs old mode 100644 new mode 100755 diff --git a/08-modules.hs b/08-modules.hs old mode 100644 new mode 100755 diff --git a/09-types-and-typeclasses-redux.hs b/09-types-and-typeclasses-redux.hs old mode 100644 new mode 100755 diff --git a/10-input-and-output.hs b/10-input-and-output.hs new file mode 100755 index 0000000..ed673f5 --- /dev/null +++ b/10-input-and-output.hs @@ -0,0 +1,264 @@ + +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!" + + diff --git a/11-thinking-functionally.hs b/11-thinking-functionally.hs new file mode 100755 index 0000000..139597f --- /dev/null +++ b/11-thinking-functionally.hs @@ -0,0 +1,2 @@ + + diff --git a/README.md b/README.md old mode 100644 new mode 100755