Аннотации программного типа в Haskell

При метапрограммировании может быть полезно (или необходимо) передать в систему типов Haskell информацию о типах, которые известны вашей программе, но не выводятся в Hindley-Milner. Есть ли в Haskell библиотека (или расширение языка и т. Д.), Которая предоставляет средства для этого, то есть программные аннотации типов?

Рассмотрим ситуацию, когда вы работаете с гетерогенным списком (реализованным с использованием библиотеки Data.Dynamic или, скажем, экзистенциальной количественной оценки) и хотите отфильтровать список до стандартного однородно типизированного списка Haskell. Вы можете написать такую ​​функцию, как

import Data.Dynamic
import Data.Typeable

dynListToList :: (Typeable a) => [Dynamic] -> [a]
dynListToList = (map fromJust) . (filter isJust) . (map fromDynamic)

и вызовите его с аннотацией ручного типа. Например,

foo :: [Int]
foo = dynListToList [ toDyn (1 :: Int)
                    , toDyn (2 :: Int)
                    , toDyn ("foo" :: String) ]

Здесь foo - список [1, 2] :: [Int]; это прекрасно работает, и вы снова на твердой почве, где система типов Haskell может делать свое дело.

Теперь представьте, что вы хотите сделать то же самое, но (а) в то время, когда вы пишете код, вы не знаете, каким должен быть тип списка, созданного вызовом dynListToList, но (б) ваша программа действительно содержит информация, необходимая для выяснения этого, только (c) она не в форме, доступной для системы типов.

Например, предположим, что вы случайным образом выбрали элемент из неоднородного списка и хотите отфильтровать список по этому типу. Используя средства проверки типов, предоставляемые Data.Typeable, ваша программа имеет всю информацию, необходимую для этого, но, насколько я могу судить - в этом суть вопроса - нет никакого способа передать ее системе типов. Вот какой-то псевдо-Haskell, который показывает, что я имею в виду:

import Data.Dynamic
import Data.Typeable

randList :: (Typeable a) => [Dynamic] -> IO [a]
randList dl = do
    tr <- randItem $ map dynTypeRep dl
    return (dynListToList dl :: [<tr>])  -- This thing should have the type
                                         -- represented by `tr`

(Предположим, randItem выбирает случайный элемент из списка.)

Без аннотации типа для аргумента return компилятор сообщит вам, что он имеет "неоднозначный тип", и попросит вас указать его. Но вы не можете предоставить аннотацию типа вручную, потому что тип неизвестен во время записи (и может варьироваться); однако тип известен во время выполнения, хотя и в форме, которую система типов не может использовать (здесь необходимый тип представлен значением tr, a TypeRep - подробности см. в Data.Typeable) .

Псевдокод :: [<tr>] - это волшебство, которое я хочу совершить. Есть ли способ программно предоставить системе типов информацию о типах; то есть с информацией о типе, содержащейся в значении вашей программы?

В основном я ищу функцию с (псевдо) типом ??? -> TypeRep -> a, которая принимает значение типа, неизвестного системе типов Haskell, и TypeRep и говорит: «Поверьте мне, компилятор, я знаю, что делаю. значение, представленное этим TypeRep. " (Обратите внимание, что это не то, что делает unsafeCoerce.)

Или есть что-то совершенно другое, что привело меня в то же место? Например, я могу представить себе расширение языка, которое позволяет присваивать переменные типа, как усовершенствованная версия расширения, включающая переменные типа с ограниченной областью видимости.

(Если это невозможно или крайне непрактично, - например, это требует упаковки полного интерпретатора, подобного GHCi, в исполняемый файл - попробуйте объяснить, почему.)


person pash    schedule 05.09.2012    source источник
comment
С import Data.Maybe, dynListToList = mapMaybe fromDynamic.   -  person Antal Spector-Zabusky    schedule 05.09.2012
comment
@ AntalS-Z - Хорошее упрощение.   -  person pash    schedule 05.09.2012
comment
Как вы сказали, в вашем примере информация о типе доступна только во время выполнения, поэтому вы не можете получить ее на этапе проверки типа (что происходит во время компиляции). Невозможно когда-либо реализовать ваш пример псевдокода, потому что он действительно не имеет смысла.   -  person shang    schedule 05.09.2012
comment
Мне сложно отслеживать, сколько архитектурных красных флажков связано с этим вопросом. Если вам нужно это сделать, вы, вероятно, получите большую пользу, если остановите опытного хаскеллера в коридоре и поговорите с ним час или два о том, как начать все сначала.   -  person Daniel Wagner    schedule 05.09.2012
comment
@DanielWagner: Я бы хотел, чтобы в коридоре был опытный Хаскеллер! Тем не менее, это подразумевается как исследование ограничений системы типов Haskell. Но действительно существуют проблемы, требующие либо выполнения чего-то очень похожего на это, либо (что еще хуже с архитектурной точки зрения) полного окончательного обхода системы типов.   -  person pash    schedule 11.09.2012


