It's a followup question of Functions to Polymorphic data types
Data type Question
models a question/answer with a Message
(the text of the question) and a function (String -> a
) that maps user's input to the result of the question:
data Question where
Simple :: (Typeable a, Show a) => Message -> (String -> a) -> Question
This CLI program should first gets the name of the Question
, find an instance using getQuestion
function and then run the Question
and print out the result.
{-# LANGUAGE GADTs #-}
import Data.Typeable
type Message = String
data Question where
Simple :: (Typeable a, Show a) => Message -> (String -> a) -> Question
-- more constructors
yourName :: Question
yourName = Simple "Your name?" id
yourWeight :: Question
yourWeight = Simple "What is your weight?" (read :: String -> Int)
getQuestion :: String -> Question
getQuestion "name" = yourName
getQuestion "weight" = yourWeight
runQuestion :: (Typeable a, Show a) => Question -> IO a
runQuestion (Simple message parser) = do
putStrLn message
ans <- getLine
return $ parser ans
main = getLine >>= (runQuestion . getQuestion) >>= print
Type checking fails at here: runQuestion :: (Typeable a, Show a) => Question -> IO a
with No instance for (Typeable a0) arising from a use of ‘runQuestion’
.
If I remove the class constraints (runQuestion :: Question -> IO a
) then I get No instance for (Show a0) arising from a use of ‘print
.
This type
Question -> IO a
means "a function that accepts a Question
and returns an IO a
for whatever a
the caller wants". This is obviously wrong; some questions have an Int
answer and some have a String
answer, but no question has an answer that can on demand be Int
, String
, or whatever else we may want.
If all you need from the answer is the ability to show itself, just return a shown answer as an IO String
.
type Message = String
data Question = Simple Message (String -> String)
-- more constructors
yourName :: Question
yourName = Simple "Your name?" show
yourWeight :: Question
yourWeight = Simple "What is your weight?" (show . (read :: String -> Int))
getQuestion :: String -> Question
getQuestion "name" = yourName
getQuestion "weight" = yourWeight
runQuestion :: Question -> IO String
runQuestion (Simple message parser) = do
putStrLn message
ans <- getLine
return $ parser ans
main = getLine >>= (runQuestion . getQuestion) >>= putStrLn
Otherwise you can move existentiality to the answer, which you need to encapsulate in a new GADT:
type Message = String
data Question where
Simple :: Message -> (String -> Answer) → Question
-- more constructors
data Answer where
Easy :: (Typeable a, Show a) => a -> Answer
instance Show Answer where
show (Easy a) = show a
yourName :: Question
yourName = Simple "Your name?" Easy
yourWeight :: Question
yourWeight = Simple "What is your weight?" (Easy . (read :: String -> Int))
getQuestion :: String -> Question
getQuestion "name" = yourName
getQuestion "weight" = yourWeight
runQuestion :: Question -> IO Answer
runQuestion (Simple message parser) = do
putStrLn message
ans <- getLine
return $ parser ans
main = getLine >>= (runQuestion . getQuestion) >>= print
but this is IMHO overkill.