Haskell 中的基因编程

2024-03-19

有 GenProg (http://hackage.haskell.org/package/genprog http://hackage.haskell.org/package/genprog)例如,但这仅涉及数值优化,在本例中找到描述数据的方程。

但我需要 for 循环、if 语句、when 语句、布尔检查等。我需要能够生成命令式结构。对此有什么想法吗?到目前为止,我最好的选择似乎是 husk-scheme,我可以在 Haskell 中将 Scheme 代码作为 DSL 运行。当然一定有更好的方法吗?


如果您正在寻找类似于 S 表达式的东西,那么在 Haskell 中可以很容易地对其进行建模。举例来说,我们想要用变量表示简单的代数方程,例如

x + 5 / (y * 2 - z)

这可以用 Haskell 中的简单 AST 来表示,特别是我们可以将其实现为

data Expr
    = Lit Double        -- Literal numbers
    | Var Char          -- Variables have single letter names
    | Add Expr Expr     -- We can add things together
    | Sub Expr Expr     -- And subtract them
    | Mul Expr Expr     -- Why not multiply, too?
    | Div Expr Expr     -- And divide
    deriving (Eq)

这将使我们将上面的方程表示为

Add (Var 'x') (Div (Lit 5) (Sub (Mul (Var 'y') (Lit 2)) (Var 'z')))

但这写起来相当笨拙并且难以阅读。让我们先做一些工作Show魔法,让它打印得漂亮:

instance Show Expr where
    showsPrec n (Lit x)   = showParen (n > 10) $ showsPrec 11 x
    showsPrec n (Var x)   = showParen (n > 10) $ showChar x
    showsPrec n (Add x y) = showParen (n >  6) $ showsPrec 7 x . showString " + " . showsPrec 7 y
    showsPrec n (Sub x y) = showParen (n >  6) $ showsPrec 7 x . showString " - " . showsPrec 7 y
    showsPrec n (Mul x y) = showParen (n >  7) $ showsPrec 8 x . showString " * " . showsPrec 8 y
    showsPrec n (Div x y) = showParen (n >  7) $ showsPrec 8 x . showString " / " . showsPrec 8 y

如果您不理解这里发生的所有事情,那也没关系,通过一些内置函数,可以轻松有效地构建带有括号的字符串以及所有有趣的东西,从而使很多复杂性变得容易。它几乎是从文档中复制出来的Text.Show。现在,如果我们从上面打印出我们的表达式,它将看起来像

x + 5.0 / (y * 2.0 - z)

现在简化这些表达式的构建。因为它们几乎都是数字,所以我们可以实现Num(除了abs and signum) and Fractional:

instance Num Expr where
    fromInteger = Lit . fromInteger
    (+) = Add
    (-) = Sub
    (*) = Mul
    abs = undefined
    signum = undefined

instance Fractional Expr where
    (/) = Div
    fromRational = Lit . fromRational

现在我们可以输入上面的表达式

Var 'x' + 5 / (Var 'y' * 2 - Var 'z')

即使我们必须手动指定变量,这至少更容易直观地解析。

现在我们有了相当多的输入和输出,让我们专注于评估这些表达式。由于这里有变量,因此我们需要某种将变量与值关联起来的环境

import Data.Map (Map)
import qualified Data.Map as M

type Env = Map Char Double

现在它只是基本的模式匹配(以及辅助函数)

import Control.Applicative

binOp :: (Double -> Double -> Double) -> Expr -> Expr -> Env -> Maybe Double
binOp op x y vars = op <$> evalExpr x vars <*> evalExpr y vars

evalExpr :: Expr -> Env -> Maybe Double
evalExpr (Lit x)   = const $ Just x
evalExpr (Var x)   = M.lookup x
evalExpr (Add x y) = binOp (+) x y
evalExpr (Sub x y) = binOp (-) x y
evalExpr (Mul x y) = binOp (*) x y
evalExpr (Div x y) = binOp (/) x y

现在我们可以使用evalExpr通过变量替换来评估我们的迷你语言中的表达式。如果存在未定义的变量,则所有错误处理均由Maybemonad,我们甚至可以通过隐含环境参数来减少重复。对于简单的 DSL 表达式来说,这都是非常标准的。


因此,为了有趣一点,生成随机表达式和(最终)突变。为此,我们需要System.Random。我们希望能够生成不同复杂度的表达式,因此我们将粗略地表达它。由于它是随机的,因此我们有可能获得比指定的更短或更深的树。您可能需要对此进行调整和调整以获得所需的结果。首先,因为我有先见之明已经编写了这段代码,所以让我们定义两个助手来生成随机文字和随机变量:

randomLit, randomVar :: IO Expr
randomLit = Lit <$> randomRIO (-100, 100)
randomVar = Var <$> randomRIO ('x',  'z')

这里没什么令人兴奋的,我们得到 -100 到 100 之间的双精度数,以及最多 3 个变量。现在我们可以生成大型表达式树。

generateExpr :: Int -> IO Expr
-- When the depth is 1, return a literal or a variable randomly
generateExpr 1 = do
    isLit <- randomIO
    if isLit
        then randomLit
        else randomVar
-- Otherwise, generate a tree using helper
generateExpr n = randomRIO (0, 100) >>= helper
    where
        helper :: Int -> IO Expr
        helper prob
            -- 20% chance that it's a literal
            | prob < 20  = randomLit
            -- 10% chance that it's a variable
            | prob < 30  = randomVar
            -- 15% chance of Add
            | prob < 45  = (+) <$> generateExpr (n - 1) <*> generateExpr (n - 1)
            -- 15% chance of Sub
            | prob < 60  = (-) <$> generateExpr (n - 1) <*> generateExpr (n - 1)
            -- 15% chance of Mul
            | prob < 75  = (*) <$> generateExpr (n - 1) <*> generateExpr (n - 1)
            -- 15% chance of Div
            | prob < 90  = (/) <$> generateExpr (n - 1) <*> generateExpr (n - 1)
            -- 10% chance that we generate a possibly taller tree
            | otherwise = generateExpr (n + 1)

该函数的大部分内容只是指定生成给定节点的概率,然后为每个运算符生成左节点和右节点。由于我们重载了,我们甚至必须使用普通的算术运算符Num,多么方便啊!


现在,请记住,我们仍然可以在这个构造函数上进行模式匹配Expr用于其他操作的类型,例如替换节点、交换节点或测量深度。为此,您只需将其视为 Haskell 中的任何其他二叉树类型,除了它有 2 个叶构造函数和 4 个节点构造函数。至于突变,您必须编写遍历此结构的代码,并选择何时交换节点以及用什么交换节点。它将生活在IOmonad 因为您将生成随机值,但这应该不会太困难。这个结构应该很容易根据需要进行扩展,例如如果您想添加三角函数和求幂,您只需要更多的构造函数,更多的表达式evalExpr,以及中的适当条款helper,以及一些概率调整。

您可以获得此示例的完整代码here https://gist.github.com/609ebd2073c6342827b6。希望这可以帮助您了解如何在 Haskell 中制定类似 S 表达式的内容。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Haskell 中的基因编程 的相关文章

  • 使用包阴影符号

    例如 我有这个包定义 它遮蔽了 COMMON LISP LISTEN defpackage shadows use common lisp shadow listen export listen 然后我想使用另一个包中的这个包 比如说 de
  • 扫雷板标签(初级)

    我们得到了一份家庭作业 其中我们得到了一个类似扫雷的样本板 其中有空格而不是数字 板是 String 形式 并且已经放置了地雷 我们需要的是创建一个函数 用数字替换所有空格 数字等于相邻地雷的数量 除了删除所有带零的空格之外 我无法取得任何
  • 函数式 GUI 编程可能吗? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我最近发现了 FP bug 试图学习 Haskell 到目前为止所看到的东西给我留下了深刻的印象 一流的函数 惰性求值和所有其他好处 我还不是专
  • 如何使用 Haskell 创建符号链接?

    如何使用 Haskell 创建符号链接 这directory据我所知 包没有提供一种方法来做到这一点 Creating a symbolic link is non portable For example the creation sym
  • 在Emacs中,这个错误是什么意思? “警告:运行时需要 cl 包”

    我正在字节编译一个模块 它给了我这个警告 Warning cl package required at runtime 为什么这是一个警告 我很清楚我正在使用cl包裹 事实上有一个 require cl 模块中的语句 使用有什么问题吗cl
  • 将元组列表转换为列表列表 Haskell

    I have m n p q r s 我怎样才能将它转换为 m n p q r s 谁能帮帮我吗 谢谢 编写一个函数将一对转换为列表 pairToList a a gt a pairToList x y x y 那么你只需要map pair
  • Haskell:为什么将辅助函数命名为“go”?

    I see go在阅读 Haskell 材料或源代码时 我经常会遇到这样的情况 但我从来没有真正感到舒服 我猜它在我的脑海中具有 goto 的负面含义 我开始用 LYAH 学习 Haskell 这就是我开始使用 Haskell 的原因acc
  • 哪种语言(在 JVM 上运行)最适合创建 DSL?

    我们需要创建复杂的固定长度和可变长度字符串 这些字符串可能代表客户资料 订单等 你们建议使用哪种基于 JVM 的编程语言 想法是让最终用户使用此 DSL 创建字符串 所以我正在寻找验证 代码完成等 Groovy http docs code
  • Emacs 在 haskell 模式下挂起,并调用 下面的 haskell-load-file 调用

    当在 Haskell 文件中时 我使用C c C l运行命令inferior haskell load file其目的是将当前文件加载到 GHCI 解释器中 但 Emacs 会挂起 直到我点击C g 有人知道我怎样才能让它发挥作用吗 all
  • Haskell:单词,取消单词分隔符

    有什么办法可以提供分隔符words and unwords在haskell中 使其类似于python中的split和join 另请查看友好的包裹split 它提供了一个模块Data List Split http hackage haske
  • 陷入状态 Monad

    我想使用节点和唯一键的 IntMap 创建一个图形结构 这个话题已经被很好地涵盖了here https stackoverflow com questions 12941625 ids from state monad in haskell
  • 在 Haskell 中的列表末尾添加一个元素

    我是 Haskell 的初学者 我正在尝试在列表末尾添加一个元素 我输入一个像 1 2 3 4 这样的列表和一个数字 10 我想要一个像这样的输出 1 2 3 4 10 My code func a a func a x xs x func
  • “实例显示状态”无法编译

    这是我试图弄清楚的 State Monad 代码 data State a State Int gt a Int instance Monad State where return x State c gt x c State m gt g
  • 由相同数据类型的不同构造函数共享的 Haskell 记录访问器

    关于 Haskell 记录的基本问题 如果我定义这个数据类型 data Pet Dog name String Cat name String deriving Show 以下作品 main do let d Dog name Spot c
  • 指定 ghci 中“加载”操作的搜索路径

    In 加载源文件 http www haskell org ghc docs 7 6 1 html users guide loading source files html它指出查找源文件的搜索路径是使用 i 选项指定的 ghci idi
  • 功能段落

    抱歉 我还不太明白 FP 我想将一系列行分割成一系列行序列 假设一个空行作为段落划分 我可以在 python 中这样做 如下所示 def get paraghraps lines paragraphs paragraph for line
  • 何时使用 STRef 或 IORef?

    STRef 和 IORef 之间到底有什么区别 何时使用它们 据我所知 它们都是可变状态 那么它们存在的意义是什么 您可以在其中做更多事情IO单子比ST单子 后者提供可变引用 前者提供可变引用 异常捕获 线程 当然还有IO 使用可以解决问题
  • Haskell 错误:“非详尽模式”

    所以我有这个功能 当我尝试像这样使用它时 合并排序列表 1 1 1 1 它给了我一个错误 1 1 例外 SortFunctions hs 86 1 91 89 非详尽 函数 mergeSortedLists 中的模式 85 mergeSor
  • Haskell 程序的 -hc 配置文件中的 PINNED 是什么意思?

    我正在尝试分析我的应用程序 分析内存使用情况时 hcRTS 选项 我注意到很多内存标记为 PINNED 当与 hy内存被标记为ARR WORDS 该程序使用以下命令创建 2400 2400 双精度矩阵Data Packed Matrixhm
  • 按广度优先顺序列出目录所有内容导致效率低下

    我编写了一个 Haskell 模块来按广度优先顺序列出目录的所有内容 下面是源代码 module DirElements dirElem where import System Directory getDirectoryContents

随机推荐