Ответы (3)


Нет, ты не можешь этого сделать. Короче говоря, вы пытаетесь написать функцию с зависимой типизацией, а Haskell не является языком с зависимой типизацией; вы не можете поднять значение TypeRep до истинного типа, поэтому нет возможности записать тип желаемой функции. Чтобы объяснить это более подробно, я сначала покажу, почему то, как вы сформулировали тип randList, на самом деле не имеет смысла. Затем я объясню, почему вы не можете делать то, что хотите. Наконец, я вкратце выскажу пару мыслей о том, что на самом деле делать.

Экзистенциальные

Сигнатура вашего типа для randList не может означать то, что вы хотите. Помня, что все переменные типа в Haskell универсально количественно определены, он читает

randList :: forall a. Typeable a => [Dynamic] -> IO [a]

Таким образом, я имею право называть это, скажем, randList dyns :: IO [Int] где угодно; Я должен иметь возможность предоставить возвращаемое значение для всех a, а не просто для некоторых a. Думая об этом как об игре, в ней вызывающий может выбрать a, а не саму функцию. То, что вы хотите сказать (это недопустимый синтаксис Haskell, хотя вы можете перевести его в действительный Haskell, используя экзистенциальный тип данных 1), больше похоже на

randList :: [Dynamic] -> (exists a. Typeable a => IO [a])

Это обещает, что элементы списка относятся к некоторому типу a, который является экземпляром Typeable, но не обязательно любым таким типом. Но даже с этим у вас будут две проблемы. Во-первых, даже если бы вы могли составить такой список, что бы вы с ним сделали? А во-вторых, оказывается, что его вообще нельзя даже построить.

Поскольку все, что вы знаете об элементах экзистенциального списка, это то, что они являются экземплярами Typeable, что вы можете с ними сделать? Глядя на документацию, мы видите, что есть только две функции 2, которые принимают экземпляры Typeable:

Таким образом, все, что вы знаете о типе элементов в списке, - это то, что вы можете вызывать для них typeOf и cast. Поскольку мы никогда не сможем с пользой делать с ними что-либо еще, наше экзистенциальное может быть (опять же, недействительным Haskell)

randList :: [Dynamic] -> IO [(TypeRep, forall b. Typeable b => Maybe b)]

Это то, что мы получим, если применим typeOf и cast к каждому элементу нашего списка, сохраним результаты и выбросим теперь бесполезное экзистенциально типизированное исходное значение. Ясно, что TypeRep часть этого списка бесполезна. И второй половины списка тоже нет. Поскольку мы вернулись к универсально определяемому типу, вызывающий randList снова имеет право запросить Maybe Int, Maybe Bool или Maybe b для любого (типизированного) b по своему выбору. (Фактически, у них немного больше возможностей, чем раньше, поскольку они могут создавать экземпляры разных элементов списка для разных типов.) Но они не могут понять, какой тип они преобразовывают из, если они еще не знайте это - вы все еще потеряли информацию о типе, которую пытались сохранить.

И даже если отбросить тот факт, что они бесполезны, вы просто не можете создать здесь желаемый экзистенциальный тип. Ошибка возникает при попытке вернуть экзистенциально типизированный список (return $ dynListToList dl). На какой конкретный тип вы звоните dynListToList? Напомним, что dynListToList :: forall a. Typeable a => [Dynamic] -> [a]; таким образом, randList отвечает за выбор того, что a dynListToList будет использовать. Но он не знает, какой a выбрать; Опять же, это источник вопроса! Таким образом, тип, который вы пытаетесь вернуть, недооценен и поэтому неоднозначен. 3

Зависимые типы

