Общие преобразования типов в Haskell

Я пытаюсь написать преобразователь 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.

Огромное спасибо!


person gatoatigrado    schedule 30.04.2011    source источник
comment
Стрелки здесь могут быть не очень полезны. Набор классов Functor-Applicative-Monad совместно использует операцию fmap (называемую liftM для Monad и liftA для Applicative), которая отображает функцию преобразования типа на элемент или элементы внутри Functor. Стрелки являются очень общей структурой и поэтому не поддерживают такую ​​операцию.   -  person stephen tetley    schedule 30.04.2011
comment
stephen tetley кажется правильным, что Arrow как класс типов - это не то, что вам нужно. Однако я не могу четко следовать вашим целям. Обратите внимание, что первые два бита определения f' предполагают определение AV a s как Error s | Concrete a. Это тип Either, и те строки вашего определения f' делают его стандартным fmap f для Either, который мы все используем. Возможно, изучение бессмертной Typeclassopedia (haskell.org/wikiupload/8/ 85/TMR-Issue13.pdf) облегчит общение о ваших целях? Опять же, я просто развиваю замечание немного stephen tetleys.   -  person applicative    schedule 01.05.2011


Ответы (2)


Позвольте мне посмотреть, понимаю ли я, что вы здесь ищете. У вас есть тип 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? Здесь, в контексте, мы знаем, что он должен иметь тип AV C -> AV A. Но какого типа он будет вообще? Что-то вроде AV c -> AV a? Это неправильно, потому что результатом является не просто любой тип a, это тип, который использовался для построения значения AV c. Предполагая, что значение, с которым мы работаем, было построено с использованием результата liftAV2 f, мы знаем, что рассматриваемый тип существует, но у нас нет способа найти его вообще.

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

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

В вашем конкретном случае может быть проще указать 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, отбрасывая структуру. Вы также можете «распаковать» вычисление, используя тип структуры, отбросив тип результата, если вам нужно разобрать структуру, чтобы, скажем, красиво распечатать ее.

person C. A. McCann    schedule 25.05.2011

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

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

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

person Lambdageek    schedule 25.05.2011