Ленивые и полиморфные значения

(Для следующего упростите 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не вычисляется более чем как только (и Strings, которые не используются, вообще не вычисляются).

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? --тоже подойдет.)

7
задан shachaf 16 April 2012 в 06:17
поделиться