Связывание / объединение типов классов в Haskell - PullRequest
10 голосов
/ 20 мая 2010

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

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, автоматически стало экземпляром Monad, или экземпляры для каждого класса должны быть написаны явно? Любое понимание будет наиболее ценно, спасибо!

Ответы [ 5 ]

13 голосов
/ 21 мая 2010

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

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

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

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

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

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

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

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

7 голосов
/ 21 мая 2010

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

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

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

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

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

7 голосов
/ 21 мая 2010

Это необычный дизайн. Вы не можете просто удалить PhantomMonad, так как он изоморфен другому классу.

3 голосов
/ 21 мая 2010

Другое решение заключается в использовании 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
1 голос
/ 21 мая 2010

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

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

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

...