重新设计Haskell类型类 [英] Redesign of Haskell type classes

查看:116
本文介绍了重新设计Haskell类型类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在得到一些帮助之后,了解我试图编译代码的问题,在这个问题中(理解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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