Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Not a Monad Constraint

Is it possible to define a instance constrain for "not a monad", in order to define two non-overlapping instances, one for monadic values, other for non-monadic values?

A simplified example:

{-# 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 --------"

So, I use the FunctionalDependencies to avoid type annotations, but of course, the compiler complains with

   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

Because a can assume the value m a, and thus a conflict arises.

However, if I could replace the first instance with something like:

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

This problem would not present itself.

So far I've found this very old email that seems to propose a somewhat related solution, but I'm not sure if there are new techniques to address this...

I also found the constraints package, which I'm sure has useful functions for this case, but it is sorely lacking in (simple) examples.

Any clues?

Edit: after user2407038 correct answer.

So, I tried user2407038 answer below, and indeed I managed to compile the provided example. The conclusion? I should not have simplified the example so much. After some thinkering with my actual example, I was able to reduce it to this:

{-# 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

But I still getting the dreaded Functional dependencies conflict between instance declarations error. Why? The part after the => (the instance head, as user2407038 promptly noted) is in fact quite different, thus it does not even qualify for OverlappingInstances, as the compiler can choose the most specific one.

Then what?

The error is, as always, hinted by the error message. The a b c d | a b c -> d part is not being respected by the code above. So I finally tried this instead:

{-# 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à!

By using (m b) in the last parameter, I was trying to indicate that the final result has the same type as the second and third parameter. But the problem seems to be that the FunctionalDependencies extension does not make the same kind of instance choosing on types as OverlappingInstances, and thus considers b and (m b) "the same" for its purposes. Is this interpretation correct, or am I still missing something?

I can still 'tell' the compiler that c is of the same type as b using the constrain c ~ b, and thus reaching the intended result.

After reading some more material about this, I highly recomend reading this article by Oleg where he generalizes his former solutions that both I and user2407038 linked. I found it quite accessible.

If my interpretation of the FunctionalDependencies above is correct, and TypeFamilies being presented as a more flexible solution for the same problem domain, I wonder if could use them to solve this in another way. Oleg solution mentioned above sure uses them, of course.

like image 322
jcristovao Avatar asked Jan 08 '14 17:01

jcristovao


1 Answers

You don't need a NotAMonad class, or rather, WhatIs is exactly this class already.

{-# 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

You don't strictly need IncoherentInstances but if you want things like whatIs (1 :: Num a => a) to work you need it.

This probably isn't the best way of doing what you want but your use case isn't clear.

Edit: more explanation: First of all: these instances are not overlapping! I included the full list of language pragmas. The error you got said that "Functional dependencies conflict between instance declarations". Say you have the following:

class C a b | a -> b

Supposed you have two instances of C: C a b, C c d (here a is not a rigid type variable; it is just any haskell type). If a is an instantiation of c (or vice versa) then b must be an instantiation of d. This general rule may be somewhat abstract so lets look at your code:

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

Since a is any type, you are declaring that 'for any type a, whatIs a == String'. The second instance declares that 'for any type (m a), whatIs (m a) == (m String)'. Obviously m a is an instantiation of a (any type is an instantiation of a free type variable) but String is never an instantiation of m String.

Why does any of this matter? When the compiler checks to see if fundeps conflict, it only looks at the instance head; that is, the portion to the right of =>. Therefore,

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

is saying 'for any types a,b, whatIs a == b'. Obviously, since a and b are both free variables, they can be instantiated with any other type. So if a == (m a0) you can freely say that b == (m String). The fact that b must be a string becomes known if and only if the first instance is the one that is matched.

Since any types match a and b, WhatIs (IO ()) b matches the first instance. The second instance is used because the compiler will try to match the instances in order of specificity. You can find the 'rules' for determining specificity here.. The simple explanation is that WhatIs a b matches more things so it is more general, and will be used later. (In fact, the instance C a0 a1 a2 .. an where a* is a distinct type variable, is the most general instance and will always be tried last)

Edit: Here is the general solution to your problem. With this code, whatIs = f_map show.

like image 123
user2407038 Avatar answered Oct 27 '22 03:10

user2407038