2016-08-25 4 views
1

Это Followup вопрос Functions to Polymorphic data typesФункции GADTs

типа данных 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.

ответ

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 

но это имхо перебор.

+0

Можем ли мы сделать вопрос экземпляром Monad или Category? Мне нравится, что он должен быть сложен: «Вопрос o -> (o -> Question o ') -> Вопрос o'' Таким образом, во время разработки я могу гарантировать, что вопросы будут упорядочены и будут заданы в« рациональном »порядке , – homam

+0

Сохраняя возможность 'getQuestion :: String -> Question', возможно, первый вопрос. – homam

+0

Оригинальный 'Question' имеет вид' * '(без параметров типа). Если вы хотите, чтобы он был параметризован по типу ответа, это совершенно другой чайник из рыбы. У вас может быть 'String-> Question', а' String-> Question a' имеет ту же проблему, с которой вы сталкиваетесь с вопросом -> IO a'. –

4

Ошибка, о которой вы сообщаете, является не единственной ошибкой.

Наденьте специальные очки, которые показывают вещи, которые обычно не видны «типом вывода».

Во-первых, конструктор данных:

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 передается непосредственно в 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, он должен быть дело print's c тип то же самое, что и runQuestionb тип, но кроме этого есть ничего, чтобы определить, какой тип b является, или почему он является экземпляром из Typeable или Show. При аннотации типа появляется необходимость в Typeable (в вызове runQuestion); без, потребность в Show в print вызывает жалобу.

Реальная проблема, в том, что каким-то образом, вы, кажется, хотят runQuestion доставить вещь в любой тип спрятан вопрос, как если бы вы могли как-то написать программу (в зависимости набранный) как

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

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

Это совершенно разумная вещь, которую нужно хотеть, но это не Haskell: нет способа назвать «тип, упакованный внутри этого аргумента». Все, что связано с этим типом, должно жить в рамках анализа случая или соответствия шаблону, который его раскрывает. Это ваша попытка сделать print вне этой области действия, которая не будет разрешена.

+0

Я думал, что я будут работать на зависимые типы. Я хочу создать рациональный вопрос/ответ; это означает, что последовательность Вопросов должна быть проверена во время компиляции, например: 'Question a b {run :: a -> Input -> Либо (b, Вопрос b)}'. Последний вопрос в любой цепочке заканчивается на 'Left finalResult'. Верхние вопросы в цепочке не принимают ввода, возможно, 'a =()'. Вопросы, вероятно, образуют категорию.'b' результат зависит от результата предыдущего вопроса' a'. 'a' и' b' являются Typeable, Show и Read, потому что я хочу продолжить цепочку, получая и кормя ее «a» откуда-то (например, Redis). – homam

+1

@homam Вы можете показать конкретный пример цепочки вопросов? Мне сложно представить, как они могут сочинять. Чтобы быть конкретным, какой вопрос вы можете связать по обе стороны от «Что такое ваш вес»? –

+0

@ н.м. Если 'Simple :: lastResult -> Input -> Либо результат (результат, результат вопроса _)' then 'yourName = Simple $ \ _ i -> Right (i, yourWeight)' 'yourWieght = Simple $ \ name i -> let res = (name, read i :: Int) в левом res'. Я могу представить, что верно 'yourName >>> yourWeight'. – homam