我需要一个像这样工作的函数:
foo :: Integer -> Integer -> [Integer]
foo a b = do
let result = []
let Coord x y = boo a b
if x > 0
let result = result ++ [3]
if y > 0
let result = result ++ [5]
if x < a
let result = result ++ [7]
if y < b
let result = result ++ [9]
result
我无法使用防护,因为结果可能有多个元素。但正如我所见,我不能在'if'表达式中使用'let':
all_possible_combinations.hs:41:14: parse error on input `let'
如何检查多个表达式并在列表中添加新元素? 我不仅搜索命令式解决方案,还搜索功能性解决方案。
答案 0 :(得分:10)
其他人说过为什么你的尝试不起作用。这是一个简洁的方式来做你想要的:
foo :: Integer -> Integer -> [Integer]
foo a b = map snd (filter fst [(x > 0, 3),
(y > 0, 5),
(x < a, 7),
(y < a, 9)])
where Coord x y = boo a b
那么这是如何运作的?
我们从一对配对列表开始。每对中的第二个元素是我们可能希望包含在foo
的返回值中的值。每对中的第一个元素告诉我们是否确实要包含它。
然后我们调用filter fst
,它会丢弃条件为假的所有对。
最后,我们调用map snd
,它抛弃了条件(所有剩下的条件都是真的),并保留我们感兴趣的值。
答案 1 :(得分:5)
编辑:感谢sepp2k指出了目的。这比我能用我的Haskell色眼镜更加迫切需要......
这是一个快速解决方案:
result = (if x > 0 then [3] else []) ++
(if y > 0 then [5] else []) ++
(if x < a then [7] else []) ++
(if y < b then [9] else [])
(旧答案已删除。)
答案 2 :(得分:5)
三件事:
您可以在let
表达式中使用if
,但需要in
或do
内部块。无论哪种方式,使用您刚刚与let
绑定的名称的表达式都需要位于相同的if
中。即你可以这样做:
if x > 0
then let a = 42 in a+x
else 23
但不是
if x > 0
then let a = 42 in ()
else let a = 23 in ()
print a
您无法使用let
更改现有变量的值。事实上,你永远无法改变变量的值。
如果在=
的RHS上使用=
的LHS上绑定的名称,它将递归地引用自身 - 而不是任何先前的绑定。换句话说,let x = x+1
导致无限递归,它不会将x
的值增加一。
要做你想做的事,你需要将你的代码重构为这样的东西:
let result1 = if x > 0 then [3] else []
let result2 = if y > 0 then result1 ++ [5] else result1
let result3 = if x < a then result2 ++ [7] else result2
if y < b then result3 ++ [9] else result3
请注意,使用++
在最后添加,而不是在末尾使用:
然后reverse
列表,这通常是一个糟糕的想法,性能明智。< / p>
答案 3 :(得分:3)
您认为应该使用if
的方式也存在问题。在Haskell中if
是一个表达式,即它计算为一个值。因此,它必须始终伴有else
分支。马塞洛的例子应该澄清这一点。如果您想要一种命令式语言中的if
语句,那么您可以使用Control.Monad.when
或Control.Monad.unless
。
答案 4 :(得分:2)
遗憾的是,您的代码存在许多问题。第一个问题是顶线上的do
:你没有使用任何monad,所以你根本不应该使用它。其余的代码试图以命令式方式工作,但这(不出所料)在Haskell中不起作用。在Haskell中,每个表达式都需要有一个结果;因此,所有if语句的格式必须为if ... then ... else ...
。同样,每个let绑定必须是let ... in ...
形式;改变变量的值是不可能的,所以如果你离开in
,就什么都不会发生。正因为如此,每一行let result = result ++ [3]
,如果可以执行,将尝试制作一个列表result
,其中包含所有元素,最后三个 - 换句话说,一个自我 - 优惠清单! (这种事物的典型示例是let ones = 1:ones in ones
来创建一个尾部本身的列表,因此只包含1
s。)
所以现在的问题是,你的功能想要做什么?您有四个布尔条件,并希望为每个元素添加一个不同的元素。您可以通过多种方式编写此代码。一种方法是
foo :: Integer -> Integer -> Integer
foo a b = let Coord x y = boo a b
checkAdd cond elem = if cond then (elem :) else id
in foldr (uncurry checkAdd) []
[(x > 0, 3), (y > 0, 5), (x > a, 7), (y > b, 9)]
首先,我们致电boo
并查看结果。然后,我们定义checkAdd
函数,其类型为checkAdd :: Bool -> a -> ([a] -> [a])
。如果它的第一个参数为true,那么它返回一个函数,该函数将其第二个参数添加到列表中;否则,它返回一个对列表无效的函数。所以checkAdd True 1 x == 1:x
和checkAdd False 1 x == x
。最后一行无疑是有点神秘,但它并不太糟糕。如果您有fns = [f1,f2,...,fn]
个功能列表,并希望生成f1(f2(...(fn(x))...))
,那么您可以使用foldr ($) x fns
。除了uncurry checkAdd
而不是$
之外,此折叠类似。 uncurry
导致函数采用元组而不是两个参数,因此当我们在列表上foldr
时,checkAdd
会为每个元素生成一个函数。然后,应用每个函数,您将到达所需的列表。我们没有($) . uncurry checkAdd
的原因是$
实际上只是具有低优先级的身份函数;这是有道理的,因为$
的唯一目的是优先级高但不做任何事情。
当然,这段代码看起来与上面的内容完全不同,但这是尝试完全不同语言的有趣之处: - )
答案 5 :(得分:2)
这种附加输出对Writer
monad非常自然。
import Control.Monad.Writer
foo a b = execWriter $ do
let Coord x y = boo a b
when (x > 0) $ tell [3]
when (y > 0) $ tell [5]
when (x < a) $ tell [7]
when (x < b) $ tell [9]
答案 6 :(得分:1)
正如已经提到的,你正在以错误的方式看待这个问题。您无法在Haskell中更改变量的值,而if-then-else表达式始终具有else子句。我已经制作了一些外观和感觉就像你想要做的那样的代码,但正如你所看到的,有很多更好的方法可以做到这一点。
foo a b = let Coord x y = boo a b in
(if x > 0 then (3 :) else id) .
(if y > 0 then (5 :) else id) .
(if x < a then (7 :) else id) .
(if x < b then (9 :) else id) $
[]