Functions of GADTs

霸气de小男生 提交于 2019-12-11 05:52:32

问题


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.


回答1:


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.




回答2:


The error you report is not the only error.

Let's put on the special spectacles which show the things usually kept invisible by "type inference".

Firstly, the data constructor:

Simple :: forall a. (Typeable a, Show a) =>
          Message -> (String -> a) -> Question

Effectively, a value of type Question looks like

Simple {a}{typeableDict4a}{showDict4a} message parser

where I've written the invisible things in braces. The constructor packs up a type and the two typeclass dictionaries that give the implementations for the members of Typeable and Show.

Now let's have the main program. I've renamed the type variable to make a point.

runQuestion :: forall b. (Typeable b, Show b) => Question -> IO b

The type to be given back is chosen by the caller of runQuestion, separately from whatever type is packed inside the argument of type Question. Now let's fill in the invisible components in the program itself.

runQuestion {b}{typeableDict4b}{showDict4b}
  (Simple {a}{typeableDict4a}{showDict4a} message parser) = do
                        -- so parser :: String -> a
      putStrLn message  -- ok, as message :: String
      ans <- getLine    -- ensures ans :: String
  return $ parser ans   -- has type IO a, not IO b

The parser computes a value of the type a packed up in the Question, which is totally separate from the type b passed directly to runQuestion. The program does not typecheck because there's a conflict between two types which can be made different by the program's caller.

Meanwhile, let's look at print

print :: forall c. Show c => c -> IO ()

When you write

main = getLine >>= (runQuestion . getQuestion) >>= print

you get

main = getLine >>=
  (runQuestion {b}{typeableDict4b}{showDict4b} . getQuestion) >>=
  print {b}{showDict4b}

and as the return type of runQuestion {b} is IO b, it must be the case that print's c type is the same as runQuestion's b type, but other than that, there is nothing to determine which type b is, or why it is an instance either of Typeable or Show. With the type annotation, the need for Typeable shows up first (in the runQuestion call); without, the need for Show in print causes the complaint.

The real problem, is that somehow, you seem to want runQuestion to deliver a thing in whatever type is hidden inside the question, as if you could somehow write a (dependently typed) program like

typeFrom :: Question -> *
typeFrom (Simple {a}{typeableDict4a}{showDict4a} message parser) = a

runQuestion :: (q :: Question) -> IO (typeFrom q)

That's a perfectly sensible thing to want, but it isn't Haskell: there's no way to name "the type packed up inside that argument". Everything which involves that type has to live in the scope of the case analysis or pattern match which exposes it. It's your attempt to do the print outside that scope that won't be allowed.



来源:https://stackoverflow.com/questions/39147517/functions-of-gadts

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!