Соединение/Объединение Классов Типа в Haskell

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

class Monad m where
    (>>=)  :: m a -> (a -> m b) -> m b
    return :: a -> m a

class PhantomMonad p where
    pbind    :: p a -> (a -> p b) -> p b
    preturn  :: a -> p a

Существует ли способ связать эти два класса так что-то, что является экземпляром PhantomMonad, автоматически будет экземпляр Монады, или будете, экземпляры для каждого класса должны быть явно записаны? Любое понимание больше всего ценилось бы, Спасибо!

10
задан thegravian 6 March 2014 в 05:27
поделиться

5 ответов

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

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

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

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

Если вы действительно хотите спуститься в эту кроличью нору, поищите немного на сайте Олега Киселева ; он своего рода покровитель метапрограммирования на уровне типов в Haskell.

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

Изменить: Хорошо, задним числом я переоценил проблему здесь. Что-то вроде PhantomMonad отлично работает как одноразовый и должен делать то, что вы хотите, учитывая перекрывающиеся - и UndecidableInstances расширения GHC. Сложный материал начинается, когда вы хотите сделать что-то гораздо более сложное, чем то, о чем идет речь. Я искренне благодарен Норману Рэмси за то, что позвал меня на это - я действительно должен был знать лучше.

Я до сих пор не рекомендую делать такие вещи без уважительной причины, но это не так плохо, как я сказал. Моя вина.

13
ответ дан 3 December 2019 в 14:52
поделиться

Хотя это и не имеет смысла, попробуйте

instance Monad m => PhantomMonad m where
    pbind = (>>=)
    preturn = return

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

1
ответ дан 3 December 2019 в 14:52
поделиться

Необычный дизайн. Разве нельзя просто удалить PhantomMonad, поскольку он изоморфен другому классу.

7
ответ дан 3 December 2019 в 14:52
поделиться

Есть ли способ связать эти два класса вместе, чтобы нечто, являющееся экземпляром PhantomMonad, автоматически стало экземпляром Monad?

Да, но для этого требуются слегка тревожные языковые расширения FlexibleInstances и UndecidableInstances :

instance (PhantomMonad m) => Monad m where
  return = preturn
  (>>=)  = pbind

FlexibleInstances не так уж и плох, но риск неразрешимости немного более тревожный. Проблема в том, что в правиле вывода ничего не становится меньше, поэтому, если вы объедините это объявление экземпляра с другим аналогичным (например, в обратном направлении), вы можете легко заставить средство проверки типов работать бесконечно.

Обычно мне удобно использовать FlexibleInstances , но я стараюсь избегать UndecidableInstances без веской причины. Здесь я согласен с предложением Дона Стюарта, что для начала вам лучше использовать Монаду . Но ваш вопрос больше похож на мысленный эксперимент, ответ таков: вы можете делать все, что хотите, не впадая в пугающий уровень Олега.

7
ответ дан 3 December 2019 в 14:52
поделиться

Другим решением является использование newtype. Это не совсем то, что вам нужно, но часто используется в подобных случаях.

Это позволяет связать различные способы указания одной и той же структуры. Например, ArrowApply (из Control.Arrow) и Monad эквивалентны. Вы можете использовать Kleisli, чтобы сделать ArrowApply из монады, и ArrowMonad, чтобы сделать монаду из ArrowApply.

Также возможны односторонние обертки: WrapMonad (в Control.Applicative) формирует аппликатив из монады.

class PhantomMonad p where
    pbind    :: p a -> (a -> p b) -> p b
    preturn  :: a -> p a

newtype WrapPhantom m a = WrapPhantom { unWrapPhantom :: m a }

newtype WrapReal m a = WrapReal { unWrapReal :: m a }

instance Monad m => PhantomMonad (WrapPhantom m) where
  pbind (WrapPhantom x) f = WrapPhantom (x >>= (unWrapPhantom . f))
  preturn = WrapPhantom . return

instance PhantomMonad m => Monad (WrapReal m) where
  WrapReal x >>= f = WrapReal (x `pbind` (unWrapReal . f))
  return = WrapReal . preturn
3
ответ дан 3 December 2019 в 14:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: