Преобразования универсального типа в Haskell - PullRequest
4 голосов
/ 30 апреля 2011

Я пытаюсь написать преобразователь arrow , который принимает обычные функции и превращает их в вычисления для абстрактных значений. Если у нас есть стрелка «источник»,

f :: Int -> Int
f x = x + 1

тогда цель состоит в том, чтобы f работал с поднятыми [sic?] Типами абстрактных значений, в этом примере

f' :: AV Int -> AV Int
f' (Const x) = Const (f x)
-- pass along errors, since AV computation isn't always defined
-- or computable in the case of errors
f' (Error s) = Error s
-- avRep = "abstract representation". Think of symbolic math manipulation or ASTs.
f' (Abstract avRep) = AVRepPlus avRep (AVRepConst 1)

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

g = uncurry (+) -- i.e. g (x, y) = x + y

затем я добавляю абстрактное представление для (,) конструктора кортежей,

AVTuple :: AV a -> AV b -> AV (a, b)

и код для g поднимается до [развернуто немного],

g' (AVTuple (AVConst a) (AVConst b)) = (AVConst (g (a, b)))
g' (AVTuple (AVError e) _) = (AVError e)
-- symmetric case here, i.e. AVTuple _ (AVError e)
g' (AVTuple a@(AVTuple _ _) b) = -- recursive code here

То же самое нужно сделать с AVEither. Это будет в конечном итоге много дел. Есть ли хороший способ обойти это?

Я новичок в Haskell, поэтому, пожалуйста, пришлите мне ссылки или полу-подробные пояснения наверное, самая близкая вещь, которую я прочитал, - это бумага SYBR (откажитесь от оборотов), разделы 1-3.

Большое спасибо!

Ответы [ 2 ]

1 голос
/ 25 мая 2011

Дай мне посмотреть, понимаю ли я, что ты здесь ищешь.У вас есть тип AV a, который описывает вычисления, производящие что-то типа a, где структура этих вычислений может быть сохранена таким образом, чтобы разрешить проверку.Вам нужен способ поднять произвольные функции в операции на AV, сохраняя структуру, без необходимости создавать особые случаи для каждой операции.

Обычно для подъема функций в какую-то структуру можно использовать Functor иApplicative.Тем не менее, простой способ сделать это включает в себя преобразование структуры и непосредственное применение поднятой функции, не сохраняя приложение функции как часть структуры.

То, что вы хотите, гораздо более неудобно, и вот почему:

Допустим, у нас есть некоторая функция, которую мы хотим поднять, и два абстрактных значения соответствующего типа, к которым она будет применяться:

x :: AV A
x = ...

y :: AV B
y = ...

f :: A -> B -> C
f = ...

Предположим, существует функция liftAV2, которая делает то, что вы хотите.Мы ожидаем, что тип lift2 f будет AV A -> AV B -> AV C, точно так же как liftA для Applicative.

Позже мы хотим проверить вычисления, произведенные с использованием lift2 f, путем восстановлениязначения f, x и y.Допустим, сейчас мы просто хотим извлечь первый аргумент.Предположим, существует функция extractArg1, которая делает это, например, extractArg1 (liftAV2 f x y) = x.Какой тип extractArg1?Здесь, в context , мы знаем, что он должен иметь тип AV C -> AV A.Но какой это будет тип?Что-то вроде AV c -> AV a?Это неправильно, потому что результат не просто любой тип a, это тот тип, который использовался для создания значения AV c.Предполагая, что значение, с которым мы работаем, было построено с использованием результата liftAV2 f, мы знаем, что рассматриваемый тип существует , но у нас нет способа найти его вообще.

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

Вы, вероятно, можете выполнить то, что вам нужно, с некоторыми усилиями, но это становится довольно продвинутой территорией.Возможно, вы захотите использовать GADTs для начала, хотя я думаю, что вы, возможно, уже делаете это.Кроме того, работа с экзистенциальными типами чрезвычайно неуклюжа, потому что вы ограничены знанием только того, что они представляют в ограниченном контексте.

В вашем конкретном случае, возможно, будет проще дать AV два параметра типа : один, представляющий конечный тип вычисления, и один, представляющий структуру вычисления, например:

data f :$ x = ...

data AV structure result where
    ...
    AVApply :: AV f (a -> b) -> AV x a -> AV (f :$ x) b

Затем для проверки вычислений вы можете посмотреть на первый тип, чтобызнаю, что у тебя есть;для построения вычислений вы можете посмотреть на вторую, чтобы убедиться, что типы совпадают.Функция оценки будет иметь тип, подобный AV t a -> a, отбрасывая структуру.Вы также можете «распаковать» вычисления, используя тип структуры, отбрасывая тип результата, если вам нужно разобрать структуру, чтобы, скажем, довольно распечатать ее.

0 голосов
/ 25 мая 2011

Как мне нравится думать об этом, я бы использовал экземпляр Functor, когда хочу поговорить о каких-то «данных с небольшим дополнительным» (в зависимости от того, что такое «маленький дополнительный», я на самом деле мог бы быть говорить о Applicative или Monad).

С другой стороны, я использую экземпляр Arrow, чтобы говорить о «функциях с чуть меньшим количеством»: стрелки позволяют вам определять вещи, которые можно составлять вместе так же, как функции, но вы можете добавить дополнительную структуру или ограничения, запрещающие определенные конструкции (например, стрелки без ArrowChoice или ArrowLoop).

Не совсем ясно, чего вы хотите достичь, но похоже, что вы фактически оборачиваете свои данные в конструкторы типа AV. В этом случае вы, вероятно, захотите сделать AV экземпляром Functor и добавить Functor экземпляров для (AV a, AV b) => AV (a, b) и аналогично для AV, обернутых вокруг Either.

...