I'm making my way through some introductory material on Haskell and trying to complete this silly Rock, Paper, Scissors implementation for the command line.
I would think that a type guard on the input would be good enough to convince the compiler that the input is of the type RPS
, but alas, it is not.
How does one go about telling the compiler that input data is of one type or another?
data RPS = Rock | Paper | Scissors
_shoot :: RPS -> RPS -> String
_shoot Rock Paper = "Paper beats rock, you win!"
_shoot Paper Rock = "Paper beats rock, you loose."
_shoot Rock Scissors = "Rock beats scissors, you loose."
_shoot Scissors Rock = "Rock beats scissors, you win!"
_shoot Paper Scissors = "Scissors beats paper, you win!"
_shoot Scissors Paper = "Scissors beats paper, you loose!"
_shoot Rock Rock = "Tie!"
_shoot Scissors Scissors = "Tie!"
_shoot Paper Paper = "Tie!"
isRPS :: String -> Bool
isRPS s = elem s ["Rock", "Paper", "Scissors"]
main :: IO ()
main = do
putStrLn "Rock, Paper, or Scissors?"
choice <- getLine
if isRPS choice -- this was my idea but is apparently not good enough
then putStrLn (_shoot choice Rock)
-- ^^^^^^
-- Couldn't match type ‘[Char]’ with ‘RPS’ Expected type: RPS Actual type: String
else putStrLn "Invalid choice."
You did not transform the choice
(which is a String
) to an RPS
, or even better a Maybe RPS
:
readRPS :: String -> Maybe RPS
readRPS "rock" = Just Rock
readRPS "paper" = Just Paper
readRPS "scissors" = Just Scissors
readRPS _ = Nothing
Here we thus return a Just x
given the input is valid (with x
the corresponding RPS
item), or Nothing
if the string is not a valid option.
We can then implement this as:
import Data.Char(toLower)
main :: IO ()
main = do
putStrLn "Rock, Paper, or Scissors?"
choice <- getLine
case readRPS (map toLower choice) of
Just rps -> putStrLn (_shoot rps Rock)
Nothing -> putStrLn "Invalid choice."
main
You're nearly there, you just need the read function to convert the user's string to your RPS
data type.
The first thing you need to do is to make RPS
an instance of the Read
typeclass. This can be done easily by amending your data
declaration to:
data RPS = Rock | Paper | Scissors deriving Read
what deriving Read
does is give RPS
a default instance of the Read
typeclass, which works in the obvious way: read "Rock"
will become Rock
and so on, provided the compiler knows you're using read
in a context where a value of type RPS
is expected.
Then all you need to do, in your main
function, is change this:
putStrLn (_shoot choice Rock)
to
putStrLn (_shoot (read choice) Rock)
Since _shoot
has a type signature telling GHC that its first argument must be an RPS
value, it will know to use the instance of read
defined for your RPS
type, and all should be well, since you've already restricted the valid user choices to those 3 specific strings.
(Note that for larger programs there are safer and better ways of handling things like this - see Willem's answer for one simple approach - but this is fine for a basic learning exercise.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With