【发布时间】:2014-08-03 17:15:28
【问题描述】:
注意下面的纯函数,用命令式语言:
def foo(x,y):
x = f(x) if a(x)
if c(x):
x = g(x)
else:
x = h(x)
x = f(x)
y = f(y) if a(y)
x = g(x) if b(y)
return [x,y]
该函数表示您必须增量更新变量的样式。在大多数情况下可以避免这种情况,但在某些情况下这种模式是不可避免的——例如,为机器人编写烹饪程序,这本质上需要一系列步骤和决策。现在,假设我们试图在 Haskell 中表示 foo。
foo x0 y0 =
let x1 = if a x0 then f x0 else x0 in
let x2 = if c x1 then g x1 else h x1 in
let x3 = f x2 in
let y1 = if a y0 then f y0 else y0 in
let x4 = if b y1 then g x3 else x3 in
[x4,y1]
该代码有效,但由于需要手动管理数字标签,它过于复杂且容易出错。请注意,在设置了x1 之后,x0 的值不应再次使用,但它仍然可以。如果您不小心使用了它,那将是一个未检测到的错误。
我已经设法使用 State monad 解决了这个问题:
fooSt x y = execState (do
(x,y) <- get
when (a x) (put (f x, y))
(x,y) <- get
if c x
then put (g x, y)
else put (h x, y)
(x,y) <- get
put (f x, y)
(x,y) <- get
when (a y) (put (x, f y))
(x,y) <- get
when (b y) (put (g x, x))) (x,y)
这样,标签跟踪的需求以及意外使用过时变量的风险就消失了。但是现在代码很冗长,更难理解,主要是因为(x,y) <- get的重复。
那么:有什么更易读、更优雅、更安全的方式来表达这种模式?
【问题讨论】:
-
嗯,首先,您只需要一个
let开头和一个in结尾。此外,有状态的计算可以用 state monad 来表达,尽管我不确定在这种情况下这是否有很大的改进;首先,它实际上并不比您的命令式示例长。 -
@Cubic 是真的,谢谢。问题更多在于数字标记,因为它令人困惑,容易出错。例如,
x1设置后,x0不应该再次使用,但如果你犯了错误并这样做,这将是一个未检测到的错误。我试图通过 State monad 找到答案,但代码变得更加臃肿(尽管更正确)。我想我会尽快更新问题。 -
我建议您停止强制式思考,而是以功能性方式思考。我同意适应新模式需要一些时间,但尝试将命令式思想转换为函数式语言并不是一个好方法。如果我知道您要达到的目标,我可以帮助您以更优雅的方式解决您的问题。不幸的是,你的
foo函数并没有给我太多的想法。 -
我是,但这是一个必要的情况。具体来说,它是一种决策算法,用于决定 AI 在 TCG 游戏中应如何使用他的牌。就像烹饪一样,它自然会涉及一系列步骤和决策——我并没有做太多的事情。
-
你绝对可以使用 state monad 的 modify 函数来清理它。
标签: haskell coding-style functional-programming