Haskell: как вы проверяете типы времени выполнения на IO? - PullRequest
6 голосов
/ 09 июня 2019

Я пробираюсь через некоторые вводные материалы по Haskell и пытаюсь завершить эту глупую реализацию Rock, Paper, Scissors для командной строки.

Я бы подумал, что защита типа на входе будет достаточно хороша, чтобы убедить компилятор, что вход имеет тип RPS, но, увы, это не так.

Как можно сказать компилятору, что входные данные того или иного типа?


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

Ответы [ 2 ]

11 голосов
/ 09 июня 2019

Вы не преобразовали choice (то есть String) в RPS или, что еще лучше, Maybe RPS:

readRPS :: String -> Maybe RPS
readRPS "rock" = Just Rock
readRPS "paper" = Just Paper
readRPS "scissors" = Just Scissors
readRPS _ = Nothing

Здесь мы, таким образом, возвращаем Just xучитывая, что ввод действителен (с x соответствующим RPS элементом) или Nothing, если строка не является допустимым параметром.

Затем мы можем реализовать это как:

import Data.Char(toLower)

main :: IO ()
main = do
    putStrLn "Rock, Paper, or Scissors?"
    choice <- getLine
    case <b>readRPS (map toLower choice)</b> of
        <b>Just rps</b> -> putStrLn (_shoot rps Rock) 
        Nothing -> putStrLn "Invalid choice."
    main
5 голосов
/ 09 июня 2019

Вы почти на месте, вам просто нужна функция read , чтобы преобразовать строку пользователя в тип данных RPS.

Первое, что вам нужно сделать, это сделать RPS экземпляром класса типов Read. Это можно легко сделать, внеся изменения в data декларацию:

data RPS = Rock | Paper | Scissors deriving Read

то, что делает deriving Read, дает RPS экземпляр по умолчанию класса типов Read, который работает очевидным образом: read "Rock" станет Rock и так далее, при условии, что компилятор знает, что вы используете read в контексте, где ожидается значение типа RPS.

Тогда все, что вам нужно сделать в функции main, это изменить это:

putStrLn (_shoot choice Rock)

до

putStrLn (_shoot (read choice) Rock)

Так как _shoot имеет сигнатуру типа, сообщающую GHC, что его первый аргумент должен быть значением RPS, он будет знать, использовать ли экземпляр read, определенный для вашего типа RPS, и все должно быть в порядке, так как вы уже ограничили допустимый выбор пользователя этими 3 конкретными строками.

(Обратите внимание, что для более крупных программ существуют более безопасные и более эффективные способы обработки подобных вещей - см. Ответ Виллема для одного простого подхода - но это хорошо для базового учебного упражнения.)

...