Хорошо, так что же сделать эту экзистенциальную книгу полезной (и возможной)? Что ж, на самом деле у нас немного больше информации: мы не только знаем, что есть некоторые a, у нас есть его TypeRep. Так что, возможно, мы сможем упаковать это:

randList :: [Dynamic] -> (exists a. Typeable a => IO (TypeRep,[a]))

Однако этого недостаточно; TypeRep и [a] вообще не связаны. И это именно то, что вы пытаетесь выразить: какой-то способ связать TypeRep и a.

По сути, ваша цель - написать что-то вроде

toType :: TypeRep -> *

Здесь * - это все типы; Если вы раньше не видели виды, они относятся к типам, а типы относятся к значениям. * классифицирует типы, * -> * классифицирует конструкторы типов с одним аргументом и т. Д. (Например, Int :: *, Maybe :: * -> *, Either :: * -> * -> * и Maybe Int :: *.)

При этом вы могли бы написать (еще раз, этот код недействителен Haskell; на самом деле он имеет лишь мимолетное сходство с Haskell, поскольку вы не можете написать его или что-то подобное в системе типов Haskell):

randList :: [Dynamic] -> (exists (tr :: TypeRep).
                           Typeable (toType tr) => IO (tr, [toType tr]))
randList dl = do
  tr <- randItem $ map dynTypeRep dl
  return (tr, dynListToList dl :: [toType tr])
    -- In fact, in an ideal world, the `:: [toType tr]` signature would be
    -- inferable.

Теперь вы обещаете правильную вещь: не то, что существует какой-то тип, который классифицирует элементы списка, а то, что существует некоторый TypeRep, такой, что соответствующий ему тип классифицирует элементы списка. Если бы вы только могли это сделать, вы были бы настроены. Но написание toType :: TypeRep -> * в Haskell совершенно невозможно: для этого требуется язык с зависимой типизацией, поскольку toType tr - это тип, который зависит от значения.

Что это значит? В Haskell вполне допустимо, чтобы значения зависели от других значений; вот что такое функция. Например, значение head "abc" зависит от значения "abc". Точно так же у нас есть конструкторы типов, поэтому типы могут зависеть от других типов; рассмотрим Maybe Int, а как это зависит от Int. У нас могут быть даже значения, зависящие от типов! Рассмотрим id :: a -> a. На самом деле это семейство функций: id_Int :: Int -> Int, id_Bool :: Bool -> Bool и т. Д. Какая из них у нас есть, зависит от типа a. (На самом деле, id = \(a :: *) (x :: a) -> x; хотя мы не можем написать это на Haskell, есть языки, на которых мы можем.)

Однако важно то, что у нас никогда не может быть типа, который зависит от значения. Мы могли бы захотеть такую ​​вещь: представьте Vec 7 Int, тип списков целых чисел длиной 7. Здесь Vec :: Nat -> * -> *: тип, первым аргументом которого должно быть значение типа Nat. Но мы не можем писать такого рода вещи в Haskell. 4 Языки, которые это поддерживают, называются зависимо-типизированными (и позволят нам писать id, как мы делали выше); примеры включают Coq и Agda. (Такие языки часто служат помощниками при проверке и обычно используются для исследовательской работы, а не для написания реального кода. Зависимые типы сложны, и сделать их полезными для повседневного программирования - активная область исследований.)

Таким образом, в Haskell мы можем сначала проверить все, что касается наших типов, выбросить всю эту информацию, а затем скомпилировать что-то, что относится только к значениям. Фактически, это именно то, что делает GHC; поскольку мы никогда не можем проверить типы во время выполнения в Haskell, GHC стирает все типы во время компиляции без изменения поведения программы во время выполнения. Вот почему unsafeCoerce легко реализовать (с точки зрения эксплуатации) и совершенно небезопасно: во время выполнения это не работает, но это лежит в системе типов. Следовательно, что-то вроде toType совершенно невозможно реализовать в системе типов Haskell.

Фактически, как вы заметили, вы даже не можете записать желаемый тип и использовать unsafeCoerce. При некоторых проблемах это может сойти с рук; мы можем записать тип функции, но реализовать ее можно только с помощью читерства. Именно так работает fromDynamic. Но, как мы видели выше, нет даже хорошего типа для решения этой проблемы из Haskell. Мнимая функция toType позволяет вам дать программе тип, но вы не можете даже записать тип toType!

Что теперь?

