2

I'm trying to create a simple Haskell evolutionary algorithm and I'm trying to make it as general as possible. This was originally an assignment which I solved with Python at the time which I wanted to go back to when I had more time and solve in Haskell. The assignment needed the code to be very flexible and I have tried to recreate that in my preliminary Haskell implementation.

In the code below you can see the error GHC is giving me:

Ambiguous type variable 'a0' in the constraint:
  (Genome a0) arising from a use of 'crossover'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: crossover cross (genome dad) (genome mom)
In the first argument of 'mapM', namely
  '(\ (dad, mom) -> crossover cross (genome dad) (genome mom))'
In a stmt of a 'do' block:
  children <- mapM
                (\ (dad, mom) -> crossover cross (genome dad) (genome mom)) parents

I have the following class declarations:

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 -> b

class (Eq a, Show a) => Phenotype a 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

The code that is giving me the problem is:

breed :: (Phenotype b) => [(b, b)] -> Double -> Double -> IO [b]
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

I'm not entirely sure I understand the error and I would think that because both mom and dad are of class Phenotype which means they must support a genome method this should not be a problem. One problem I can see is that GHC can't make sure that the newly created Genomes will result in the same Phenotypes as it receives, but I'm not sure of how to solve that problem. There also might be some problems with the class declarations that I have overlooked so I would probably help to get someone better than me to look it over.

Nordmoen
  • 215
  • 1
  • 7
  • The problem stems from the source that calls the breed function. What is the type of `b`, from where the breed function is called? – mhitza Apr 02 '13 at 13:08
  • @mhitza The type of `b` in the source should also be of just the class Phenotype if I understand it correctly. The full code is here http://hpaste.org/85037 – Nordmoen Apr 02 '13 at 13:13

1 Answers1

4

children :: Genome a => [(a,a)]. ch1 :: Genome a => [a]. etc. "What data type is a?" - asks Haskell. "I need to check it belongs to the Genome type class".

Nothing in the code determines the concrete data type of a, you only operate with methods.

You need to put a into the return type too, and add Genome a into the constraint, so that it will be determined by a call site of breed:

breed :: (Phenotype b, Genome a) => [(b, b)] -> Double -> Double -> (Something a,IO [b])

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 1
    Hm, I think I understand the problem, but I still don't see why it is a problem. Shouldn't the fact that `ch1 :: Genome a => [a]` be enough for it to determine that all members of `ch1` must support `mutate` and `develop` which should ensure that I get a `Phenotype` back from the method. My thinking is that neither breed nor the caller needs to know about the intermediary step down to `Genome` as long as it knows it is getting a proper `Phenotype` back, which might be completely wrong – Nordmoen Apr 02 '13 at 13:32
  • 1
    @Nordmoen each member of ch1 is of type `a` that must belong to Genome type class. What is the concrete data type? Is not known. We know it must support the operation, but what *is* the operation? We must perform it. What you describe is a function polymorphic in both types, one a Genome, another a Phenotype. *Which exactly* Phenotype, is determined by call site of `breed`, according to your type signature. Not so for Genome. If you want the specific Genome to remain hidden, you must provide the specific Genome type inside `breed`. Or, make `breed` polymorphic in Genomes as well. – Will Ness Apr 02 '13 at 13:45
  • ah I see, my understanding of the interaction between the classes and methods are/were quite off. I'm wondering however if there is a way other than having to return some of the `Genomes`? They would just be thrown away as everyone above breed are just interested in the `Phenotypes` it produces – Nordmoen Apr 02 '13 at 13:46
  • 2
    @Nordmoen You will have to redesign your types, for that. You can ask another question, about how to redesign your types so that you don't have to do this. – Will Ness Apr 02 '13 at 13:47