36

Since these flexible contexts and instances aren't available in the Haskell standard, I assume there are potential problems when using them. What are they? Can they lead to some ambiguity, undecidability, overlapping instances, etc.?

There is a similar question that asks only about FlexibleInstances, not FlexibleContexts, but the answer only says "that it's safe to use them".

Community
  • 1
  • 1
Petr
  • 62,528
  • 13
  • 153
  • 317
  • 4
    As far as I have gathered, they are just things they didn't think about when they wrote the standard, but they turned out useful so GHC included them as extensions. The obvious pitfall I can think of is that code written using them will not work with other compilers. But I'm sure someone else knows more. – kqr Aug 14 '13 at 11:08
  • you can see an example in this [answer](http://stackoverflow.com/a/15770150/849891)'s [edit history](http://stackoverflow.com/posts/15770150/revisions). I [first did something wrong](http://stackoverflow.com/revisions/95690635-a708-4c29-a22d-95b23038258d/view-source) (in rev. 1) and got suggested by the GHCi to add FlexibleContexts. And it compiled. (I initially didn't have Genome constraint in the class at all, and w/ FlexContxs it compiled). – Will Ness Aug 14 '13 at 13:11
  • @WillNess This is very hard to discover, would you perhaps give an answer based on it? – Petr Aug 15 '13 at 17:41

1 Answers1

16

I once stumbled upon the following. Answering this question, I first tried this code:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}

class (Eq a, Show a) => Genome a where
    crossover       :: (Fractional b) => b -> a -> a -> IO (a, a)
    mutate          :: (Fractional b) => b -> a -> IO a
    develop         :: (Phenotype b a)  => a -> b

class (Eq a, Show a) => Phenotype a b | a -> b where
    --  In case of Coevolution where each phenotype needs to be compared to 
    --  every other in the population
    fitness         :: [a] -> a -> Int 
    genome          :: (Genome b) => a -> b    -- here, the problem

breed parents cross mute = do
    children <- mapM (\ (dad, mom) -> crossover cross (genome dad) (genome mom)) 
                     parents
    let ch1 = map fst children ++ map snd children
    mutated <- mapM (mutate mute) ch1
    return $ map develop mutated

And got a compilation error and a suggestion by GHCi to add the FlexibleContexts option. When I did, it compiled OK. But this was actually not a right thing to do, as the constraint declaration introduced new scope for type variables, and b in genome's type signature became completely unrelated to the one in the type class; yet FlexibleContexts provided a cover for this.

With the constraint specified properly at the type class level,

class (Eq a, Show a, Genome b) => Phenotype a b | a -> b where
    --  In case of Coevolution where each phenotype needs to be compared to 
    --  every other in the population
    fitness         :: [a] -> a -> Int 
    genome          :: a -> b

it passed compilation without requiring the FlexibleContexts option.

Community
  • 1
  • 1
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 3
    Wasn't the problem that by declaring `genome :: (Genome b) => a -> b` with the type class constraint, it actually introduced a new type variable `b` that shadowed the original one? – Petr Aug 16 '13 at 13:43
  • @PetrPudlák so declaring a constraint introduces new scope for type variables? In that case, the genome method *could* return a Genome type completely unrelated to the Phenotype type it belonged to. This would surely be a design flow, and FlexibleInstances would provide a cover for it, right? That would indeed qualify for a pitfall then. :) – Will Ness Aug 16 '13 at 15:48
  • 1
    Too bad `-Wall` doesn't warn here. (I don't think this is an error... just name shadowing. That said, the code has a bug due to this.) – Thomas Eding Sep 27 '13 at 04:41
  • @ThomasEding yes, "this was actually [done] *in* error" i.e. it was a design error (not *an* actual code error), is what I meant. Sorry for being needlessly cryptic. :) Thanks. – Will Ness Sep 27 '13 at 08:58
  • Any explanation as to why `FlexibleContexts` eliminated the error? In other words: does `FlexibleContexts` simply allow this sort of shadowing? Why do you reckon it's illegal without the extension? – Erik Kaplun Aug 17 '15 at 10:47
  • @ErikAllik as explained by Petr Pudlak in the comments above, ""declaring genome :: (Genome b) => a -> b with the type class constraint [...] actually introduced a new type variable b" unrelated to the class's type variable, without the extension. You'll have to find someone more knowledgeable in these things to explain all of this properly, I just provided some empirical evidence in this answer AFAIAC, sorry. Try asking on reddit/haskell, maybe? – Will Ness Aug 17 '15 at 11:02
  • Yeah, I read that comment. And yeah, I was looking for somewhat deeper insight as to the rationale behind this behavior :) – Erik Kaplun Aug 17 '15 at 11:03
  • @ErikAllik note, that there was already a bounty offered on the question, and it expired without getting any additional answers... – Will Ness Aug 17 '15 at 11:04