Не ограничение Монады

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

Упрощенный пример:

{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE OverlappingInstances   #-}

class WhatIs a b | a -> b where
  whatIs :: a -> b

instance (Show a) => WhatIs a String where
  whatIs = show

instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where
  whatIs x = fmap show x


main :: IO ()
main = do
  let x = 1 :: Int
  putStrLn "----------------"
  {-print $ whatIs (1::Int)-}
  print $ whatIs (Just x)
  putStrLn "--- End --------"

Итак, я использую FunctionalDependencies, чтобы избежать аннотаций типа, но, разумеется, компилятор жалуется на

   Functional dependencies conflict between instance declarations:
     instance [overlap ok] Show a => WhatIs a String
       -- Defined at test.hs:10:10
     instance [overlap ok] (Monad m, Functor m, Show a) =>
                           WhatIs (m a) (m String)
       -- Defined at test.hs:13:10

Потому что a может принимать значение m a, и, следовательно, возникает конфликт.

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

instance (NotAMonad a, Show a) => WhatIs a String where
  whatIs = show

Эта проблема не представилась бы сама.

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

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

Любые подсказки?

Изменить: после user2407038 правильный ответ.

Итак, я попробовал ответить user2407038 ниже, и мне действительно удалось собрать предоставленный пример. Вывод? Я не должен был так упростить пример. После некоторого размышления с моим фактическим примером, я смог уменьшить его до этого:

{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE UndecidableInstances   #-}

module IfThenElseStackExchange where

class IfThenElse a b c d | a b c -> d where
  ifThenElse :: a -> b -> c -> d

instance (Monad m) => IfThenElse (m Bool) (m b) (m b) (m b) where
  ifThenElse m t e  = do
    b <- m
    if b then t else e

instance (Monad m) => IfThenElse (m Bool) b b (m b) where
  ifThenElse ma t e = do
    a <- ma
    return $ if a then t else e

Но я все еще получаю страшную ошибку Functional dependencies conflict between instance declarations. Зачем? Часть после => (глава экземпляра, как указано в user2407038) на самом деле совсем другая, поэтому она даже не подходит для OverlappingInstances, так как компилятор может выбрать наиболее конкретный.

Тогда что?

Ошибка, как всегда, указана сообщением об ошибке. Часть a b c d | a b c -> d не соблюдается приведенным выше кодом. Поэтому я, наконец, попробовал это:

{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE UndecidableInstances   #-}

module IfThenElseStackExchange where

class IfThenElse a b c d | a b c -> d where
  ifThenElse :: a -> b -> c -> d

instance (Monad m, c ~ b) => IfThenElse (m Bool) (m b) (m b) (m c) where
  ifThenElse m t e  = do
    b <- m
    if b then t else e

instance (Monad m, c ~ b) => IfThenElse (m Bool) b b (m c) where
  ifThenElse ma t e = do
    a <- ma
    return $ if a then t else e

Et voilà!

Используя (m b) в последнем параметре, я пытался указать, что конечный результат имеет тот же тип, что и второй и третий параметры. Но проблема заключается в том, что расширение FunctionalDependencies не делает тот же тип экземпляра, который выбирается на типах как OverlappingInstances, и поэтому рассматривает b и (m b) "то же самое" для его целей. Правильно ли эта интерпретация, или я все еще что-то пропущу?

Я все еще могу сказать "компилятору, что c имеет тот же тип, что и b, используя constrain c ~ b и, таким образом, достигая намеченного результата.

Прочитав еще несколько материалов об этом, я очень рекомендую читать статью этой статьи Олега, где он обобщает свои прежние решения, которые связаны как с I, так и с user2407038, Я нашел его вполне доступным.

Если моя интерпретация FunctionalDependencies выше правильная, и TypeFamilies представляются как более гибкое решение для той же проблемной области, интересно, могли ли они использовать их для решения этого по-другому. Решение Олега, упомянутое выше, конечно же использует их, конечно.

Ответ 1

Вам не нужен класс NotAMonad, или, скорее, WhatIs уже этот класс.

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances,
    TypeFamilies, UndecidableInstances, IncoherentInstances #-}

class WhatIs a b | a -> b where
  whatIs :: a -> b

instance (Show a, b ~ String) => WhatIs a b where
  whatIs = show

instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where
  whatIs x = fmap show x

Вам не нужно строго IncoherentInstances, но если вы хотите, чтобы такие вещи, как whatIs (1 :: Num a => a), работали, вам это нужно.

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

Изменить: больше объяснений: Прежде всего: эти экземпляры не перекрываются! Я включил полный список языковых прагм. Вы указали, что "Функциональные зависимости конфликтуют между объявлениями экземпляра". Скажем, у вас есть следующее:

class C a b | a -> b

Предположим, что у вас есть два экземпляра C: C a b, C c d (здесь a не является жесткой переменной типа, это просто любой тип haskell). Если a является экземпляром C (или наоборот), то b должен быть экземпляром d. Это общее правило может быть несколько абстрактным, поэтому давайте посмотрим на ваш код:

instance (Show a) => WhatIs a String where
instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where

Так как a - это любой тип, вы объявляете, что "для любого типа a, whatIs a == String". Второй экземпляр объявляет, что "для любого типа (m a), whatIs (m a) == (m String)". Очевидно, что m a является экземпляром a (любой тип является экземпляром переменной свободного типа), но String никогда не является экземпляром m String.

Почему все это имеет значение? Когда компилятор проверяет, конфликтуют ли фонды, он смотрит только на голову экземпляра; то есть участок справа от =>. Следовательно,

instance (Show a, b ~ String) => WhatIs a b where

говорит "для любых типов a, b, whatIs a == b". Очевидно, что поскольку a и b являются свободными переменными, они могут быть созданы с любым другим типом. Поэтому, если a == (m a0), вы можете свободно сказать, что b == (m String). Тот факт, что b должен быть строкой, становится известным тогда и только тогда, когда первый экземпляр совпадает с ним.

Так как любые типы соответствуют a и b, WhatIs (IO ()) b соответствует первому экземпляру. Второй экземпляр используется, потому что компилятор попытается сопоставить экземпляры в порядке их специфичности. Здесь вы можете найти "правила" для определения специфики.. Простое объяснение состоит в том, что WhatIs a b соответствует большему количеству вещей, поэтому он более общий и будет использоваться позже. (Фактически, экземпляр C a0 a1 a2 .. an, где a* является отдельной переменной типа, является наиболее общим экземпляром и всегда будет проверяться последним)

Изменить: Вот общее решение вашей проблемы. С помощью этого кода whatIs = f_map show.