What are the pitfalls of using FlexibleContexts and FlexibleInstances?
Asked Answered
G

1

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".

Gaither answered 14/8, 2013 at 10:41 Comment(3)
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.Goodrow
you can see an example in this answer's edit history. I first did something wrong (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).Natant
@WillNess This is very hard to discover, would you perhaps give an answer based on it?Gaither
N
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.

Natant answered 16/8, 2013 at 13:26 Comment(8)
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?Gaither
@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. :)Natant
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.)Burletta
@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.Natant
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?Futhark
@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?Natant
Yeah, I read that comment. And yeah, I was looking for somewhat deeper insight as to the rationale behind this behavior :)Futhark
@ErikAllik note, that there was already a bounty offered on the question, and it expired without getting any additional answers...Natant

© 2022 - 2024 — McMap. All rights reserved.