Re: [Haskell-beginners] data design for a questionnaire (retitled + update)

Hi folks, As the code (from my prior post) is not so relevant to the issue at hand, it was quite reasonably suggested that I should simplify my question somewhat. So here goes: In my model, I have a Question record type, which has a type parameter to take into account that answers can be evaluated to different types. For example, I can ask a question which requires a Double as an answer and another which requires an Int as an answer: data Question a = Question {text:: String, answer:: Maybe a} This question type is wrapped/boxed in another type to allow the different questions to referenced in a list. Hence, data Question' = QuestionS (Question String) | QuestionI (Question Int) | QuestionD (Question Double) and I can now store the questions as follows: questions = [ QuestionI {text="What's 1+1?", answer=Maybe 2} , QuestionD {text="What's 1.0+1.5?", answer=Maybe 2.5} ] Now to extract Question a from Question' I would have liked to write the following: extract :: Question' -> Question a extract q = case q of QuestionS x -> extractQString q QuestionI x -> extractQInt q QuestionD x -> extractQDouble q but instead, I had to produce the following instead: extractQString :: Question' -> Question String extractQString (QuestionS q) = q extractQInt :: Question' -> Question Int extractQInt (QuestionI q) = q extractQDouble :: Question' -> Question Double extractQDouble (QuestionD q) = q From what I understand so far, that specialized extraction or unboxing functions are the way to go unless one uses language extensions like GADTs which can be conceptually more complicated. Is this indeed the case, or are there other ways to achieve generalized type unboxing while remaining within the Haskell98 specification? Best, Alia

On Thu, Nov 24, 2011 at 3:32 AM, Alia
extract :: Question' -> Question a extract q = case q of QuestionS x -> extractQString q QuestionI x -> extractQInt q QuestionD x -> extractQDouble q
What is a caller supposed to do with this function? How were you imagining it would be called? In Haskell98 (or Haskell2010), the type signature `Question' -> Question a` would mean that the function returns a `Question` of whatever type the caller chooses. Since this is not what the function in fact does, the compiler rejects it. Antoine

extract :: Question' -> Question a extract q = case q of QuestionS x -> extractQString q QuestionI x -> extractQInt q QuestionD x -> extractQDouble q
Antoine wrote:
What is a caller supposed to do with this function? How were you imagining it would be called?
The sole purpose of the above function would be to unbox the inner parametrized (Question a) type from its (Question') wrapper. If it were possible I could write this: testQ' :: Question' -> Answer -> Bool testQ' q a = testQ (extract q) a given: testQ :: (Eq a) => Question a -> Answer -> Bool testQ q ans = case (correctAnswer q) of Nothing -> False Just x -> x == (answerFunc q $ ans) instead of this testQ' :: Question' -> Answer -> Bool testQ' q a = case q of QuestionS x -> testQS q a QuestionI x -> testQI q a QuestionD x -> testQD q a where testQS q a = testQ (extractQString q) a testQI q a = testQ (extractQInt q) a testQD q a = testQ (extractQDouble q) a In fact this seemingly redundant pattern emerges in all wrapper handling code, for instance: askQ' :: Question' -> IO Answer askQ' q = case q of QuestionS x -> askQS q QuestionI x -> askQI q QuestionD x -> askQD q where askQS q = askQ (extractQString q) askQI q = askQ (extractQInt q) askQD q = askQ (extractQDouble q) Hope this clarifies things somewhat. Alia Alia

For example, I can ask a question which requires a Double as an answer and another which requires an Int as an answer:
Might this be easier if you made all the answers strings? If you need to permit things like 0.5 vs .5 you could instead use a String->String normalisation function which could vary with the "type" of the answer but not affect the data type of the question.
It would also be easier to use strings if you wanted to store the questions in a database or load from a file.
Peter
On 24 Nov 2011, at 09:32, Alia
Hi folks,
As the code (from my prior post) is not so relevant to the issue at hand, it was quite reasonably suggested that I should simplify my question somewhat. So here goes:
In my model, I have a Question record type, which has a type parameter to take into account that answers can be evaluated to different types. For example, I can ask a question which requires a Double as an answer and another which requires an Int as an answer:
data Question a = Question {text:: String, answer:: Maybe a}
This question type is wrapped/boxed in another type to allow the different questions to referenced in a list. Hence,
data Question' = QuestionS (Question String) | QuestionI (Question Int) | QuestionD (Question Double)
and I can now store the questions as follows:
questions = [ QuestionI {text="What's 1+1?", answer=Maybe 2} , QuestionD {text="What's 1.0+1.5?", answer=Maybe 2.5} ]
Now to extract Question a from Question' I would have liked to write the following:
extract :: Question' -> Question a extract q = case q of QuestionS x -> extractQString q QuestionI x -> extractQInt q QuestionD x -> extractQDouble q
but instead, I had to produce the following instead:
extractQString :: Question' -> Question String extractQString (QuestionS q) = q
extractQInt :: Question' -> Question Int extractQInt (QuestionI q) = q
extractQDouble :: Question' -> Question Double extractQDouble (QuestionD q) = q
From what I understand so far, that specialized extraction or unboxing functions are the way to go unless one uses language extensions like GADTs which can be conceptually more complicated. Is this indeed the case, or are there other ways to achieve generalized type unboxing while remaining within the Haskell98 specification?
Best,
Alia
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

Peter Hall wrote:
Might this be easier if you made all the answers strings? If you need to permit things like 0.5 vs .5 you could instead > use a String->String normalisation function which could vary with the "type" of the answer but not affect the data type > of the question. It would also be easier to use strings if you wanted to store the questions in a database or load from a file.
Indeed that is exactly how I do it. All user entered answers are stored as strings, and are converted to the appropriate type by an answerFunc (see below) and compared against correct answers. The purpose of the answerFunc is simply to convert (String -> type of answer) and can include normalization if required. Fyi, the complete schema is as follows: data QuestionType = Open | Test | Choice deriving (Show, Eq) data Question a = Question { questionName :: Name , questionText :: QuestionText , questionType :: QuestionType , answerFunc :: (String -> a) , correctAnswer :: Maybe a , options :: Maybe [Option a] } deriving (Show) data Question' = QuestionS (Question String) | QuestionI (Question Int) | QuestionD (Question Double) deriving (Show) data QuestionSet = QuestionSet { qsetTitle :: String , qsetQuestions :: [Question'] , qsetPoints :: Double } deriving (Show) data Survey = Survey { surveyTitle :: String , surveyQuestionSets :: [QuestionSet] } deriving (Show)

