Search code examples
haskellgadt

Functions of GADTs


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.


Solution

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