Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: how do you check runtime types on IO?

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."
like image 527
RichardForrester Avatar asked Jun 09 '19 10:06

RichardForrester


2 Answers

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
like image 190
Willem Van Onsem Avatar answered Sep 25 '22 21:09

Willem Van Onsem


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.)

like image 40
Robin Zigmond Avatar answered Sep 21 '22 21:09

Robin Zigmond