Why not have:
data Question = Question
{ questionName :: Name
, questionText :: QuestionText
, questionType :: QuestionType
, answerFunc :: (String -> AnswerType)
, correctAnswer :: Maybe AnswerType
, options :: Maybe [Option AnswerType]
} deriving (Show)
data AnswerType = AnsD Double | AnsS String | AnsI Integer deriving (Show, Read)
Then, I'd personally make another change, why would you have a flat
structure with a questionType and then optional correctAnswer and
options fields? There's no type safety in that. I'd try:
data Answer = StraightAnswer (String -> AnswerType) | MultipleChoice
AnswerType [Option AnswerType]
data Question = Question
{ questionName :: Name
, questionText :: QuestionText
, answerFunc :: (String -> AnswerType)
, answer :: Answer
} deriving (Show)
If you are storing answers as string, just store them as "AnsD 5.589",
"AnsS \"Constantiople\"". Then with the read instance you can go:
let answer = read x :: AnswerType
On Thu, Nov 24, 2011 at 4:52 PM, Alia
Peter Hall wrote:
Might this be easier if you made all the answers strings? If you need to permit things like 0.5 vs .5 you could instead > use a String->String normalisation function which could vary with the "type" of the answer but not affect the data type > of the question. It would also be easier to use strings if you wanted to store the questions in a database or load from a file.
Indeed that is exactly how I do it. All user entered answers are stored as strings, and are converted to the appropriate type by an answerFunc (see below) and compared against correct answers. The purpose of the answerFunc is simply to convert (String -> type of answer) and can include normalization if required. Fyi, the complete schema is as follows:
data QuestionType = Open | Test | Choice deriving (Show, Eq)
data Question a = Question { questionName :: Name , questionText :: QuestionText , questionType :: QuestionType , answerFunc :: (String -> a) , correctAnswer :: Maybe a , options :: Maybe [Option a] } deriving (Show)
data Question' = QuestionS (Question String) | QuestionI (Question Int) | QuestionD (Question Double) deriving (Show)
data QuestionSet = QuestionSet { qsetTitle :: String , qsetQuestions :: [Question'] , qsetPoints :: Double } deriving (Show)
data Survey = Survey { surveyTitle :: String , surveyQuestionSets :: [QuestionSet] } deriving (Show)
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

David McBride wrote:
Why not have:
data Question = Question { questionName :: Name , questionText :: QuestionText , questionType :: QuestionType , answerFunc :: (String -> AnswerType) , correctAnswer :: Maybe AnswerType , options :: Maybe [Option AnswerType] } deriving (Show)
data AnswerType = AnsD Double | AnsS String | AnsI Integer deriving (Show, Read)
Then, I'd personally make another change, why would you have a flat structure with a questionType and then optional correctAnswer and options fields? There's no type safety in that. I'd try:
data Answer = StraightAnswer (String -> AnswerType) | MultipleChoice AnswerType [Option AnswerType]
data Question = Question { questionName :: Name , questionText :: QuestionText , answerFunc :: (String -> AnswerType) , answer :: Answer } deriving (Show)
If you are storing answers as string, just store them as "AnsD 5.589", "AnsS \"Constantiople\"". Then with the read instance you can go:
let answer = read x :: AnswerType
Thank you very much for the reply which is eye-opening. But I do have to spend time implementing your revised schema in the prior question handling functions to get my head around it (-: Best, Alia

Hi folks,
Just to wrap things up: I think I'm satisfied with the design below, mostly due to David McBride's advice.
My final code is a slight variation on his suggested course, but not by much. Many thanks to all those
who replied and helped with this problem.
participants (4)
-
Alia
-
Antoine Latter
-
David McBride
-
Peter Hall