Итак, вы не можете этого сделать. Что следует делать? Я предполагаю, что ваша общая архитектура не идеальна для Haskell, хотя я этого не видел; Typeable и Dynamic на самом деле не так часто появляются в программах на Haskell. (Возможно, вы, как говорится, «говорите на Haskell с акцентом на Python».) Если у вас есть только конечный набор типов данных, с которыми нужно иметь дело, вы могли бы вместо этого объединить вещи в простой старый алгебраический тип данных:

data MyType = MTInt Int | MTBool Bool | MTString String

Затем вы можете написать isMTInt и просто использовать filter isMTInt или filter (isSameMTAs randomMT).

Хотя я не знаю, что это такое, вы, вероятно, могли бы unsafeCoerce решить эту проблему. Но, честно говоря, это плохая идея, если вы действительно, действительно, действительно, действительно, действительно, действительно не знаете, что делаете. И даже тогда, вероятно, это не так. Если вам нужен unsafeCoerce, знайте, это не просто удобство.

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


1 Это выглядит следующим образом:

{-# LANGUAGE ExistentialQuantification #-}
data TypeableList = forall a. Typeable a => TypeableList [a]
randList :: [Dynamic] -> IO TypeableList

Однако, поскольку ни один из этих кодов в любом случае не компилируется, я думаю, что написать его с помощью exists будет проще.

2 Технически есть некоторые другие функции, которые выглядят актуальными, например _ 83_ и _ 84_. Однако Dynamic является более или менее экзистенциальной оболочкой для Typeables, полагающейся на typeOf и TypeReps, чтобы знать, когда unsafeCoerce (GHC использует некоторые типы, зависящие от реализации и unsafeCoerce, но вы могли бы сделать это таким образом, с возможным исключение _91 _ / _ 92_), поэтому toDyn не делает ничего нового. И fromDyn на самом деле не ожидает своего аргумента типа a; это просто оболочка вокруг cast. Эти и другие подобные функции не предоставляют дополнительных возможностей, которые недоступны только для typeOf и cast. (Например, возврат назад к Dynamic не очень полезен для вашей проблемы!)

3 Чтобы увидеть ошибку в действии, вы можете попробовать скомпилировать следующую полную программу на Haskell:

{-# LANGUAGE ExistentialQuantification #-}
import Data.Dynamic
import Data.Typeable
import Data.Maybe

randItem :: [a] -> IO a
randItem = return . head -- Good enough for a short and non-compiling example

dynListToList :: Typeable a => [Dynamic] -> [a]
dynListToList = mapMaybe fromDynamic

data TypeableList = forall a. Typeable a => TypeableList [a]

randList :: [Dynamic] -> IO TypeableList
randList dl = do
  tr <- randItem $ map dynTypeRep dl
  return . TypeableList $ dynListToList dl -- Error!  Ambiguous type variable.

Конечно, если вы попытаетесь скомпилировать это, вы получите ошибку:

SO12273982.hs:17:27:
    Ambiguous type variable `a0' in the constraint:
      (Typeable a0) arising from a use of `dynListToList'
    Probable fix: add a type signature that fixes these type variable(s)
    In the second argument of `($)', namely `dynListToList dl'
    In a stmt of a 'do' block: return . TypeableList $ dynListToList dl
    In the expression:
      do { tr <- randItem $ map dynTypeRep dl;
           return . TypeableList $ dynListToList dl }

Но, как и весь вопрос, вы не можете «добавить сигнатуру типа, которая фиксирует эти переменные типа», потому что вы не знаете, какой тип вам нужен.

4 В основном. GHC 7.4 поддерживает подъем типов к видам и полиморфизм видов; см. раздел 7.8, «Вид полиморфизм и продвижение "в руководстве пользователя GHC 7.4. Это не делает Haskell зависимо типизированным - что-то вроде TypeRep -> * примера все еще отсутствует 5 - но вы сможете писать Vec, используя очень выразительные типы, которые выглядят как значения.

5 Технически теперь вы можете записать что-то, что выглядит как имеющее желаемый тип: type family ToType :: TypeRep -> *. Однако для этого требуется тип повышенного типа TypeRep, а не значение типа TypeRep; к тому же вы все равно не сможете это реализовать. (По крайней мере, я так не думаю, и я не понимаю, как бы вы, но я не эксперт в этом.) Но на данный момент мы довольно далеко.

person Antal Spector-Zabusky    schedule 05.09.2012
comment
Под randList :: [Dynamic] -> [Typeable] вы, вероятно, имеете в виду randList :: [Dynamic] -> [TypeRep], но даже это немного лукавит, поскольку typeOf - это не единственное, что вы можете делать со значениями Typeable, вы также можете cast их. Кроме того, если у вас randList :: [Dynamic] -> (exists (tr :: TypeRep). Typeable (toType tr) => IO [toType tr]), вы, вероятно, действительно хотите, чтобы результат был напечатан, не так ли? В противном случае вы просто говорите, что результат имеет представимый тип, а не результат представимого типа, и это то, что он есть - person Ben Millwood; 05.09.2012
comment
Бен: 1. Да, я имею в виду [TypeRep], спасибо. Я исправлю это, как только… 2. Я выясню, как ответить на ваш замечательный момент о cast. (Глупо unsafeCoerce, делающее мои аргументы недействительными :-)) 3. Я думал о том, чтобы иметь exists … (tr, IO [toType tr]), но я представлял exists как зависимую сумму, которую затем можно сопоставить с шаблоном для извлечения информации. Haskell не может этого сделать для экзистенциальных типов из-за параметричности, но мы явно уже оставили здесь параметричность далеко позади :-) Однако, если это будет яснее, я могу ее изменить; это, наверное, хорошая идея. - person Antal Spector-Zabusky; 05.09.2012
comment
Я не буду претендовать на авторитет в последнем пункте - если вы уверены, что знаете, что делаете, то можете оставить все как есть :) - person Ben Millwood; 05.09.2012
comment
@BenMillwood: исправлено, исправлено и исправлено. (С некоторыми уточнениями / исправлением опечаток, происходящими одновременно.) Спасибо, что уловили эти ошибки в моем ответе. (Включая экзистенциальную пару с tr; ясность важнее, чем потенциально технически правильная, и я в любом случае не претендую на большой авторитет в зависимых типах!) - person Antal Spector-Zabusky; 07.09.2012

