Есть ли в Haskell морфизмы? - PullRequest
       39

Есть ли в Haskell морфизмы?

6 голосов
/ 14 августа 2011

У меня есть некоторый GADT, который представляет термин в лямбда-исчислении.

data Term a = 
      Var a
    | Lambda a (Term a)
    | Apply (Term a) (Term a)

То, что я хочу сделать, - это иметь общий интерфейс для преобразования этого типа.Она должна иметь сигнатуру типа, подобную этой:

(Term a -> Term a) -> Term a -> Term a

Эта функция легко написать:

fmap' :: (Term a → Term a) → Term a → Term a 
fmap' f (Var v) = f (Var v)
fmap' f (Lambda v t) = Lambda v (fmap' f t)
fmap' f (Apply t1 t2) = Apply (fmap' f t1) (fmap' f t2)

Итак, мой вопрос, есть ли какая-то общая структура в haskell (или haskellбиблиотека), чтобы сделать этот вид преобразований (аналогично Functor, его, вероятно, следует назвать морфизмом)?

Ответы [ 5 ]

12 голосов
/ 14 августа 2011

Для справки, вот условия ...

data Term a = 
      Var a
    | Lambda a (Term a)
    | Apply (Term a) (Term a)

(отмечу, что представление переменных абстрагировано, что часто является хорошим планом.)

...и вот предложенная функция.

fmap' :: (Term a → Term a) → Term a → Term a 
fmap' f (Var v) = f (Var v)
fmap' f (Lambda v t) = Lambda v (fmap' f t)
fmap' f (Apply t1 t2) = Apply (fmap' f t1) (fmap' f t2)

Что меня беспокоит в этом определении, так это то, что f когда-либо применяется только к терминам формы (Var v), так что вы могли бы также реализовать substitution .

subst :: (a → Term a) → Term a → Term a 
subst f (Var v) = f v
subst f (Lambda v t) = Lambda v (subst f t)
subst f (Apply t1 t2) = Apply (subst f t1) (subst f t2)

Если бы вы немного позаботились о том, чтобы отличить границу от свободных переменных, вы могли бы сделать Term a Monad с заменой, реализующей (>>=).В общем случае термины могут иметь структуру Functor для переименования и структуру Monad для подстановки.Об этом есть прекрасная статья Берда и Патерсона , но я отвлекся.

Между тем, если вы do хотите действовать иначе, чем с переменными, один общий подход заключается виспользовать универсальные наборы инструментов обхода, такие как uniplate, как предполагает augustss.Другая возможность, возможно, немного более дисциплинированная, это работать с 'fold' для вашего типа.

tmFold :: (x -> y) -> (x -> y -> y) -> (y -> y -> y) -> Term x -> y
tmFold v l a (Var x)       = v x
tmFold v l a (Lambda x t)  = l x (tmFold v l a t)
tmFold v l a (Apply t t')  = a (tmFold v l a t) (tmFold v l a t')

Здесь v, l и a определяют альтернативную алгебру для ваших Term -образующих операций, действуя только на y, а не Term x, объясняя, как обрабатывать переменные, лямбда-выражения и приложения.Вы можете выбрать y, чтобы быть m (Term x) для некоторой подходящей монады m (например, потоковое окружение для переменных), а не просто Term x.Каждый подтерм обрабатывается, чтобы дать y, затем выбирается соответствующая функция, чтобы получить y для всего термина.Сгиб захватывает стандартный шаблон рекурсии.

Обычные типы данных первого порядка (и некоторые хорошо организованные типы данных более высокого порядка) могут быть снабжены операторами сгиба.За счет читабельности вы даже можете написать оператор сгиба раз и навсегда.

data Fix f = In (f (Fix f))

fixFold :: Functor f => (f y -> y) -> Fix f -> y
fixFold g (In xf) = g (fmap (fixFold g) xf)

data TermF a t
  = VarF a
  | LambdaF a t
  | ApplyF t t

type Term a = Fix (TermF a)

В отличие от вашего рекурсивного Term a, этот TermF a t объясняет, как сделать один слойтермина, с t элементами в подотчетных местах.Мы возвращаем рекурсивную структуру Term, используя рекурсивный тип Fix.Мы теряем немного косметически, в этом каждый слой имеет дополнительную In, оборачивающую его.Мы можем определить

var x      = In (VarF x)
lambda x t = In (LambdaF x t)
apply t t' = In (Apply x t t')

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

TermF a y -> y

, которая (как и v, l и a выше) объясняет, как обрабатывать любыетермин, чьи подтермы уже были обработаны до значений типа y.Будучи явным в типах о том, из чего состоит один слой, мы можем подключиться к общей схеме работы слоя за слоем.

7 голосов
/ 14 августа 2011

Посмотрите на одноплатный пакет .Он может выполнять те преобразования, о которых вы говорите, но для произвольной структуры данных.

4 голосов
/ 14 августа 2011

С помощью fmap', который вы указали в вопросе, функция преобразования f может преобразовывать только значения Var в различные лямбда-выражения.Он не может преобразовать Lambda или Apply во что-то другое, поскольку он жестко задан в функции fmap', что эти конструкторы остаются неизменными.

Ваш fmap' может, например, преобразовать Var 1 в Lambda 1 (Var 1), но не наоборот.Это действительно ваше намерение?

Если fmap' разрешит произвольные преобразования, вы получите следующее:

fmap' :: (Term a → Term a) → Term a → Term a 
fmap' f t = f t

Это то же самое, что и $.

* 1020.* (С другой стороны, если fmap' разрешено изменять только значения в выражении, не меняя его структуру вообще, вы вернетесь к обычному fmap.)
2 голосов
/ 14 августа 2011

Во-первых, у меня нет ответа на ваш настоящий вопрос, хотя у меня есть некоторые идеи, которые могут быть полезны для вас, не уверен.

Мне кажется, что это менее общий вопрос, чем мог быбыть, я ожидал бы, что вы будете использовать что-то вроде:

fmap' :: (Term a → Term a) → Term a → Term a 
fmap' f (Var v) = f $ Var v
fmap' f (Lambda v t) = f $ Lambda v (fmap' f t)
fmap' f (Apply t1 t2) = f $ Apply (fmap' f t1) (fmap' f t2)

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

Что-то, что может быть более полезным для вас в будущем, может быть:

fmapM :: Monad m => (Term a -> m (Term a)) -> Term a -> m (Term a)
fmapM f (Var v)         = f (Var v)
fmapM f (Lambda v t)    = do
    t' <-fmapM f t
    f (Lambda v t')
fmapM f (Apply t1 t2)   = do
    t1' <- fmapM f t1
    t2' <- fmapM f t2
    f (Apply t1' t2')

Который вы затем можете использовать позже с монадой State, чтобы отслеживать привязки из лямбд.Также эта функция такая же, как и выше, когда вы используете монаду Identity, вы можете написать простую функцию, взяв f :: (Term a -> Term a) и использовать fmap 'f = fmapM (f. (Return :: -)> Идентичность а)).

Дайте мне знать, если это полезно:)

1 голос
/ 15 августа 2011

Может оказаться полезным пакет unbound , особенно функция substs .

...