Проблемы с выводом типа GHC

Вопрос. Есть ли способ заставить этот код работать без явной подписи типа?

Код. Во-первых, у меня есть гораздо более приятный на практике альтернативный класс MonadTrans, вдохновленный Data.Newtype. Это выглядит так,

{-# LANGUAGE FlexibleContexts, TypeFamilies #-}

module Alt.Control.Monad.Trans where

import Control.Monad

class (Monad ????, Monad (BaseMonad ????)) => MonadTrans (???? :: * -> *) where
    type BaseMonad ???? :: * -> *
    lift :: (BaseMonad ????) α -> ???? α

Затем у меня есть класс A с методом foo, и если некоторая базовая монада M является A, то любая преобразованная монада T M также является A. В коде

class A ???? where
    foo :: String -> ???? ()

instance (A (BaseMonad ????), MonadTrans ????) => A ???? where
    foo n = lift $ foo n

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

minimize_call :: A ???? => ???? ()
minimize_call = foo "minimize"

Возможная информация, которая поможет сделать вывод. Допустим, у нас есть связанный тип B :: * -> *. Я думаю, что я хочу сказать компилятору, что B удовлетворяет B t /= t, B (B t) /= B t и т. д., т.е. B в некотором роде "монотонный" - что погоня за связанными типами эквивалентна удалению оберток нового типа, и он должен знать, что он не может удалить оболочки нового типа навсегда , следовательно, необходимо добавить контекст A в подпись.


person gatoatigrado    schedule 15.02.2012    source источник
comment
извините, надо было потрудиться вспомнить, почему я переключился на альтернативный MonadTrans... на данный момент, допустим, он производит более чистый код, но я думаю, что была более существенная причина.   -  person gatoatigrado    schedule 16.02.2012
comment
Интересный вопрос. Почему вам не нужна явная подпись типа? Разве minimize_call не должно быть каким-то фиксированным значением, а не полиморфной константой (или, может быть, вы можете сделать его полиморфным, я не уверен)? Если у него есть какой-то один фиксированный тип, я бы предпочел это задокументировать, а если нет, я бы задокументировал это. Заставлять читателя анализировать всю программу в уме, чтобы выяснить, какой тип имеет minimize_call, кажется несколько контрпродуктивным.   -  person Ben    schedule 16.02.2012
comment
@Ben, это правда, что в этом случае хорошей практикой является наличие подписи типа для minimize_call. Однако нарушение вывода типа предполагает, что что-то идет не так (с дизайном, компилятором или связью с компилятором) и, вероятно, вызовет проблемы, не говоря уже о непонятных сообщениях об ошибках.   -  person gatoatigrado    schedule 16.02.2012


Ответы (1)


Да, есть способ. Предоставьте заземленный экземпляр для A и добавьте NoMonomorphismRestriction в прагму языка (в дополнение к обязательным FlexibleInstances и UndecidableInstances).

Однако класс A будет непригоден для использования. Компилятор не может знать, что никогда не будет экземпляра MonadTrans с BaseMonad m = m. Таким образом, он никогда не может выбрать экземпляр, потому что он не может знать, использовать ли экземпляр отсюда или другой.

{-# LANGUAGE FlexibleContexts, TypeFamilies, FlexibleInstances, UndecidableInstances, NoMonomorphismRestriction #-}

module Trans (MonadTrans(..), A(..), minimize_call) where

import Control.Monad

class (Monad m, Monad (BaseMonad m)) => MonadTrans (m :: * -> *) where
    type BaseMonad m :: * -> *
    lift :: (BaseMonad m) α -> m α

class A m where
    foo :: String -> m ()


data Foo a = Bork

instance Monad Foo where
    return _ = Bork
    _ >>= _ = Bork

instance A Foo where
    foo _ = Bork


instance (A (BaseMonad m), MonadTrans m) => A m where
    foo n = lift $ foo n

-- minimize_call :: A m => m ()
minimize_call = foo "minimize"

компилируется с ghc 6.12, 7.0, 7.2 и 7.4. Без подписи minimize_call должен получить мономорфный тип, если только MR не выключен. В любом случае это не сработает, потому что ограничение A m не имеет значения по умолчанию. Поэтому MR должен быть выключен. Но затем средство проверки типов все еще гоняется за собственным хвостом, пытаясь доказать, что ограничение выполнимо. Только с подъемным экземпляром это невозможно. Если вы предоставите якорь, он сможет.

Но гораздо лучше предоставить подпись типа.

person Daniel Fischer    schedule 15.02.2012
comment
Спасибо, но мне не нужен заземленный экземпляр в модуле. На самом деле может быть несколько заземленных экземпляров, в зависимости от того, сколько бэкэндов у меня есть для моего компилятора, и предполагая, что использование одного конкретного из них нежелательно. У меня уже включены FlexibleInstances, FlexibleContexts и NoMonomorphismRestriction. - person gatoatigrado; 16.02.2012
comment
Вы можете использовать неэкспортированную фиктивную монаду, см. обновление. Предполагаемый тип minimize_call — это A m => m (), как и должно быть, это не исключает использования других заземленных экземпляров в другом месте, просто необходимо разрешить завершение проверки типов. - person Daniel Fischer; 16.02.2012
comment
На самом деле нет, вы вообще не можете использовать minimize_call или foo. С подписью типа или без нее. - person Daniel Fischer; 16.02.2012
comment
Хм, посмотрю на ваше бесполезное утверждение. Я успешно использовал этот шаблон, но, возможно, были некоторые тонкие различия, или я не пытался заставить его работать через несколько базовых экземпляров A. - person gatoatigrado; 16.02.2012