This is an exercise to learn the StateT monad. The program implements the game Morra. The two players are the computer and a person. The state accumulates the score of the computer and player. The program works for one iteration of function morra. However I am at a loss how to loop it. I have tried a few things but nothing seems to work.
module Morra where
import Control.Monad.Trans.State.Lazy
import Control.Monad.IO.Class
import Data.Char (isDigit, digitToInt)
import System.Random (randomRIO)
import Control.Monad (when)
morra :: StateT (Int, Int) IO ()
morra = do
p <- liftIO getChar
when (isDigit p) $
do
let p' = digitToInt p
c <- liftIO $ randomRIO (1, 2)
liftIO $ putStrLn ['P',':',' ',p] --"P: " ++ p)
liftIO $ putStrLn ("C: " ++ show c)
(pt, ct) <- get
if even (c + p') then
do
liftIO $ putStrLn "Computer Wins"
put (pt, ct + 1)
else
do
liftIO $ putStrLn "Player Wins"
put (pt + 1, ct)
main :: IO ()
main = do
putStrLn "-- p is Player"
putStrLn "-- c is Computer"
putStrLn "-- Player is odds, Computer is evens."
fScore <- runStateT morra (0,0)
let personS = fst . snd $ fScore
compS = snd . snd $ fScore
putStrLn ("Person Score: " ++ show personS)
putStrLn ("Computer Score: " ++ show compS)
if personS > compS then
putStrLn "Winner is Person"
else
putStrLn "Winner is Computer"
You're 99% there. Just add main on a new line right after the last putStrLn, and main will call itself, effectively restarting the program.
A few tricks to simplify some things in your code:
Use execStateT:: StateT s m a -> s -> m s to take just the final state of the round. This way, you don't need to use the let bindings to extract the score, and can do it inline instead: (personS,compS) <- execStateT morra (0,0)
['P',':',' ',p] can be written as ("P: " ++ [p])
It's a matter of style and preference, but you can reduce a lot of the indentation and formatting whitespace by rearranging your ifs, elses and dos:
if condition
then do
doSomethingA
doSomethingB
else someFunction $ do
doSomethingElseA
doSomethingElseB
Overall, nice job :)
I replaced p <- liftIO getChar with p <- liftIO getLine and made a few other minor changes to allow for the fact that p is now a String rather than a Char. Now it works. Seems that it has something to do with Windows as it works using getChar on linux. This is the final code:
module Morra where
import Control.Monad.Trans.State.Lazy
import Control.Monad.IO.Class
import Data.Char (isDigit, digitToInt)
import System.Random (randomRIO)
import Control.Monad (when)
morra :: StateT (Int, Int) IO ()
morra = do
p <- liftIO getLine
let p1 = head p
when (isDigit p1) $ do
let p' = digitToInt p1
c <- liftIO $ randomRIO (1, 2)
liftIO $ putStrLn ("P: " ++ p)
liftIO $ putStrLn ("C: " ++ show c)
(pt, ct) <- get
if even (c + p') then do
liftIO $ putStrLn "Computer Wins"
put (pt, ct + 1)
else do
liftIO $ putStrLn "Player Wins"
put (pt + 1, ct)
morra
main :: IO ()
main = do
putStrLn "-- p is Player"
putStrLn "-- c is Computer"
putStrLn "-- Player is odds, Computer is evens."
(personS,compS) <- execStateT morra (0,0)
putStrLn ("Person Score: " ++ show personS)
putStrLn ("Computer Score: " ++ show compS)
if personS == compS then
putStrLn "No Winner"
else if personS > compS then
putStrLn "Winner is Person"
else
putStrLn "Winner is Computer"
Related
The following example requires the packages of:
- text
- string-conversions
- process
Code:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
module Example where
import qualified Data.Text as T
import Data.Text (Text)
import Data.Monoid
import Control.Monad.Identity
import System.Process
import GHC.IO.Handle
import Debug.Trace
import Data.String.Conversions
runGhci :: Text -> IO Text
runGhci _ = do
let expr = "print \"test\""
let inputLines = (<> "\n") <$> T.lines expr :: [Text]
print inputLines
createProcess ((proc "ghci" ["-v0", "-ignore-dot-ghci"]) {std_in=CreatePipe, std_out=CreatePipe, std_err=CreatePipe}) >>= \case
(Just pin, Just pout, Just perr, ph) -> do
output <-
forM inputLines (\i -> do
let script = i <> "\n"
do
hPutStr pin $ cs $ script
hFlush pin
x <- hIsEOF pout >>= \case
True -> return ""
False -> hGetLine pout
y <- hIsEOF perr >>= \case
True -> return ""
False -> hGetLine perr
let output = cs $! x ++ y
return $ trace "OUTPUT" $ output
)
let f i o = "ghci>" <> i <> o
let final = T.concat ( zipWith f (inputLines :: [Text]) (output :: [Text]) :: [Text])
print final
terminateProcess ph
pure $ T.strip $ final
_ -> error "Invaild GHCI process"
If I attempt to run the above:
stack ghci src/Example.hs
ghci> :set -XOverloadedStrings
ghci> runGhci ""
["print \"test\"\n"]
It appears to be blocking on hIsEOF perr, according to https://stackoverflow.com/a/26510673/1663462 it sounds like I shouldn't call this function unless there is 'some output' ready to be flushed / read... However how do I handle the case where it does not have any output at that stage? I don't mind periodically 'checking' or having a timeout.
How can I prevent the above from hanging? I've tried various approaches involving hGetContents, hGetLine however they all seem to end up blocking (or closing the handle) in this situation...
I had to use additional threads, MVars, as well as timeouts:
runGhci :: Text -> IO Text
runGhci _ = do
let expr = "123 <$> 123"
let inputLines = filter (/= "") (T.lines expr)
print inputLines
createProcess ((proc "ghci" ["-v0", "-ignore-dot-ghci"]) {std_in=CreatePipe, std_out=CreatePipe, std_err=CreatePipe}) >>= \case
(Just pin, Just pout, Just perr, ph) -> do
output <- do
forM inputLines
(\i -> do
let script = "putStrLn " ++ show magic ++ "\n"
++ cs i ++ "\n"
++ "putStrLn " ++ show magic ++ "\n"
do
stdoutMVar <- newEmptyMVar
stderrMVar <- newMVar ""
hPutStr pin script
hFlush pin
tOutId <- forkIO $ extract' pout >>= putMVar stdoutMVar
tErrId <- forkIO $ do
let f' = hGetLine perr >>= (\l -> modifyMVar_ stderrMVar (return . (++ (l ++ "\n"))))
forever f'
x <- timeout (1 * (10^6)) (takeMVar stdoutMVar) >>= return . fromMaybe "***ghci timed out"
y <- timeout (1 * (10^6)) (takeMVar stderrMVar) >>= return . fromMaybe "***ghci timed out"
killThread tOutId
killThread tErrId
return $ trace "OUTPUT" $ cs $! x ++ y
)
let final = T.concat ( zipWith f (inputLines :: [Text]) (output :: [Text]) :: [Text])
print final
terminateProcess ph
pure $ T.strip $ cs $ final
_ -> error "Invaild GHCI process"
I have a very simple function f :: Int -> Int and I want to write a program that calls f for each n = 1,2,...,max. After each call of f the (cumulative) time that was used up to that point should be displayed (along with n and f n). How can this be implemented?
I'm still really new to input/output in Haskell, so this is what I've tried so far (using some toy example function f)
f :: Int -> Int
f n = sum [1..n]
evalAndTimeFirstN :: Int -> Int -> Int -> IO()
evalAndTimeFirstN n max time =
if n == max
then return () -- in the following we have to calculate the time difference from start to now
else let str = ("(" ++ (show n) ++ ", " ++ (show $ f n) ++ ", "++ (show time)++ ")\n")
in putStrLn str >> evalAndTimeFirstN (n+1) max time -- here we have to calculate the time difference
main :: IO()
main = evalAndTimeFirstN 1 5 0
I don't quite see how I have to introduce the timing here. (The Int for time probably has to be replaced with something else.)
You probably want something like this. Adapt the following basic example as needed for your recursive function.
import Data.Time.Clock
import Control.Exception (evaluate)
main :: IO ()
main = do
putStrLn "Enter a number"
n <- readLn
start <- getCurrentTime
let fact = product [1..n] :: Integer
evaluate fact -- this is needed, otherwise laziness would postpone the evaluation
end <- getCurrentTime
putStrLn $ "Time elapsed: " ++ show (diffUTCTime end start)
-- putStrLn $ "The result was " ++ show fact
Uncomment the last line to print the result (it gets very large very quickly).
I finally managed to find a solution. In this case we're measuring the "real" time in ms.
import Data.Time
import Data.Time.Clock.POSIX
f n = sum[0..n]
getTime = getCurrentTime >>= pure . (1000*) . utcTimeToPOSIXSeconds >>= pure . round
main = do
maxns <- getLine
let maxn = (read maxns)::Int
t0 <- getTime
loop 1 maxn t0
where loop n maxn t0|n==maxn = return ()
loop n maxn t0
= do
putStrLn $ "fun eval: " ++ (show n) ++ ", " ++ (show $ (f n))
t <- getTime
putStrLn $ "time: " ++ show (t-t0);
loop (n+1) maxn t0
I am trying to read information entered by the user and to parse it into the type Person, which uses the type Gender. To do so, I use this code:
data Person = Person String Int Gender String
data Gender = Male | Female | NotSpecified deriving Read
instance Show Gender where
show Male = "male"
show Female = "female"
show NotSpecified = "not specified"
instance Show Person where
show (Person n a g j) = "Person {name: " ++ n ++ ", age: " ++ show a ++
", gender: " ++ show g ++ ", job: " ++ j ++ "}"
readPersonMaybeT :: MaybeT IO ()
readPersonMaybeT = do
putStrLn "Name?:"
name <- getLine
putStrLn "Age?:"
ageStr <- getLine
putStrLn "Gender?:"
genderStr <- getLine
putStrLn "Job?:"
job <- getLine
let newPerson = Person name (read ageStr) (read genderStr) job
putStrLn $ show newPerson
Now I would like to make this more failsafe - to achieve this I tried to use the MaybeT monad. using this, I got this code:
readPersonMaybeT :: MaybeT IO ()
readPersonMaybeT = do
lift $ putStrLn "Name?:"
name <- lift getLine
lift $ putStrLn "Age?:"
ageStr <- lift getLine
lift $ putStrLn "Gender?:"
genderStr <- lift getLine
lift $ putStrLn "Job?:"
job <- lift getLine
let newPerson = Person name (read ageStr) (read genderStr) job
lift $ putStrLn "show newPerson"
It get compiles/loaded by the GHCI, but when I try to execute the readPersonMaybeT function I get the error-message
No instance for (Data.Functor.Classes.Show1 IO)
arising from a use of `print'
In a stmt of an interactive GHCi command: print it
How can I solve this issue? Writing this code, I used the wikibook about Monad Transformers.
EDIT: When I try to 'run' it with runMaybeT it gets executed, but it is not failsafe at all. Entering nonsense for the age for example still results in a output like
Person {name: 85, age: *** Exception: Prelude.read: no parse.
If you are doing the validation only after you have asked for all of the input, I would just use the IO monad and return a Maybe:
import Text.Read
import Control.Monad.Trans.Maybe
import Control.Monad.IO.Class
askPerson :: IO (Maybe Person)
askPerson = do
name <- putStr "Name? " >> getLine
a <- putStr "Age? " >> getLine
g <- putStr "Gender? " >> getLine
return $ do age <- readMaybe a
gender <- readMaybe g
return $ Person name age gender
Note how we are using the Maybe monad in the return statement.
I would use MaybeT if you want to quit asking for input once they enter an invalid value --
askPersonT :: MaybeT IO Person
askPersonT = do
name <- liftIO $ putStr "Name? " >> getLine
age <- MaybeT $ fmap readMaybe $ putStr "Age? " >> getLine
gender <- MaybeT $ fmap readMaybe $ putStr "Gender? " >> getLine
return $ Person name age gender
doit = runMaybeT askPersonT
If the user enters an invalid age they won't be asked for a gender.
I'm having trouble directing flow though a pipeline with haskell-pipes. Basically, I analyze a bunch of files and then I have to either
print results to the terminal in a human-friendly way
encode results to JSON
The chosen path depends upon a command line option.
In the second case, I have to output an opening bracket, then every incoming value followed by a comma and then a closing bracket. Currently insertCommas never terminates, so the closing bracket is never outputted.
import Pipes
import Data.ByteString.Lazy as B
import Data.Aeson (encode)
insertCommas :: Consumer B.ByteString IO ()
insertCommas = do
first <- await
lift $ B.putStr first
for cat $ \obj -> lift $ do
putStr ","
B.putStr obj
jsonExporter :: Consumer (FilePath, AnalysisResult) IO ()
jsonExporter = do
lift $ putStr "["
P.map encode >-> insertCommas
lift $ putStr "]"
exportStream :: Config -> Consumer (FilePath, AnalysisResult) IO ()
exportStream conf =
case outputMode conf of
JSON -> jsonExporter
_ -> P.map (export conf) >-> P.stdoutLn
main :: IO ()
main = do
-- The first two lines are Docopt stuff, not relevant
args <- parseArgsOrExit patterns =<< getArgs
ins <- allFiles $ args `getAllArgs` argument "paths"
let conf = readConfig args
runEffect $ each ins
>-> P.mapM analyze
>-> P.map (filterResults conf)
>-> P.filter filterNulls
>-> exportStream conf
AFAIK a Consumer cannot detect the end of a stream. In order to do that you need to use a Pipes.Parser and invert the control.
Here is a Parser which inserts commas between String elements:
import Pipes
import qualified Pipes.Prelude as P
import Pipes.Parse (draw, evalStateT)
commify = do
lift $ putStrLn "["
m1 <- draw
case m1 of
Nothing -> lift $ putStrLn "]"
Just x1 -> do
lift $ putStrLn x1
let loop = do mx <- draw
case mx of
Nothing -> lift $ putStrLn "]"
Just x -> lift (putStr "," >> putStrLn x) >> loop
loop
test1 = evalStateT commify ( mapM_ yield (words "this is a test") )
test2 = evalStateT commify P.stdinLn
To handle the different output formats I would probably make both formats a Parser:
exportParser = do
mx <- draw
case mx of
Nothing -> return ()
Just x -> (lift $ putStrLn $ export x) >> exportParser
and then:
let parser = case outputMode of
JSON -> commify
_ -> exportParser
evalStateT parser (P.mapM analyze
>-> P.map (filterResults conf)
>-> P.filter filterNulls)
There is probably a slicker way to write exportParser in terms of foldAllM. You can also use the MaybeT transformer to more succinctly write the commify parser. I've written both out explicitly to make them easier to understand.
I think you should 'commify' with pipes-group. It has an intercalates, but not an intersperse, but it's not a big deal to write. You should stay away from the Consumer end, I think, for this sort of problem.
{-#LANGUAGE OverloadedStrings #-}
import Pipes
import qualified Pipes.Prelude as P
import qualified Data.ByteString.Lazy.Char8 as B
import Pipes.Group
import Lens.Simple -- or Control.Lens or Lens.Micro or anything with view/^.
import System.Environment
intersperse_ :: Monad m => a -> Producer a m r -> Producer a m r
intersperse_ a producer = intercalates (yield a) (producer ^. chunksOf 1)
main = do
args <- getArgs
let op prod = case args of
"json":_ -> yield "[" *> intersperse_ "," prod <* yield "]"
_ -> intersperse_ " " prod
runEffect $ op producer >-> P.mapM_ B.putStr
putStrLn ""
where
producer = mapM_ yield (B.words "this is a test")
which give me this
>>> :main json
[this,is,a,test]
>>> :main ---
this is a test
I have this simple code which reads a string and prints it, indefinitely.
main :: IO ()
main = getLine >>= putStrLn >> main
Now I want to exit after the getLine call if the line is either "quit" or "exit".
My attempt:
main :: IO ()
main = do
line <- getLine
if line == "exit" || line == "quit"
then return ()
else putStrLn line >> main
Doesn't look idiomatic to me. Is there a better way?
Control.Monad.unless (and it's slightly more popular cousin, when) abstract this pattern out of your code:
import Control.Monad (unless)
main = do
line <- getLine
unless (line == "exit" || line == "quit") $ do
putStrLn line
main
-- or
when (line /= "exit" && line /= "quit") $ do
putStrLn line
main
A conditional return () followed by unconditional code won't do the trick, as return is just a function, not a flow control keyword as in most other languages.
Using pipes-4.0:
import Pipes
import qualified Pipes.Prelude as P
main = runEffect $
P.stdinLn >-> P.takeWhile (`notElem` ["quit", "exit"]) >-> P.stdoutLn
It seems that you are concerned about the sequential feel of the code because of using if/else and the do notation. You can try something like:
main = getLine >>= proc
where
proc s | s == "exit" || s == "quit" = return ()
| otherwise = putStrLn s >> main
An attempt to be fashionable:
module Main where
import Control.Monad
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Class
import System.IO
isValid s = s ≠ "quit" && s ≠ "exit"
getL ∷ MaybeT IO String
getL = do s ← lift getLine
guard (isValid s)
return s
main = runMaybeT main' where
main' = do
lift $ putStr "Enter line: "
lift $ hFlush stdout
s ← getL
lift $ putStrLn $ "Your line is: " ⧺ s
main'
We can create a helper function that repeats a given action while it returns value:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
while :: (Monad m) => MaybeT m b -> m ()
while k = runMaybeT (forever k) >> return ()
Once k returns mzero, the loop stops. Then we can use it nicely to interrupt the loop at any place using the standard MonadPlus combinators:
main = while $ do
l <- lift getLine
guard $ l /= "quit"
lift $ putStrLn l
Or on one line:
main = while $ mfilter (/= "quit") (lift getLine) >>= lift . putStrLn
Update: Perhaps the simplest solutions is using whileJust_ from monad-loops:
isValid s | s /= "quit" = Just s
| otherwise = Nothing
main = whileJust_ (isValid `liftM` getLine) putStrLn