Hi folks, As a way to get my head wrapped around haskell data design idioms, I'm translating a questionnaire data model (in python) to haskell, I would appreciate some advice and criticism on whether I'm going about this the right way. Here's the initial translation: <version1.hs> module Main where data QuestionType = Open | Test | Choice deriving (Show, Eq) data Question = Question { questionName :: String , questionText :: String , questionType :: QuestionType , answerType :: String , correctAnswer :: Maybe String , options :: Maybe [(String, String)] } deriving (Show, Eq) data QuestionSet = QuestionSet { qsetTitle :: String , qsetQuestions :: [Question] } deriving (Show, Eq) data Questionnaire = Questionnaire { questionnaireTitle :: String , questionnaireQuestionSets :: [QuestionSet] } deriving (Show, Eq) q1 = Question { questionName = "q1" , questionText = "What is our name?" , questionType = Open , answerType = "str" , correctAnswer = Nothing , options = Nothing } q2 = Question { questionName = "q2" , questionText = "What is 1+1?" , questionType = Test , answerType = "int" , correctAnswer = Just "2" , options = Nothing } q3 = Question { questionName = "q2" , questionText = "What is 2+1?" , questionType = Choice , answerType = "int" , correctAnswer = Just "3" , options = Just [("1", "2"), ("2", "3"), ("3", "4")] } qset = QuestionSet { qsetTitle = "simple questions" , qsetQuestions = [q1, q2, q3] } questionnaire = Questionnaire { questionnaireTitle = "a questionnaire" , questionnaireQuestionSets = [qset] } </version1.hs> In this first version, the Question record basically holds all possible fields for any kind of question, and fields which are not used are assigned Nothing thanks to the Maybe monad. The final type of the answer is specified as a string in the record for later conversion. Now I wanted to take into account a scenario where I don't need to specify the answerType by creating a polymorphic Question type which is specialized to take different answertypes. So I came up with this version: <version2.hs> module Main where import Text.Show.Functions -- type converters str = id int s = read s :: Int float s = read s :: Double data QuestionType = Open | Test | Choice deriving (Show, Eq) data Question a = Question { questionName :: String , questionText :: String , questionType :: QuestionType , answerFunc :: (String -> a) , correctAnswer :: Maybe a , options :: Maybe [(String, a)] } deriving (Show) data QuestionSet a = QuestionSet { qsetTitle :: String , qsetQuestions :: [Question a] } deriving (Show) data Questionnaire a = Questionnaire { questionnaireTitle :: String , questionnaireQuestionSets :: [QuestionSet a] } deriving (Show) q1 = Question { questionName = "q1" , questionText = "What is our name?" , questionType = Open , answerFunc = id , correctAnswer = Nothing , options = Nothing } q2 = Question { questionName = "q2" , questionText = "What is 1+1?" , questionType = Test , answerFunc = int , correctAnswer = Just 2 , options = Nothing } q3 = Question { questionName = "q2" , questionText = "What is 2+1?" , questionType = Choice , answerFunc = int , correctAnswer = Just 3 , options = Just [("a", 2), ("b", 3), ("c", 4)] } qset = QuestionSet { qsetTitle = "simple questions" , qsetQuestions = [q1, q2, q3] } questionnaire = Questionnaire { questionnaireTitle = "a questionnaire" , questionnaireQuestionSets = [qset] } </version2.hs> The problem is that now all the questionsets, and questionnaires expect the same specialized type of question which is obviously not what I want and I get this error: Couldn't match expected type `[Char]' with actual type `Int' Expected type: Question String Actual type: Question Int In the expression: q2 In the `qsetQuestions' field of a record So what am I doing wrong here? Should I just revert back to version 1 which I sense is not the haskell way. Many thanks for any help or advice. Alia
Hi Alia, Here's my suggestion. It does compile. Of course, there are lots of ways to approach this kind of thing. My choice was to refactor it so that the Answer type contains the correct answer and the possible answers, as appropriate. Then when you write the method that prompts the user for an answer, and the method that checks the user's answer, you can pattern match on the Answer type. ----- module Main where data Answer = Open | Test { correctIntAnswer :: Int } | Choice { correctStringAnswer :: Int, options :: [(String, String)] } deriving (Show, Eq) data Question = Question { questionName :: String , questionText :: String , answer :: Answer } deriving (Show, Eq) data QuestionSet = QuestionSet { qsetTitle :: String , qsetQuestions :: [Question] } deriving (Show, Eq) data Questionnaire = Questionnaire { questionnaireTitle :: String , questionnaireQuestionSets :: [QuestionSet] } deriving (Show, Eq) q1 = Question { questionName = "q1" , questionText = "What is our name?" , answer = Open } q2 = Question { questionName = "q2" , questionText = "What is 1+1?" , answer = Test 2 } q3 = Question { questionName = "q2" , questionText = "What is 2+1?" , answer = Choice 3 [("1", "2"), ("2", "3"), ("3", "4")] } qset = QuestionSet { qsetTitle = "simple questions" , qsetQuestions = [q1, q2, q3] } questionnaire = Questionnaire { questionnaireTitle = "a questionnaire" , questionnaireQuestionSets = [qset] }
Oops, I just realised that you have a couple of other threads with pretty much the same question, and you have answers on them.
participants (2)
-
Alia -
Amy de Buitléir