这是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 a
与No 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
。
答案 0 :(得分:5)
此类型
Question -> IO a
表示“接受Question
的函数,并为调用者想要的IO a
”返回a
。这显然是错的;有些问题有Int
个答案,有些问题有String
个答案,但没有问题可以根据需要Int
,String
或其他任何我们想要的答案
如果答案中只需要显示自己的能力,只需将显示的答案作为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
我在大括号里写下了看不见的东西。构造函数打包一个类型和两个类型类词典,为Typeable
和Show
的成员提供实现。
现在让我们有主程序。我已经重命名了类型变量来表达观点。
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
调用中);没有,Typeable
中runQuestion
的需要会导致投诉。
真正的问题是,不知何故,你似乎希望Show
提供隐藏在问题中的任何类型的东西,好像你可以以某种方式编写一个(依赖类型)程序,如
print
这是一个非常明智的想法,但它并不是Haskell:没有办法命名&#34;这个类型包含在那个论点中#34;。涉及该类型的所有内容都必须存在于案例分析或模式匹配的范围内,从而暴露它。您试图在runQuestion
之外的范围内进行,但不允许这样做。