Не ограничение Монады - программирование
Подтвердить что ты не робот

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

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

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

{-# 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 представляются как более гибкое решение для той же проблемной области, интересно, могли ли они использовать их для решения этого по-другому. Решение Олега, упомянутое выше, конечно же использует их, конечно.

4b9b3361

Ответ 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.