Редизайн классов типа 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
,