在'if'表达式中使用'let'

时间:2010-10-21 08:37:36

标签: haskell

我需要一个像这样工作的函数:

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'

如何检查多个表达式并在列表中添加新元素? 我不仅搜索命令式解决方案,还搜索功能性解决方案。

7 个答案:

答案 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)

三件事:

  1. 可以let表达式中使用if,但需要indo内部块。无论哪种方式,使用您刚刚与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
    
  2. 您无法使用let更改现有变量的值。事实上,你永远无法改变变量的值。

  3. 如果在=的RHS上使用=的LHS上绑定的名称,它将递归地引用自身 - 而不是任何先前的绑定。换句话说,let x = x+1导致无限递归,它不会将x的值增加一。

  4. 要做你想做的事,你需要将你的代码重构为这样的东西:

    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.whenControl.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:xcheckAdd 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) $
         []