GADT的功能

时间:2016-08-25 14:06:33

标签: haskell gadt

这是Functions to Polymorphic data types

的后续问题

数据类型Question使用Message(问题文本)和将用户输入映射到问题结果的函数(String -> a)对问题/答案进行建模:

data Question where
  Simple :: (Typeable a, Show a) => Message -> (String -> a) -> Question

此CLI程序应首先获取Question的名称,使用getQuestion函数查找实例,然后运行Question并打印出结果。

{-# 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

类型检查在此处失败:runQuestion :: (Typeable a, Show a) => Question -> IO aNo instance for (Typeable a0) arising from a use of ‘runQuestion’

如果我删除了类约束(runQuestion :: Question -> IO a),那么我得到No instance for (Show a0) arising from a use of ‘print

2 个答案:

答案 0 :(得分:5)

此类型

Question -> IO a

表示“接受Question的函数,并为调用者想要的IO a ”返回a 。这显然是错的;有些问题有Int个答案,有些问题有String个答案,但没有问题可以根据需要IntString或其他任何我们想要的答案

如果答案中只需要显示自己的能力,只需将显示的答案作为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

否则你可以将存在性移到答案中,你需要将其封装在一个新的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

但这是IMHO矫枉过正。

答案 1 :(得分:4)

您报告的错误不是唯一的错误。

让我们戴上特殊的眼镜,通过&#34;类型推断&#34;来展示通常看不见的东西。

首先,数据构造函数:

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

实际上,类型Question的值类似于

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

我在大括号里写下了看不见的东西。构造函数打包一个类型和两个类型类词典,为TypeableShow的成员提供实现。

现在让我们有主程序。我已经重命名了类型变量来表达观点。

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

要返回的类型由runQuestion的调用者选择,与类型Question的参数内打包的任何类型分开。现在让我们填写程序本身的隐形组件。

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

parser计算a中打包的Question类型的值,该值与直接传递给b的{​​{1}}类型完全分开。该程序没有进行类型检查,因为两种类型之间存在冲突,可以通过程序的调用者使其不同。

同时,让我们看一下runQuestion

print

写作时

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

你得到了

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

并且main = getLine >>= (runQuestion {b}{typeableDict4b}{showDict4b} . getQuestion) >>= print {b}{showDict4b} 的返回类型为runQuestion {b}IO b&#39; print类型与{{1}相同{}} c类型,但除此之外,还有 nothing 来确定runQuestion是哪种类型,或者为什么它是{{b的实例。 1}}或b。使用类型注释,首先需要显示Typeable(在Show调用中);没有,TypeablerunQuestion的需要会导致投诉。

真正的问题是,不知何故,你似乎希望Show提供隐藏在问题中的任何类型的东西,好像你可以以某种方式编写一个(依赖类型)程序,如

print

这是一个非常明智的想法,但它并不是Haskell:没有办法命名&#34;这个类型包含在那个论点中#34;。涉及该类型的所有内容都必须存在于案例分析或模式匹配的范围内,从而暴露它。您试图在runQuestion之外的范围内进行,但不允许这样做。