重新设计Haskell类型类 [英] Redesign of Haskell type classes
问题描述
在得到一些帮助之后,了解我试图编译代码的问题,在这个问题中(理解GHC对歧义的抱怨有困难)Ness建议我重新设计我的类型类,以避免我不完全满意的解决方案。
类型有问题的类是这些:
class(Eq a,Show a)=>基因组a其中
crossover ::(小数b)=> b - > a - > a - > IO(a,a)
mutate ::(Fractional b)=> b - > a - > IO a
develop ::(表型b)=> a - > b
class(Eq a,Show a)=>表型a其中
- 在Coevolution的情况下,每个表型需要与人群中的所有其他表型进行比较
fitness :: [a] - > a - > Int
genome ::(Genome b)=> a - > b
我试图在Haskell中创建一个可扩展的进化算法,它应该支持不同的 Genomes
和表型
。例如,一个 Genome
可以是一个位数组,另一个可以是一个整数列表,而 Phenotypes
也可以是从 http://en.wikipedia.org/wiki/Colonel_Blotto表示部队移动的双打列表,或者它可以代表一个ANN。
因为 Phenotype
是从 Genome
所使用的方法必须是可以互换的,一个 Genome
类应该能够支持多个表型
通过提供不同的开发方法(这可以在代码中静态完成,并且不必在运行时动态完成)。
代码使用这些类型的类应该在大多数情况下对所使用的具体类型感到不知所措,这就是导致我提出上述问题的原因。
部分我想适应这些类型的代码是:
- |全代替换选择协议
fullGenerational ::(Phenotype b)=>
(Int→> [b] - > IO [(b,b)]) - > - 选择机制
Int - > --Elitism
Int - > - 要创建的子女数量
双倍 - > - 分号
双 - > - 突变率
[b] - > - 人口选择
IO [b] - 新人口创建
全基因选择e数量交叉静音弹出=做
父母< - 选择(金额 - e)弹出
next< - 品种父母交叉静音
返回$ next ++采取反向排序
其中reverseSorted =反向$ sortBy(适合流行)流行
品种::(表型b,基因组a)=> [(b,b)] - >双 - >双 - > IO [b]
繁殖父母交叉静音= do
children <-mapM(\(爸爸,妈妈) - >交叉交叉(基因组父亲)(基因组妈妈))父母
let ch1 = map fst children ++ map snd children
mutated< - mapM(mutate mute)ch1
return $ map develop mutated
据我所知,这段代码将不得不被改变,新的约束将被添加,但我想显示一些代码,我记住使用类型类与。例如,上面的全代替换不需要知道底层 Genome
的任何功能以正常工作;它只需要知道 Phenotypes
可以产生产生它的 Genome
,这样它就可以将它们一起繁殖并创建新的儿童。 fullGenerational
的代码应尽可能通用,以便一旦设计了新的 Phenotype
或更好的 Genome
被创建,它不需要改变。
上面的类型类可以如何更改以避免问题I是/我的类型模糊不清,同时保留我想要的一般EA代码中的属性(应该是可重用的)?
它只需要知道表型可以产生产生它的基因组
这意味着表型实际上是两种关系,另一种是用于产生给定表型的基因组类型MultiParamTypeClasses# - }
{ - #LANGUAGE FunctionalDependencies# - }
import Data.List(sortBy)
$ b $ class(Eq a,Show a)=>基因组a其中
crossover ::(小数b)=> b - > a - > a - > IO(a,a)
mutate ::(Fractional b)=> b - > a - > IO a
develop ::(表型b a)=> a - > b
class(Eq a,Show a,Genome b)=>表型a b | a - > b其中
- 在Coevolution的情况下,每个表型需要与
相比较 - 在人口中每隔一个
fitness :: [a] - > a - > Int
genome :: a - > b
品种::(表型b a,基因组a)=> [(b,b)] - >双 - >双 - > IO [b]
繁殖父母交叉静音=做
儿童<-mapM(\(爸爸,妈妈) - >交叉交叉(基因组父亲)(基因组妈妈))
父母
let ch1 = map fst children ++ map snd children
mutated< - mapM(mutate mute)ch1
return $ map开发变异
- |完整代代替代选择方案
fullGenerational ::(表型ba,基因组a)=>
(Int→> [b] - > IO [(b,b)]) - > - 选择机制
Int - > --Elitism
Int - > - 要创建的子女数量
双倍 - > - 分号
双 - > - 突变率
[b] - > - 人口选择
IO [b] - 新人口创建
全基因选择e数量交叉静音弹出=做
父母< - 选择(金额 - e)弹出
next< - 品种父母交叉静音
返回$ next ++采取反向排序
where reverseSorted =反向$ sortBy(适合流行)流行
适合流行ab = LT - 虚函数
编译。每个表型必须提供基因组
的恰好一个实现。
After getting some help, understanding the problem I had trying to compile the code, in this question (Trouble understanding GHC complaint about ambiguity) Will Ness suggested I redesign my type classes to avoid a solution I was not completely happy with.
The type classes in question are these:
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
I'm trying to create an extendible evolutionary algorithm in Haskell which should support different Genomes
and Phenotypes
. For instance one Genome
could be a bit array, another could be a list of ints, and the Phenotypes
will also be diverse from just a list of doubles representing troop movement in http://en.wikipedia.org/wiki/Colonel_Blotto, or it could represent an ANN.
Since a Phenotype
is developed from a Genome
the methods used must be quite interchangeable, and one Genome
class should be able to support multiple Phenotypes
by supplying a different develop method (this can be done statically in code, and does not have to be done dynamically at runtime).
The code using these type classes should, for the most part, be blissfully unaware of the specific types used, which is what lead me to ask the question mentioned above.
Some of the code that I want to adapt to these type classes are:
-- |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
I understand that this code will have to be changed and new constraints will have to be added, but I wanted to show some of the code I have in mind using the type classes with. For instance, the full generational replacement above does not need to know anything about the underlying Genome
to function properly; it only needs to know that Phenotypes
can produce the Genome
that produced it so that it can breed them together and create new children. The code for fullGenerational
should be as general as possible so that once a new Phenotype
is designed or a better Genome
is created, it does not need to be changed.
How can the type classes above be changed to avoid the problems I was/am having with type class ambiguity while retaining the properties I want in the general EA code (which should be reusable)?
"it only needs to know that Phenotypes can produce the Genome which produced it "
this means that Phenotype is really a relation on two types, the other being a Genome type used to produce a given Phenotype:
{-# 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
This compiles. Each Phenotype will have to provide exactly one implementation of genome
.
这篇关于重新设计Haskell类型类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!