(Для следующего упростите Show
и Read
до
class Show a where show :: a -> String
class Read a where read :: String -> a
И предположите, что read
никогда не дает сбоев.)
Хорошо-известно, что можно создать экзистенциальный тип формы
data ShowVal where
ShowVal :: forall a. Show a => a -> ShowVal
А затем построить "неоднородный список" :: [ShowVal]
, такой как
l = [ShowVal 4, ShowVal 'Q', ShowVal True]
Также хорошо-известно, что это относительно бесполезно, потому что вместо этого можно просто создайте список :: [String]
, такой как
l = [show 4, show 'Q', show True]
Который точно изоморфен (в конце концов, единственное, что можно сделать с ShowVal
это show
это ).
Ленивость делает это особенно приятным, потому что для каждого значения в списке результат show
запоминается автоматически, так что String
не вычисляется более чем как только (и String
s, которые не используются, вообще не вычисляются).
A ShowVal
эквивалентно экзистенциальному кортежу exists a. (a -> String, a)
, где функция — словарь Show
.
Аналогичная конструкция может быть сделана для Read
:
data ReadVal where
ReadVal :: (forall a. Read a => a) -> ReadVal
. Обратите внимание, что, поскольку read
является полиморфным по своему возвращаемому значению, ReadVal
является универсального, а не экзистенциального (, а это значит, что он нам на самом деле не нужен в данный момент. все, потому что у Haskell есть универсальные-классы первого класса; но мы будем использовать его здесь, чтобы выделите сходство сShow
).
Мы также можем составить список:: [ReadVal]
:
l = [ReadVal (read "4"), ReadVal (read "'Q'"), ReadVal (read "True")]
Как и в случае Show
, список :: [ReadVal]
изоморфен списку :: [String]
, например,
l = ["4", "'Q'", "True"]
(Мы всегда можем получить исходный String
с помощью
newtype Foo = Foo String
instance Read Foo where read = Foo
, потому что класс типов Read
является открытым.)
A ReadVal
эквивалентна универсальной функцииforall a. (String -> a) -> a
(представлению в стиле CPS-). Здесь словарь Read
предоставляется пользователем ReadVal
, а не производителем, потому что возвращаемое значение полиморфен, а не аргумент.
Тем не менее,ни в одном из этих представлений мы не получаем автоматического мемоизация, которую мы получаем в представлении String
с Show
. Скажем так read
для нашего типа это затратная операция, поэтому мы не хотим ее вычислять на одном и том же String
для одного и того же типа более одного раза.
Если бы у нас был закрытый тип, мы могли бы сделать что-то вроде:
data ReadVal = ReadVal { asInt :: Int, asChar :: Char, asBool :: Bool }
А затем использовать значение
ReadVal { asInt = read s, asChar = read s, asBool = read s }
Или что-то в этом роде.
Но в этом случае --даже если мы когда-либо используем ReadVal
только как один тип --String
будет анализироваться каждый раз при использовании значения. Есть ли простой способ получить мемоизацию, сохраняя полиморфность ReadVal
?
(Заставить GHC делать это автоматически, как и в случае Show
, было бы идеально, если это как-то возможно. Более явный подход к запоминанию --возможно, добавив ограничение Typeable
? --тоже подойдет.)