Io In Haskell(2) Make A Better Program For You And For Me
Almost every program languange has its toolkit to deal with command line input, haskell is not an exception. Now we dig in and see why we need it!
1. A disrepute stage show
Image you have a todo list on your table, every morning when you wake up, you write all the tasks you need to finish in this beautiful day. And when you finish one task, just delete the task from the list. Amazing, keep this habbit for a couple of years, you will absolutly change your life!!!
First, coding the adding story line:
main = do
todoItem <- getLine
appendFile "todo.txt" (todoItem ++ "\n")
Ok! Compile the program, let's add some tasks of this day:
$ ./manufile
10:00 am, hacking the haskell programming
$ ./manufile
11:00 am, jumping and dancing
$ ./manufile
14:00 pm, singing a song that no one else sing
After we input the three tasks, check the todo.txt file:
$ cat todo.txt
10:00 am, hacking the haskell programming
11:00 am, jumping and dancing
14:00 pm, singing a song that no one else sing
OK! Finish the adding program. Next, it's time to design out the delete program:
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 TO-DO 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"
Holy shit!!! What a big mess! Don’t worry, let's dig into the program line by line, i translate the program into a small poem:
Open and handle your old todo list
Open an empty paper to record the new todo list
Shake your old one and let us see your life history
Tell us you have finished what task?
The finished was finished any way! don't think about it anymore!
Start your life in the next way!
:), Speak in a sentence, it just delete the item you have finished!
Then we look back, try to find some drawbacks of the previous programs. Why we hardcode the todo.txt into the program? If we have another todo list called “todo2.txt”. (Too much work will kill you, take it easy, my friend!) Why we seperate the adding and delete actions into two program? Stay safe! The holy command line will come home!!
2. Haskell command line arguments
The System.Environment module has two cool I/O actions:
- getArgs: IO [String], get the arguments that the program was run with and have as its contained result a list with the arguments.
- getProgName: Io String, a I/O action that contains the program name.
import System.IO
import System.Environment
import Data.List
main = do
args <- getArgs
progName <- getProgName
putStrLn "The arguments are: "
putStrLn $ unlines (zipWith (\n arg -> show n ++ " : " ++ arg) [1..] args)
putStrLn "The name of the program is: "
putStrLn progName
Compile and run the program with arguments:
./manufile first second third
The arguments are:
1 : first
2 : second
3 : third
The name of the program is:
manufile
Wonderful(旺德福)!We get the arguments and progname! Now, make a salvage to the bad behavior we made in the 1th section!
import System.Environment
import System.Directory
import System.IO
import Data.List
dispatch :: [(String, [String] -> IO ())]
dispatch = [ ("add", add)
, ("view", view)
, ("remove", remove)
]
main = do
(command:args) <- getArgs
let (Just action) = lookup command dispatch
action args
add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\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
remove :: [String] -> IO ()
remove [fileName, numberString] = do
handle <- openFile fileName ReadMode
(tempName, tempHandle) <- openTempFile "." "temp"
contents <- hGetContents handle
let number = read numberString
todoTasks = lines contents
newTodoItems = delete (todoTasks !! number) todoTasks
hPutStr tempHandle $ unlines newTodoItems
hClose handle
hClose tempHandle
removeFile fileName
renameFile tempName fileName
Compile and do some test:
$ ./todo view todo.txt
0 - 10:00 am coding to the next world
1 - 10:00 am, hacking the haskell programming
2 - 11:00 am, jumping and dancing
3 - 14:00 pm, singing a song that no one else sing
$ ./todo add todo.txt "15:30 pm, kick your ass"
$ ./todo view todo.txt
0 - 10:00 am coding to the next world
1 - 10:00 am, hacking the haskell programming
2 - 11:00 am, jumping and dancing
3 - 14:00 pm, singing a song that no one else sing
4 - 15:30 pm, kick your ass
$ ./todo remove todo.txt 0
$ ./todo view todo.txt
0 - 10:00 am, hacking the haskell programming
1 - 11:00 am, jumping and dancing
2 - 14:00 pm, singing a song that no one else sing
3 - 15:30 pm, kick your ass
First, we run todo program with view and todo.txt, it list out our todo tasks with its index. Then, add a todo task into the list. Finally, delete the 0th task.
dispatch :: [(String, [String] -> IO ())]
dispatch = [ ("add", add)
, ("view", view)
, ("remove", remove)
]
dispatch is a associate list, map a string to a I/O function. Program check the first argument to find the action it will take.
3. Throw your dices in haskell: Randomness
What make haskell standing out is its pure functional character, this promise that you can’t get different results after calling a function with the same parameter twices. But it can’t deal with randomness, the same function will return different results in different time.
Enter the System.Random module, it has all the functions that satisfy our need for randomness. The first actor is random function:
Prelude System.Random> :t random
random :: (Random a, RandomGen g) => g -> (a, g)
- RandomGen: typeclass is for types that can act as sources of randomness.
- Random: typeclass is for things that can take on random values. A boolean value can take on a random value, namely True and False. A number can also take on a random value. We can translate the signature of random as following: it takes a random generator and return a random value and a new random generator.
Prelude System.Random> random (mkStdGen 1000)
(1611434616111168504,1261958764 2103410263)
Prelude System.Random> random (mkStdGen 1000) :: (Integer, StdGen)
(1611434616111168504,1261958764 2103410263)
Prelude System.Random> random (mkStdGen 1000) :: (Bool, StdGen)
(True,40054014 40692)
Prelude System.Random> random (mkStdGen 1000) :: (Bool, StdGen)
(True,40054014 40692)
Prelude System.Random> random (mkStdGen 1000) :: (Float, StdGen)
(0.8345514,698578198 1655838864)
StdGen is an instance of RandomGen class, mkStdGen takes a integer and return an random generator.
Let's see a real world example, throw a dice three times:
threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
let (firstCoin, newGen) = random gen
(secondCoin, newGen') = random newGen
(thirdCoin, newGen'') = random newGen'
in (firstCoin, secondCoin, thirdCoin)
See the newGen, newGen’ and newGen’’, A ha! This why random return a random value and a new Randomness Generator!!!
We use stack build a real example:
$ stack new random-project
The tree of the random project:
.
├── app
│ └── Main.hs
├── ChangeLog.md
├── LICENSE
├── package.yaml
├── random-project.cabal
├── README.md
├── Setup.hs
├── src
│ └── Lib.hs
├── stack.yaml
├── stack.yaml.lock
└── test
└── Spec.hs
We define our coin function in Lib.hs:
module Lib
( someFunc,
randomTest
) where
import System.Random
someFunc :: IO ()
someFunc = putStrLn "someFunc"
threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
let (firstCoin, newGen) = random gen
(secondCoin, newGen') = random newGen
(thirdCoin, newGen'') = random newGen'
in (firstCoin, secondCoin, thirdCoin)
randomTest :: Int -> IO ()
randomTest seed = do
let (first, second, third) = threeCoins (mkStdGen seed)
putStrLn (show first)
putStrLn (show second)
putStrLn (show third)
In Main.hs we read the command line argument, and execute coin throwing:
module Main where
import Lib
import System.Environment
main :: IO ()
main = do
(seed:xs) <- getArgs
randomTest (read seed)
Build and run with different arguments:
$ stack exec random-project-exe 100
True
False
False
$ stack exec random-project-exe 101
True
True
True
$ stack exec random-project-exe 102
True
True
True
We just use the command line toolkit in the 2th section!!!