2010-05-20 7 views
9

Скажем, у меня есть два класса типа определяются следующим образом, идентичные по функциям, но отличаются в именах:Linking/Объединение Тип классов в 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 будет автоматически экземпляром Monad, или экземпляры для каждого класса должны быть явно записаны? Любое понимание было бы очень признательно, спасибо!

+2

Является ли 'preturn :: a -> p b' a typo? –

ответ

13

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

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

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

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

Если вы действительно хотите спуститься по этой кроличьей дыре, просмотрите на Oleg Kiselyov's website немного; он похож на покровителя метапрограммирования на уровне уровня в Хаскелле.

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

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

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

+0

Спасибо! Мне было любопытно, если я просто пропустил что-то очевидное с моим запутанным мышлением, или если это действительно было напугано. Излишне говорить, что я придерживаюсь «Хорошего» ответа. – thegravian

+0

@ thegravian: мудрое решение. Если это помогает, ваша идея не является по своей сути абсурдной, она просто не работоспособна, учитывая систему классов типа Haskell. Я думаю, что были некоторые предлагаемые расширения, которые заставили бы его работать, но пока не были реализованы. –

+0

@camcann: Решение на самом деле не все *, что * страшно, не так ли?Я имею в виду, что слово «unecidable» немного страшно, но проверка типа Haskell уже завершена в течение экспоненциального времени, поэтому я не позволил бы мне помешать мне делать то, что я действительно хотел сделать ... –

6

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

+0

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

+3

подобными случаями были бы классы 'mtl' и' transformers' 'MonadTrans'. они точно такие же. ответ на вопрос о том, как унифицировать классы (если это было возможно) в этом простом эзотерическом примере, также может быть чрезвычайно полезным для реального кода. – yairchu

+2

@ yairchu: Справедливости ради следует, что решение «удалить один класс» Дона - это правильное решение для этого случая, а именно удаление «mtl» ... –

1

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

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

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

+0

Это наоборот, от того, что он просил. Конечно, 'instance (PhantomMonad m) => Monad m где ...' вызовет еще больше проблем. –

7

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

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

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

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

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

+0

Это не работает так, потому что вы перекрыли все остальные экземпляры 'Monad' , Но после игры, пытаясь сломать материал, кажется, что GHC умнее, чем я думал с однопараметрическими типами классов. Очевидно, что вы получаете только один общий экземпляр, подобный этому, но как одноразовый, это отлично работает, учитывая «OverlappingInstances». –

3

Другим решением является использование 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

Не удалось найти 'WrapMonad' в' Control.Arrow', но 'instance ArrowApply a => Monad (a())'. 'newtype Kleisli m a b = Kleisli (a -> m b)'; 'instance Monad m => ArrowApply (Kleisli m)'; Пройдите в оба конца через эти экземпляры, и вы не вернетесь к тому же «ArrowApply», с которого вы начали. 'pre a x y = a x y',' post a x y = x -> a() y'. – yairchu

+0

Упс, я имел в виду ArrowMonad. – sdcvvc