例如,有GenProg({{link1:http://hackage.haskell.org/package/genprog}}),但它只处理数值优化,在这种情况下找到描述数据的方程。
但我需要for循环,if语句,when语句,布尔检查等。我需要能够生成命令式结构。您对此有何想法?到目前为止,我最好的选择似乎是husk-scheme,在其中可以将Scheme代码作为Haskell中的DSL运行。肯定有更好的方法吗?
但我需要for循环,if语句,when语句,布尔检查等。我需要能够生成命令式结构。您对此有何想法?到目前为止,我最好的选择似乎是husk-scheme,在其中可以将Scheme代码作为Haskell中的DSL运行。肯定有更好的方法吗?
x + 5 / (y * 2 - z)
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')))
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
x + 5.0 / (y * 2.0 - z)
Num
(除了abs
和signum
)和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
来评估我们的迷你语言中带有变量替换的表达式。如果存在未定义的变量,则所有错误处理都由Maybe
单子处理,我们甚至能够通过使环境参数隐式来减少重复。对于简单的表达式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个节点构造函数。至于突变,您将不得不编写遍历此结构的代码,并选择何时交换节点以及用什么交换它们。它将位于IO
monad中,因为您将生成随机值,但这应该不太困难。如果需要,这个结构应该很容易扩展,例如,如果您想添加三角函数和指数函数,您只需要更多的构造函数,在evalExpr
中添加更多表达式,并在helper
中添加适当的子句,以及一些概率调整。x+22/4-y
。还需要(例如)if x+22/4-y < someInt then doAction
。您有任何想法吗? - Robotijncase evalExpr some_expr varMap of { Nothing -> handleFailure; Just x -> when x < someInt then doAction }
,或者你可以使用更好的单子来处理 IO
和 Maybe
,比如 MaybeT IO
。 - bheklilr
Control.Monad
中的函数,如forM
、when
、guard
(如果有MonadPlus
)等。在 Haskell 中做这个有什么问题吗?你只需要一个能够表示你要生成的方程类型的结构即可。 - bheklilr如果 object.xcoord < someInt 则旋转 someInt 度;当 object.distance > someInt 时移动 someInt cm;
同时能够使用 for 循环、when等。 - Robotijn