Редизайн классов типа Haskell

Получив некоторую помощь, разобравшись в проблеме, с которой я пытался скомпилировать код, в этом вопросе ( Проблемы с пониманием жалобы GHC на неоднозначность) Уилл Несс предложил мне перепроектировать классы типов, чтобы избежать решения, которым я не был полностью доволен.

Рассматриваемые классы типов:

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

Я пытаюсь создать расширяемый эволюционный алгоритм в Haskell, который должен поддерживать разные Genomes а также Phenotypes, Например один Genome может быть битовый массив, другой может быть список целых, и Phenotypes будет также отличаться от простого списка двойников, представляющих движение войск в http://en.wikipedia.org/wiki/Colonel_Blotto, или он может представлять ANN.

Так как Phenotype разработан из Genome используемые методы должны быть вполне взаимозаменяемыми, и один Genome класс должен иметь возможность поддерживать несколько Phenotypes предоставляя другой метод разработки (это может быть сделано статически в коде, и не должно выполняться динамически во время выполнения).

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

Некоторый код, который я хочу адаптировать к этим классам типов:

-- |Full generational replacement selection protocol
fullGenerational :: (Phenotype b) =>
    (Int -> [b] -> IO [(b, b)]) -> --Selection mechanism
    Int -> --Elitism
    Int -> --The number of children to create
    Double -> --Crossover rate
    Double -> --Mutation rate
    [b] -> --Population to select from
    IO [b] --The new population created
fullGenerational selection e amount cross mute pop = do
    parents <- selection (amount - e) pop
    next <- breed parents cross mute
    return $ next ++ take e reverseSorted
            where reverseSorted = reverse $ sortBy (fit pop) pop

breed :: (Phenotype b, Genome a) => [(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

Я понимаю, что этот код нужно будет изменить и добавить новые ограничения, но я хотел показать часть кода, который я имею в виду, используя классы типов с. Например, для полной замены поколений, описанной выше, не нужно ничего знать о Genome правильно функционировать; нужно только знать, что Phenotypes может произвести Genome это произвело это так, чтобы это могло развести их вместе и создать новых детей. Код для fullGenerational должно быть как можно более общим, чтобы однажды новый Phenotype разработан или лучше Genome создан, его не нужно менять.

Как можно изменить указанные выше классы типов, чтобы избежать проблем, которые у меня были / возникают с неоднозначностью классов типов, при сохранении тех свойств, которые мне нужны в общем коде советника (который должен использоваться повторно)?

1 ответ

Решение

"нужно только знать, что фенотипы могут продуцировать геном, который произвел его"

это означает, что фенотип на самом деле является отношением двух типов, а другой является типом генома, используемым для создания данного фенотипа:

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

import Data.List (sortBy)

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

breed :: (Phenotype b a, Genome a) => [(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

-- |Full generational replacement selection protocol
fullGenerational :: (Phenotype b a, Genome a) =>
    (Int -> [b] -> IO [(b, b)]) -> --Selection mechanism
    Int -> --Elitism
    Int -> --The number of children to create
    Double -> --Crossover rate
    Double -> --Mutation rate
    [b] -> --Population to select from
    IO [b] --The new population created
fullGenerational selection e amount cross mute pop = do
    parents <- selection (amount - e) pop
    next <- breed parents cross mute
    return $ next ++ take e reverseSorted
            where reverseSorted = reverse $ sortBy (fit pop) pop

fit pop a b = LT   -- dummy function

Это компилируется. Каждый фенотип должен обеспечивать ровно одну реализацию genome,

Другие вопросы по тегам