Вы наблюдаете, что тип TypeRep на самом деле не несет с собой никакой информации уровня типа; только информация на уровне терминов. Это досадно, но мы можем добиться большего, если знаем все конструкторы типов, которые нам небезразличны. Например, предположим, что нас интересуют только Int, списки и типы функций.

{-# LANGUAGE GADTs, TypeOperators #-}

import Control.Monad

data a :=: b where Refl :: a :=: a
data Dynamic where Dynamic :: TypeRep a -> a -> Dynamic
data TypeRep a where
    Int   :: TypeRep Int
    List  :: TypeRep a -> TypeRep [a]
    Arrow :: TypeRep a -> TypeRep b -> TypeRep (a -> b)

class Typeable a where typeOf :: TypeRep a
instance Typeable Int where typeOf = Int
instance Typeable a => Typeable [a] where typeOf = List typeOf
instance (Typeable a, Typeable b) => Typeable (a -> b) where
    typeOf = Arrow typeOf typeOf

congArrow :: from :=: from' -> to :=: to' -> (from -> to) :=: (from' -> to')
congArrow Refl Refl = Refl

congList :: a :=: b -> [a] :=: [b]
congList Refl = Refl

eq :: TypeRep a -> TypeRep b -> Maybe (a :=: b)
eq Int Int = Just Refl
eq (Arrow from to) (Arrow from' to') = liftM2 congArrow (eq from from') (eq to to')
eq (List t) (List t') = liftM congList (eq t t')
eq _ _ = Nothing

eqTypeable :: (Typeable a, Typeable b) => Maybe (a :=: b)
eqTypeable = eq typeOf typeOf

toDynamic :: Typeable a => a -> Dynamic
toDynamic a = Dynamic typeOf a

-- look ma, no unsafeCoerce!
fromDynamic_ :: TypeRep a -> Dynamic -> Maybe a
fromDynamic_ rep (Dynamic rep' a) = case eq rep rep' of
    Just Refl -> Just a
    Nothing   -> Nothing

fromDynamic :: Typeable a => Dynamic -> Maybe a
fromDynamic = fromDynamic_ typeOf

Все вышеперечисленное довольно стандартно. Чтобы узнать больше о стратегии проектирования, вы захотите прочитать о GADT и одноэлементных типах. Теперь функция, которую вы хотите написать, следует; этот тип будет выглядеть немного глупо, но потерпите меня.

-- extract only the elements of the list whose type match the head
firstOnly :: [Dynamic] -> Dynamic
firstOnly [] = Dynamic (List Int) []
firstOnly (Dynamic rep v:xs) = Dynamic (List rep) (v:go xs) where
    go [] = []
    go (Dynamic rep' v:xs) = case eq rep rep' of
        Just Refl -> v : go xs
        Nothing   ->     go xs

Здесь мы выбрали случайный элемент (я бросил кубик, и он выпал 1) и извлекли только те элементы, которые имеют соответствующий тип из списка динамических значений. Теперь мы могли проделать то же самое с обычным утомительным старым Dynamic из стандартных библиотек; однако то, что мы не могли сделать, - это использовать TypeRep осмысленным образом. Теперь я продемонстрирую, что мы можем это сделать: мы сопоставим с шаблоном TypeRep, а затем будем использовать заключенное значение в конкретном типе, о котором TypeRep сообщает нам, что это так.

use :: Dynamic -> [Int]
use (Dynamic (List (Arrow Int Int)) fs) = zipWith ($) fs [1..]
use (Dynamic (List Int) vs) = vs
use (Dynamic Int v) = [v]
use (Dynamic (Arrow (List Int) (List (List Int))) f) = concat (f [0..5])
use _ = []

Обратите внимание, что в правой части этих уравнений мы используем обернутое значение для разных конкретных типов; сопоставление с образцом на TypeRep фактически вводит информацию на уровне типа.

person Daniel Wagner    schedule 05.09.2012
comment
Это действительно отличное решение вашей проблемы, сделанное отличным ученым-компьютерщиком, так что +1, но ... Не делайте этого! Вам действительно нужно смешивать эти разнородные типы? Могли бы вы действительно не справиться с использованием абстрактного типа данных для нескольких типов, которые вы хотите объединить в список? Разве это не было бы более элегантно? Если нет, не могли бы вы сделать эти типы экземплярами какого-либо класса типов, и тогда вам не нужно было бы снова раскапывать этот тип в своем неоднородном списке? Статический набор текста - ваш друг. Статический набор текста избавляет от боли. Откажитесь от статической печати, только если это необходимо. - person AndrewC; 05.09.2012
comment
@AndrewC FWIW Я полностью с вами согласен. - person Daniel Wagner; 05.09.2012

Вам нужна функция, которая выбирает другой тип значений для возврата на основе данных времени выполнения. Хорошо, отлично. Но вся цель типа - сообщить вам, какие операции могут выполняться над значением. Если вы не знаете, какой тип будет возвращен функцией, что вы делаете со значениями, которые она возвращает? Какие операции с ними можно проводить? Есть два варианта:

  • Вы хотите прочитать тип и выполнить какое-то поведение в зависимости от того, какой это тип. В этом случае вы можете обслуживать только конечный список заранее известных типов, по сути, проверяя «это тип? Затем мы выполняем эту операцию ...». Это легко возможно в текущем Dynamic фреймворке: просто верните Dynamic объекты, используя dynTypeRep для их фильтрации, и оставьте применение fromDynamic тем, кто хочет получить ваш результат. Более того, это вполне возможно без Dynamic, если вы не возражаете против установки конечного списка типов в коде производителя, а не в коде потребителя: просто используйте ADT с конструктором для каждого типа, data Thing = Thing1 Int | Thing2 String | Thing3 (Thing,Thing). Этот последний вариант, безусловно, лучший, если это возможно.
  • Вы хотите выполнить некоторую операцию, которая работает с семейством типов, возможно, о некоторых из которых вы еще не знаете, например с помощью операций класса типов. Это сложнее, и это также сложно концептуально, потому что вашей программе не разрешено изменять поведение в зависимости от того, существует ли какой-либо экземпляр класса типа - это важное свойство системы классов типов, которое вводит новый экземпляр может либо выполнить проверку типа программы, либо остановить ее от проверки типа, но он не может изменить поведение программы. Следовательно, вы не можете выдать ошибку, если ваш список ввода содержит несоответствующие типы, поэтому я действительно не уверен, что вы можете сделать что-то, что по существу не связано с возвращением к первому решению в какой-то момент.
person Ben Millwood    schedule 05.09.2012